├── .gitignore ├── .travis.yml ├── CMakeLists.txt ├── LICENSE ├── NOTICE ├── README.md ├── appveyor.yml ├── config-cpp-dependencies.txt ├── example ├── CMakeLists.txt ├── DataAccess │ ├── CMakeLists.txt │ └── DA.h ├── Engine │ ├── CMakeLists.txt │ ├── Engine.cpp │ ├── Engine.h │ └── OldEngine.h ├── Framework │ ├── CMakeLists.txt │ └── framework.h ├── System │ ├── CMakeLists.txt │ ├── Engine.h │ ├── System.cpp │ └── System.h ├── UI │ ├── CMakeLists.txt │ ├── Display.cpp │ └── Display.h ├── config-cpp-dependencies.txt ├── dependencies.png ├── main │ ├── CMakeLists.txt │ └── main.cpp └── newdependencies.png ├── src ├── Analysis.cpp ├── Analysis.h ├── CMakeLists.txt ├── CmakeRegen.cpp ├── CmakeRegen.h ├── Component.cpp ├── Component.h ├── Configuration.cpp ├── Configuration.h ├── Constants.h ├── FilesystemInclude.h ├── FstreamInclude.h ├── Input.cpp ├── Input.h ├── Output.cpp ├── Output.h ├── generated.cpp └── main.cpp └── test ├── AnalysisCircularDependencies.cpp ├── CMakeLists.txt ├── CmakeRegenTest.cpp ├── ConfigurationTest.cpp ├── InputTest.cpp ├── TestUtils.h ├── test.cpp └── test.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Compiled Dynamic libraries 12 | *.so 13 | *.dylib 14 | *.dll 15 | 16 | # Fortran module files 17 | *.mod 18 | *.smod 19 | 20 | # Compiled Static libraries 21 | *.lai 22 | *.la 23 | *.a 24 | *.lib 25 | 26 | # Executables 27 | *.exe 28 | *.out 29 | *.app 30 | *.pdb 31 | *.log 32 | *.pdb 33 | *.idb 34 | *.db 35 | *.suo 36 | 37 | # MSVC specific temporary files 38 | *.sdf 39 | *.opensdf 40 | 41 | # Local build outputs 42 | obj 43 | cpp-dependencies 44 | cpp-dependencies-unittests 45 | build 46 | test/unittests 47 | Testing 48 | 49 | MSVC/x64/Debug/ 50 | MSVC/*.opendb 51 | build 52 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | script: 2 | - mkdir build 3 | - cd build 4 | - cmake ${CMAKEFLAGS} -DCMAKE_CXX_COMPILER=${CXX} .. 5 | - make 6 | - make CTEST_OUTPUT_ON_FAILURE=1 test 7 | - make package 8 | 9 | sudo: required 10 | dist: trusty 11 | 12 | matrix: 13 | include: 14 | - compiler: gcc 15 | os: osx 16 | before_script: 17 | - brew update 18 | - brew outdated cmake || brew upgrade cmake 19 | env: CMAKEFLAGS="-DWITH_BOOST=ON -DHAS_MEMRCHR=OFF" CXX=g++ 20 | - compiler: clang 21 | os: osx 22 | before_script: 23 | - brew update 24 | - brew outdated cmake || brew upgrade cmake 25 | env: CMAKEFLAGS="-DWITH_BOOST=ON -DHAS_MEMRCHR=OFF" CXX=clang++ 26 | - compiler: gcc 27 | os: linux 28 | dist: precise 29 | sudo: false 30 | addons: 31 | apt: 32 | sources: 33 | - george-edison55-precise-backports 34 | packages: 35 | - cmake 36 | - cmake-data 37 | - libboost-system-dev 38 | - libboost-filesystem-dev 39 | env: CMAKEFLAGS="-DWITH_BOOST=ON" CXX=g++-4.6 40 | - compiler: gcc 41 | os: linux 42 | addons: 43 | apt: 44 | sources: 45 | - ubuntu-toolchain-r-test 46 | packages: 47 | - g++-4.9 48 | - cmake 49 | - libboost-system-dev 50 | - libboost-filesystem-dev 51 | env: CMAKEFLAGS="-DWITH_BOOST=ON" CXX=g++-4.9 52 | - compiler: gcc 53 | os: linux 54 | addons: 55 | apt: 56 | sources: 57 | - ubuntu-toolchain-r-test 58 | packages: 59 | - g++-5 60 | - cmake 61 | env: CMAKEFLAGS="-DWITH_BOOST=OFF" CXX=g++-5 62 | - compiler: clang 63 | os: linux 64 | addons: 65 | apt: 66 | sources: 67 | - ubuntu-toolchain-r-test 68 | - llvm-toolchain-precise-3.6 69 | packages: 70 | - clang-3.6 71 | - cmake 72 | - libboost-system-dev 73 | - libboost-filesystem-dev 74 | env: CMAKEFLAGS="-DWITH_BOOST=ON" CXX=clang++-3.6 75 | - compiler: clang 76 | os: linux 77 | addons: 78 | apt: 79 | sources: 80 | - ubuntu-toolchain-r-test 81 | - llvm-toolchain-precise-3.7 82 | packages: 83 | - clang-3.7 84 | - cmake 85 | - libboost-system-dev 86 | - libboost-filesystem-dev 87 | env: CMAKEFLAGS="-DWITH_BOOST=ON" CXX=clang++-3.7 88 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1) 2 | 3 | project(cpp_dependencies LANGUAGES CXX) 4 | 5 | include(CTest) 6 | 7 | # Coverage build doesn't work with MSVC 8 | option(BUILD_COVERAGE "Build cpp_dependencies for coverage" OFF) 9 | 10 | if (WIN32) 11 | set(DEFAULT_BOOST OFF) 12 | else() 13 | set(DEFAULT_BOOST ON) 14 | endif() 15 | 16 | # Running with Boost filesystem is typically faster, until platform specific std::filesystem comes out that is faster yet. 17 | # Note that Boost::filesystem needs to be installed for this to be used. 18 | option(WITH_BOOST "Use Boost filesystem" ${DEFAULT_BOOST}) 19 | 20 | if (WIN32) 21 | set(DEFAULT_MMAP OFF) 22 | else() 23 | set(DEFAULT_MMAP ON) 24 | endif() 25 | 26 | # Switch between using the mmap logic for reading files (faster, because one copy less) or a file read (slower, because a full copy, but portable). 27 | option(WITH_MMAP "Use mmapped files" ${DEFAULT_MMAP}) 28 | 29 | if (WIN32 OR APPLE) 30 | set(DEFAULT_MEMRCHR OFF) 31 | else() 32 | set(DEFAULT_MEMRCHR ON) 33 | endif() 34 | 35 | # Whether your platform provides a fast memrchr function. If it does not, turn this off and a slower replacement will be used. 36 | option(HAS_MEMRCHR "Platform has memrchr function" ${DEFAULT_MEMRCHR}) 37 | 38 | # Default package format to use when building a package with CPack 39 | if(APPLE) 40 | set(DEFAULT_CPACK_GENERATOR "DragNDrop") 41 | elseif(UNIX) 42 | set(DEFAULT_CPACK_GENERATOR "DEB") 43 | elseif(WIN32) 44 | set(DEFAULT_CPACK_GENERATOR "NSIS") 45 | endif() 46 | set(CPACK_GENERATOR "${DEFAULT_CPACK_GENERATOR}" CACHE STRING "Package type to generate with CPack") 47 | 48 | set(CMAKE_CXX_STANDARD 11) 49 | set(CMAKE_CXX_STANDARD_REQUIRED TRUE) 50 | 51 | if("${CMAKE_CXX_COMPILER_ID}" MATCHES GNU) 52 | include(CheckCXXCompilerFlag) 53 | 54 | set(COMPILE_FLAGS -Wall -Wextra) 55 | 56 | check_cxx_compiler_flag("-Wpedantic" PEDANTIC_SUPPORTED) 57 | if(PEDANTIC_SUPPORTED) 58 | list(APPEND COMPILE_FLAGS -Wpedantic) 59 | endif() 60 | elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") 61 | set(COMPILE_FLAGS /W4) 62 | # boost gets compiled as static libs on Windows 63 | set(Boost_USE_STATIC_LIBS ON) 64 | endif() 65 | 66 | if(WITH_MMAP) 67 | list(APPEND COMPILE_FLAGS -DWITH_MMAP) 68 | endif() 69 | 70 | if(NOT HAS_MEMRCHR) 71 | list(APPEND COMPILE_FLAGS -DNO_MEMRCHR) 72 | endif() 73 | 74 | if(WITH_BOOST) 75 | list(APPEND COMPILE_FLAGS -DWITH_BOOST) 76 | find_package(Boost COMPONENTS filesystem system REQUIRED) 77 | set(FILESYSTEM_LIBS ${Boost_LIBRARIES}) 78 | else() 79 | if(NOT WIN32) 80 | set(FILESYSTEM_LIBS stdc++fs) 81 | endif() 82 | endif() 83 | 84 | add_subdirectory(src) 85 | 86 | if(BUILD_TESTING) 87 | add_subdirectory(test) 88 | endif() 89 | 90 | set(CPACK_PACKAGE_NAME cpp-dependencies) 91 | set(CPACK_PACKAGE_VENDOR "TomTom International BV") 92 | set(CPACK_PACKAGE_CONTACT "${CPACK_PACKAGE_VENDOR}") 93 | set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) 94 | 95 | set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Tool to check C++ #include dependencies 96 | The tool cpp-dependencies creates #include dependency information for C++ 97 | source code files, which it derives from scanning a full source tree. 98 | . 99 | The dependency information is output as .dot files, which can be visualized 100 | in, for example, GraphViz.") 101 | set(CPACK_DEBIAN_PACKAGE_HOMEPAGE https://github.com/tomtom-international/cpp-dependencies) 102 | set(CPACK_DEBIAN_PACKAGE_SUGGESTS graphviz) 103 | set(CPACK_DEBIAN_PACKAGE_SECTION devel) 104 | 105 | # Version: update this before or while tagging a new release, for out of repo builds 106 | set(CPACK_PACKAGE_VERSION_MAJOR 1) 107 | set(CPACK_PACKAGE_VERSION_MINOR 1) 108 | set(CPACK_PACKAGE_VERSION_PATCH 0) 109 | 110 | # Automatically detect package version to use from git 111 | find_package(Git) 112 | if(Git_FOUND OR GIT_FOUND) 113 | execute_process( 114 | COMMAND ${GIT_EXECUTABLE} describe --tags --long --dirty --always 115 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} 116 | RESULT_VARIABLE DESCRIBE_RESULT 117 | OUTPUT_VARIABLE DESCRIBE_STDOUT 118 | ) 119 | if(DESCRIBE_RESULT EQUAL 0) 120 | string(STRIP "${DESCRIBE_STDOUT}" DESCRIBE_STDOUT) 121 | message(STATUS "Git reported this project's version as '${DESCRIBE_STDOUT}'") 122 | if(DESCRIBE_STDOUT MATCHES "^(.*)-(dirty)$") 123 | set(DESCRIBE_DIRTY "${CMAKE_MATCH_2}") 124 | set(DESCRIBE_STDOUT "${CMAKE_MATCH_1}") 125 | endif() 126 | if(DESCRIBE_STDOUT MATCHES "^([0-9a-f]+)$") 127 | set(DESCRIBE_COMMIT_NAME "${CMAKE_MATCH_1}") 128 | set(DESCRIBE_STDOUT "") 129 | elseif(DESCRIBE_STDOUT MATCHES "^(.*)-g([0-9a-f]+)$") 130 | set(DESCRIBE_COMMIT_NAME "${CMAKE_MATCH_2}") 131 | set(DESCRIBE_STDOUT "${CMAKE_MATCH_1}") 132 | endif() 133 | if(DESCRIBE_STDOUT MATCHES "^(.*)-([0-9]+)$") 134 | set(DESCRIBE_COMMIT_COUNT "${CMAKE_MATCH_2}") 135 | set(DESCRIBE_TAG "${CMAKE_MATCH_1}") 136 | set(DESCRIBE_STDOUT "") 137 | endif() 138 | if("${DESCRIBE_TAG}.0.0" MATCHES "^([0-9]+)\\.([0-9]+)\\.([0-9]+).*$") 139 | set(CPACK_PACKAGE_VERSION_MAJOR "${CMAKE_MATCH_1}") 140 | set(CPACK_PACKAGE_VERSION_MINOR "${CMAKE_MATCH_2}") 141 | set(CPACK_PACKAGE_VERSION_PATCH "${CMAKE_MATCH_3}") 142 | endif() 143 | if(DESCRIBE_COMMIT_COUNT GREATER 0) 144 | # Make it a pre-release version of the next patch release 145 | math(EXPR CPACK_PACKAGE_VERSION_PATCH "${CPACK_PACKAGE_VERSION_PATCH} + 1") 146 | endif() 147 | 148 | set(CPACK_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}") 149 | set(CPACK_DEBIAN_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION}") 150 | 151 | # Now for the rest: format global CPack version according to semver.org, Debian so that it'll get proper sorting for dpkg 152 | if(DESCRIBE_COMMIT_COUNT GREATER 0) 153 | set(CPACK_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION}-${DESCRIBE_COMMIT_COUNT}") 154 | set(CPACK_DEBIAN_PACKAGE_VERSION "${CPACK_DEBIAN_PACKAGE_VERSION}~${DESCRIBE_COMMIT_COUNT}") 155 | endif() 156 | 157 | set(CPACK_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION}+g${DESCRIBE_COMMIT_NAME}") 158 | set(CPACK_DEBIAN_PACKAGE_VERSION "${CPACK_DEBIAN_PACKAGE_VERSION}+g${DESCRIBE_COMMIT_NAME}") 159 | 160 | if(DESCRIBE_DIRTY) 161 | string(TIMESTAMP DESCRIBE_DIRTY_TIMESTAMP "%Y%m%d%H%M%S" UTC) 162 | set(CPACK_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION}.dirty.${DESCRIBE_DIRTY_TIMESTAMP}") 163 | set(CPACK_DEBIAN_PACKAGE_VERSION "${CPACK_DEBIAN_PACKAGE_VERSION}+dirty${DESCRIBE_DIRTY_TIMESTAMP}") 164 | endif() 165 | else() 166 | message(WARNING "Git failed to report the version") 167 | endif() 168 | endif() 169 | 170 | if(CPACK_GENERATOR STREQUAL DEB) 171 | find_program(DPKG_CMD dpkg REQUIRED) 172 | if(NOT DPKG_CMD) 173 | message(STATUS "Can not find dpkg in your path, default to i386.") 174 | set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE i386) 175 | else() 176 | execute_process(COMMAND "${DPKG_CMD}" --print-architecture 177 | OUTPUT_VARIABLE CPACK_DEBIAN_PACKAGE_ARCHITECTURE 178 | OUTPUT_STRIP_TRAILING_WHITESPACE 179 | ) 180 | endif() 181 | 182 | if(NOT DEFINED CPACK_DEBIAN_PACKAGE_VERSION) 183 | set(CPACK_DEBIAN_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}") 184 | endif() 185 | 186 | # Because the default package name produced by CPack fails to meet Debian packaging conventions 187 | set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}_${CPACK_DEBIAN_PACKAGE_VERSION}_${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}") 188 | endif() 189 | 190 | include(CPack) 191 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | Copyright 2018, TomTom International BV 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------------------- 2 | TOMTOM DEPENDENCYCHECKER TOOL 3 | ------------------------------------------------------------------------------- 4 | 5 | DependencyChecker is a tool for analyzing and understanding large C++ code 6 | bases, originally developed by Maikel van den Hurk, Peter Bindels and many 7 | others at TomTom. 8 | 9 | Copyright (C) 2014-2015 TomTom International BV (http://www.tomtom.com) 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Read Me for Dependency Checker 2 | 3 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/69bd30706d964c308f78466aa138f64e)](https://www.codacy.com/app/rijnb/cpp-dependencies?utm_source=github.com&utm_medium=referral&utm_content=tomtom-international/cpp-dependencies&utm_campaign=Badge_Grade) 4 | [![Mac/Linux Build Status](https://img.shields.io/travis/tomtom-international/cpp-dependencies.svg?maxAge=3600)](https://travis-ci.org/tomtom-international/cpp-dependencies) 5 | [![Windows Build Status](https://img.shields.io/appveyor/ci/rijnb/cpp-dependencies.svg)](https://ci.appveyor.com/project/rijnb/cpp-dependencies) 6 | [![License](http://img.shields.io/badge/license-APACHE2-blue.svg)]() 7 | [![Release](https://img.shields.io/github/release/tomtom-international/cpp-dependencies.svg?maxAge=3600)](https://github.com/tomtom-international/cpp-dependencies/releases) 8 | 9 | [![Snap store badge](https://camo.githubusercontent.com/353bcf397acd2a7663c45bc69cd2b202417a66c24d3b38f861f9cc0fe1a25324/68747470733a2f2f736e617063726166742e696f2f7374617469632f696d616765732f6261646765732f656e2f736e61702d73746f72652d77686974652e737667)](https://snapcraft.io/cpp-dependencies) 10 | 11 | Copyright (C) 2012-2017, TomTom International BV. All rights reserved. 12 | ---- 13 | 14 | The tool `cpp-dependencies` creates `#include` dependency information for C++ source 15 | code files, which it derives from scanning a full source tree. 16 | 17 | The dependency information is output as `.dot` files, which can be visualized 18 | in, for example, [GraphViz](http://www.graphviz.org). 19 | 20 | Happy coding! 21 | 22 | Peter Bindels and Rijn Buve 23 | 24 | *TomTom International BV* 25 | 26 | # License 27 | 28 | Licensed under the Apache License, Version 2.0 (the "License"); 29 | you may not use this file except in compliance with the License. 30 | You may obtain a copy of the License at 31 | 32 | http://www.apache.org/licenses/LICENSE-2.0 33 | 34 | Unless required by applicable law or agreed to in writing, software 35 | distributed under the License is distributed on an "AS IS" BASIS, 36 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 37 | See the License for the specific language governing permissions and 38 | limitations under the License. 39 | 40 | # Build and run 41 | 42 | The tool depends on Boost.Filesystem being available and usable. Installing this 43 | should be done with your platform's package management system, such as Apt, 44 | Pacman or Brew. 45 | 46 | The build configuration is created with _CMake_. 47 | To create the build configuration for your build system (GNU make, MSBuild/Visual Studio) 48 | create a build directory outside this source directory and run 49 | 50 | cmake 51 | 52 | If you want to use Boost::Filesystem instead of std::filesystem, if your platform does 53 | not have a std::filesystem implementation yet or if you prefer it, add `-DWITH_BOOST` 54 | to the invocation of _CMake_. 55 | 56 | To build the tool, either execute 57 | 58 | make 59 | 60 | for GNU make or open the Visual Studio solution file generated in the build directory. 61 | 62 | This creates the executable file `cpp-dependencies`. 63 | 64 | To check if the tool was compiled correctly, execute: 65 | 66 | ./cpp-dependencies 67 | 68 | This provides help information about the tool. More information about 69 | its usage is presented in the next paragraph. 70 | 71 | 72 | # Using `cpp-dependencies` to analyze a component 73 | 74 | As a first thing on a code base is to find out whether it can read the code correctly. From the root of the project, 75 | run the command: 76 | 77 | cpp-dependencies --stats . 78 | 79 | to determine the complexity of the code base and the amount of nodes that are entangled in cycles with other 80 | components. In well set-up projects, the cycle count will be equal to zero and the amount of components will be in 81 | the same order of size as the logical components you expect. 82 | 83 | To investigate a specific component, you can use 84 | 85 | cpp-dependencies --info . 86 | 87 | for all information the tool has on the component, or: 88 | 89 | cpp-dependencies --inout . 90 | 91 | to find out who links to and from your component. 92 | 93 | In case you have a dependency that you were not expecting, or find out that when rebuilding component A that a 94 | supposedly-unrelated component B is built, you can use: 95 | 96 | cpp-dependencies --shortest A B . 97 | 98 | to determine why there is a link from component A to component B. It will find one of the shortest paths it can find 99 | from A to B if there is one. 100 | 101 | 102 | # Using `cpp-dependencies` to make visualized graphs 103 | 104 | The tool is also able to provide output in `.dot` format, which is a format used by [GraphViz](http://www.graphviz.org) and other tools to contain 105 | graphs. It is a human-readable format and can be used by the dot tool to convert it into a graphical image. To create 106 | a graph file, use: 107 | 108 | cpp-dependencies --graph mygraph.dot . 109 | 110 | to create a mygraph.dot file containing the full component graph. 111 | 112 | You can restrict the component graph to either all 113 | components beneath a given target (`--graph-for `) or all components part of a cycle (`--graph-cycles`). 114 | 115 | To make this text-format graph into a viewable graph, use for example: 116 | 117 | dot -Tpng mygraph.dot >mygraph.png 118 | 119 | to convert it into a PNG file. 120 | 121 | The `dot` program will try to find a way to graphically display the graph output. Note that 122 | very large graphs, in particular if many cycles are present, can take hours to render. 123 | 124 | 125 | # Example use of `cpp-dependencies` 126 | 127 | In the source tree there's a folder `example` which contains an empty skeleton project, which does have some dependency information to be extracted from it. To start analyzing it, we first run the tool to extract statistics: 128 | 129 | > cpp-dependencies --dir example --stats 130 | 6 components with 5 public dependencies, 1 private dependencies 131 | Detected 2 nodes in cycles 132 | 133 | This informs us that there is something not quite right with the dependencies. It sees 6 components: the root folder, four libraries and an executable. The simplest way to find out what's wrong is to draw out the graph in a visual way: 134 | 135 | > cpp-dependencies --dir example --graph dependencies.dot 136 | > dot -Tpng dependencies.dot >dependencies.png 137 | 138 | Then open this PNG file in any tool that can view it, such as a web browser. This shows us the following image: 139 | 140 | ![Dependency graph showing a cycle between Engine and UI](example/dependencies.png) 141 | 142 | The light blue links are an implementation-only link, the dark blue ones expose some part of this dependency on their interface. The orange ones are the most interesting ones; they are places where a component can reach itself through some other component. Let's find out why this is there: 143 | 144 | > cpp-dependencies --dir example --shortest Engine UI 145 | Engine -> UI 146 | ./Engine/Engine.h includes ./UI/Display.h 147 | > cpp-dependencies --dir example --shortest UI Engine 148 | UI -> Engine 149 | ./UI/Display.cpp includes ./Engine/Engine.h 150 | ./UI/Display.h includes ./Engine/Engine.h 151 | 152 | At this point, it's up to the developer or architect to find out which of these two dependencies is the wrong way around and to find a way around that. In the example, the Engine component should not be talking directly to the UI component. Removing this dependency results in the following statistics: 153 | 154 | > cpp-dependencies --dir example --stats 155 | 6 components with 4 public dependencies, 2 private dependencies 156 | Detected 0 nodes in cycles 157 | 158 | The cycle has been removed, and there is one less dependency. We can find out what the shortest path is to the DataAccess component from the executable: 159 | 160 | > cpp-dependencies --dir example --shortest main DataAccess 161 | main -> UI 162 | ./main/main.cpp includes ./UI/Display.h 163 | UI -> Engine 164 | ./UI/Display.cpp includes ./Engine/Engine.h 165 | ./UI/Display.h includes ./Engine/Engine.h 166 | Engine -> DataAccess 167 | ./Engine/Engine.cpp includes ./DataAccess/DA.h 168 | 169 | This tells us that there's no path shorter than three steps, and it informs us for each step of the way why it detects this link. In more complicated cycles, this can be a way to isolate the thinnest part of the cycle. In situations where there's an invalid dependency from one component to another - for example, from a unit test of one component to a very far away different component, this can help you identify where on the path from A to B a wrong link is present. It can also be used to explicitly verify that a link is not present, such as the one we just removed: 170 | 171 | > cpp-dependencies --dir example --shortest Engine UI 172 | No path could be found from Engine to UI 173 | 174 | The graph now also shows proper dependency ordering: 175 | 176 | > cpp-dependencies --dir example --graph newdependencies.dot 177 | > dot -Tpng newdependencies.dot >newdependencies.png 178 | 179 | ![Dependency graph showing no more cycles](example/newdependencies.png) 180 | 181 | We can regenerate the CMakeLists.txt files as well to remove the dependency from the build inputs, so that our build system will also know that the link is no longer present: 182 | 183 | > cpp-dependencies --dir example --dryregen 184 | Difference detected at "./Engine" 185 | > cpp-dependencies --dir example --regen 186 | 187 | # Customizing the outputs 188 | 189 | As cpp-dependencies has a lot of analysis it can do on the source tree, there are also some configurable parts to it. The configuration can be found in the file config-cpp-dependencies.txt that should be in your project root. It allows you to customize the colors used in generation, the thresholds for outlier detection and some minor parameters. Please read the documentation in the example config-cpp-dependencies.txt that is in the source distribution for the tool to see all the options. 190 | 191 | # Editing the tool 192 | 193 | The tool itself is split up into a few separate files to make it easier to find and extend its functionality. The following files are found: 194 | 195 | * `main.cpp` contains the main functions and help information, as well as the core flow. 196 | * `Input.cpp` contains the functions that read C++ and `CMakeLists` files into the information needed by the tool. 197 | * `Output.cpp` contains functions to write all output files generated, except for the `CMakeLists` generation. 198 | * `CmakeRegen.cpp` contains the functionality to write `CMakeLists` files. 199 | * `Analysis.cpp` contains all graph processing and navigation functions. 200 | * `Component.cpp` contains the implementation needed for the struct-like data storage classes. 201 | * `generated.cpp` contains the function to convert found header files into a lookup map. Also the place to add generated files 202 | to the known file list, so that they will be taken into account for components. 203 | * `Constants.h` contains the constants used throughout the code base. 204 | 205 | In general, the root functionality is kept in `main.cpp`, the structural classes are kept in `Component.cpp` 206 | and any auxiliary functions that are used to do this are split up by domain. 207 | 208 | 209 | # Rationale behind implementation 210 | 211 | The tool was implemented with the goal of being able to quickly analyze dependencies between components of a 212 | complex project, including how the dependency graph changes when some changes are made to the source tree. To 213 | accomplish this, choices were made in the direction of more performance at the expense of strict correctness. 214 | Specifically: 215 | 216 | - It does not use a proper C++ parser to read C++ files, nor a proper CMake parser to read CMake files. Properly 217 | parsing these files would increase the full run time of the program by orders of magnitude and make it much less 218 | useful. 219 | - `strstr` is used across the full code base. While profiling, we found that `std::string::find` was taking over 80% of 220 | the full runtime. Replacing it with `strstr`, which is typically much more optimized, made the whole program twice 221 | as fast. 222 | 223 | This results in it running on a 1.5GB source code base in about 2.1 seconds -- fast enough for interactive checks and 224 | rerunning after any small modification. 225 | 226 | The tool was set up to compile on a Ubuntu 12.04 system with the platform default compiler. This means that the sources will use C++11 227 | but will not use anything not available in GCC 4.6. It has been tested and used on Linux (Ubuntu 12.04 - 16.04) and MacOS X 228 | (different versions). 229 | 230 | 231 | # Using Git and `.gitignore` 232 | 233 | It's good practice to set up a personal global `.gitignore` file on your machine which filters a number of files 234 | on your file systems that you do not wish to submit to the Git repository. You can set up your own global 235 | `~/.gitignore` file by executing: 236 | `git config --global core.excludesfile ~/.gitignore` 237 | 238 | In general, add the following file types to `~/.gitignore` (each entry should be on a separate line): 239 | `*.com *.class *.dll *.exe *.o *.so *.log *.sql *.sqlite *.tlog *.epoch *.swp *.hprof *.hprof.index *.releaseBackup *~` 240 | 241 | The local `.gitignore` file in the Git repository itself to reflect those file only that are produced by executing 242 | regular compile, build or release commands. 243 | 244 | 245 | # Bug reports and new feature requests 246 | 247 | If you encounter any problems with this library, don't hesitate to use the `Issues` session to file your issues. 248 | Normally, one of our developers should be able to comment on them and fix. 249 | 250 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 1.0.{build} 2 | 3 | configuration: Release 4 | 5 | before_build: 6 | - cmake -H. -Bbuild 7 | 8 | build: 9 | parallel: true 10 | project: build\PACKAGE.vcxproj 11 | verbosity: minimal 12 | 13 | test_script: 14 | - cd build 15 | - ctest -C %configuration% --output-on-failure 16 | 17 | artifacts: 18 | - path: build\cpp-dependencies*.exe 19 | -------------------------------------------------------------------------------- /config-cpp-dependencies.txt: -------------------------------------------------------------------------------- 1 | # Configuration file for cpp-dependencies 2 | # This is the example file that has all the default values listed. 3 | 4 | # Version that was current when this configuration file was written 5 | # Used to prevent a newer version with possibly incompatible generation from overwriting 6 | # cmakefiles automatically. 7 | versionUsed: 2 8 | 9 | # Company name to use in generated CMakeLists' copyright statement. 10 | companyName: YourCompany 11 | 12 | # License text to include directly after the copyright statement. 13 | licenseString: """ 14 | Licensed under the Apache License, Version 2.0 (the "License"); 15 | you may not use this file except in compliance with the License. 16 | You may obtain a copy of the License at 17 | 18 | http://www.apache.org/licenses/LICENSE-2.0 19 | 20 | Unless required by applicable law or agreed to in writing, software 21 | distributed under the License is distributed on an "AS IS" BASIS, 22 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 23 | See the License for the specific language governing permissions and 24 | limitations under the License. 25 | """ 26 | 27 | # Tag used in generated CMakeLists. Don't change unless you also update your CMakeLists. 28 | regenTag: GENERATED BY CPP-DEPENDENCIES 29 | 30 | 31 | # The next three items describe colors that will appear in the output graphs. Ensure you 32 | # use colors that the tools you will be using on the graphs understand. 33 | 34 | # Color used for cycles in the generated graphs 35 | cycleColor: orange 36 | 37 | # Color used for public dependencies in the generated graphs 38 | publicDepColor: blue 39 | 40 | # Color used for private dependencies in the generated graphs 41 | privateDepColor: lightblue 42 | 43 | 44 | # Upper bound for the amount of outgoing component links from a single component. 45 | componentLinkLimit: 30 46 | 47 | # Lower bound for amount of code in a single component. Used to flag "empty" components. 48 | componentLocLowerLimit: 200 49 | 50 | # Upper bound for amount of code in a single component. Used to flag oversized components, 51 | # which typically harbor god class like behaviour, and hold multiple responsibilities. 52 | componentLocUpperLimit: 20000 53 | 54 | # Upper bound for file size. Large files are hard to understand and often contain multiple 55 | # logical units, which are then easy to mix up and conflate. 56 | fileLocUpperLimit: 2000 57 | 58 | # Whether custom sections, like "set_target_property(...)", from an existing CMakeLists.txt 59 | # file should be reused. 60 | reuseCustomSections: false 61 | 62 | # Aliases for CMake command add_library. Each alias is assumed to take similar arguments as the 63 | # vanilla CMake command. 64 | # Each alias must be on its own line. The last line should only contain the closing bracket. 65 | #addLibraryAlias: [ 66 | # add_my_library 67 | # add_my_special_library 68 | # ] 69 | 70 | # Aliases for CMake command add_executable. Each alias is assumed to take similar arguments as the 71 | # vanilla CMake command. 72 | # Each alias must be on its own line. The last line should only contain the closing bracket. 73 | #addExecutableAlias: [ 74 | # add_my_executable 75 | # add_my_special_exe 76 | # ] 77 | # 78 | #addIgnores: [ 79 | # Example/ThirdParty 80 | # Example/test.txt 81 | # ] 82 | 83 | # List of folder paths (from the root) that should be completely ignored. May contain multiple 84 | # space-separated values, including values with spaces escaped with quotation marks. 85 | blacklist: [ 86 | build 87 | Build 88 | Visual Studio Projects 89 | unistd.h 90 | console.h 91 | stdint.h 92 | windows.h 93 | library.h 94 | endian.h 95 | rle.h 96 | ] 97 | 98 | 99 | -------------------------------------------------------------------------------- /example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) MyExampleCompany. All rights reserved. 3 | # 4 | # GENERATED BY CPP-DEPENDENCIES - do not edit, your changes will be lost 5 | # If you must edit, remove these two lines to avoid regeneration 6 | 7 | 8 | add_subdirectory(DataAccess) 9 | add_subdirectory(Engine) 10 | add_subdirectory(Framework) 11 | add_subdirectory(System) 12 | add_subdirectory(UI) 13 | add_subdirectory(main) 14 | -------------------------------------------------------------------------------- /example/DataAccess/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) MyExampleCompany. All rights reserved. 3 | # 4 | # GENERATED BY CPP-DEPENDENCIES - do not edit, your changes will be lost 5 | # If you must edit, remove these two lines to avoid regeneration 6 | 7 | project(DataAccess) 8 | 9 | 10 | add_library(${PROJECT_NAME} INTERFACE 11 | ) 12 | 13 | target_link_libraries(${PROJECT_NAME} 14 | INTERFACE 15 | Framework 16 | ) 17 | 18 | -------------------------------------------------------------------------------- /example/DataAccess/DA.h: -------------------------------------------------------------------------------- 1 | #include "framework.h" 2 | -------------------------------------------------------------------------------- /example/Engine/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) MyExampleCompany. All rights reserved. 3 | # 4 | # GENERATED BY CPP-DEPENDENCIES - do not edit, your changes will be lost 5 | # If you must edit, remove these two lines to avoid regeneration 6 | 7 | project(Engine) 8 | 9 | 10 | add_library(${PROJECT_NAME} STATIC 11 | Engine.cpp 12 | Engine.h 13 | OldEngine.h 14 | ) 15 | 16 | target_link_libraries(${PROJECT_NAME} 17 | PUBLIC 18 | Framework 19 | UI 20 | PRIVATE 21 | DataAccess 22 | ) 23 | 24 | target_include_directories(${PROJECT_NAME} 25 | PUBLIC 26 | . 27 | ) 28 | 29 | -------------------------------------------------------------------------------- /example/Engine/Engine.cpp: -------------------------------------------------------------------------------- 1 | #include "DataAccess/DA.h" 2 | #include "Engine.h" 3 | 4 | -------------------------------------------------------------------------------- /example/Engine/Engine.h: -------------------------------------------------------------------------------- 1 | #include "Framework/framework.h" 2 | #include "UI/Display.h" 3 | 4 | -------------------------------------------------------------------------------- /example/Engine/OldEngine.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dascandy/cpp-dependencies/3036c3a52f34bb15ae3243660f4c53c24a94cbd9/example/Engine/OldEngine.h -------------------------------------------------------------------------------- /example/Framework/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) MyExampleCompany. All rights reserved. 3 | # 4 | # GENERATED BY CPP-DEPENDENCIES - do not edit, your changes will be lost 5 | # If you must edit, remove these two lines to avoid regeneration 6 | 7 | project(Framework) 8 | 9 | 10 | add_library(${PROJECT_NAME} INTERFACE 11 | ) 12 | 13 | target_include_directories(${PROJECT_NAME} 14 | INTERFACE 15 | . 16 | ) 17 | 18 | -------------------------------------------------------------------------------- /example/Framework/framework.h: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | -------------------------------------------------------------------------------- /example/System/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) MyExampleCompany. All rights reserved. 3 | # 4 | # GENERATED BY CPP-DEPENDENCIES - do not edit, your changes will be lost 5 | # If you must edit, remove these two lines to avoid regeneration 6 | 7 | project(System) 8 | 9 | 10 | add_library(${PROJECT_NAME} STATIC 11 | System.cpp 12 | System.h 13 | ) 14 | 15 | -------------------------------------------------------------------------------- /example/System/Engine.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dascandy/cpp-dependencies/3036c3a52f34bb15ae3243660f4c53c24a94cbd9/example/System/Engine.h -------------------------------------------------------------------------------- /example/System/System.cpp: -------------------------------------------------------------------------------- 1 | #include "System.h" 2 | 3 | 4 | -------------------------------------------------------------------------------- /example/System/System.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dascandy/cpp-dependencies/3036c3a52f34bb15ae3243660f4c53c24a94cbd9/example/System/System.h -------------------------------------------------------------------------------- /example/UI/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) MyExampleCompany. All rights reserved. 3 | # 4 | # GENERATED BY CPP-DEPENDENCIES - do not edit, your changes will be lost 5 | # If you must edit, remove these two lines to avoid regeneration 6 | 7 | project(UI) 8 | 9 | 10 | add_library(${PROJECT_NAME} STATIC 11 | Display.cpp 12 | Display.h 13 | ) 14 | 15 | target_link_libraries(${PROJECT_NAME} 16 | PUBLIC 17 | Engine 18 | Framework 19 | ) 20 | 21 | target_include_directories(${PROJECT_NAME} 22 | PUBLIC 23 | . 24 | ) 25 | 26 | -------------------------------------------------------------------------------- /example/UI/Display.cpp: -------------------------------------------------------------------------------- 1 | #include "Engine.h" 2 | -------------------------------------------------------------------------------- /example/UI/Display.h: -------------------------------------------------------------------------------- 1 | #include "framework.h" 2 | #include "Engine/Engine.h" 3 | -------------------------------------------------------------------------------- /example/config-cpp-dependencies.txt: -------------------------------------------------------------------------------- 1 | # Configuration file for cpp-dependencies 2 | 3 | # Version that was current when this configuration file was written 4 | # Used to prevent a newer version with possibly incompatible generation from overwriting 5 | # cmakefiles automatically. 6 | versionUsed: 2 7 | 8 | # Company name to use in copyright headers. 9 | companyName: MyExampleCompany 10 | 11 | # Tag used in generated CMakeLists. Don't change unless you also update your CMakeLists. 12 | regenTag: GENERATED BY CPP-DEPENDENCIES 13 | 14 | # Color used for cycles in the generated graphs 15 | cycleColor: orange 16 | 17 | # Color used for public dependencies in the generated graphs 18 | publicDepColor: blue 19 | 20 | # Color used for private dependencies in the generated graphs 21 | privateDepColor: lightblue 22 | 23 | # Upper bound for the amount of outgoing component links from a single component. 24 | componentLinkLimit: 30 25 | 26 | # Lower bound for amount of code in a single component. Used to flag "empty" components. 27 | # Disabled, as this example has no code. 28 | componentLocLowerLimit: 0 29 | 30 | # Upper bound for amount of code in a single component. Used to flag oversized components, 31 | # which typically harbor god class like behaviour, and hold multiple responsibilities. 32 | componentLocUpperLimit: 20000 33 | 34 | # Upper bound for file size. Large files are hard to understand and often contain multiple 35 | # logical units, which are then easy to mix up and conflate. 36 | fileLocUpperLimit: 2000 37 | 38 | -------------------------------------------------------------------------------- /example/dependencies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dascandy/cpp-dependencies/3036c3a52f34bb15ae3243660f4c53c24a94cbd9/example/dependencies.png -------------------------------------------------------------------------------- /example/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) MyExampleCompany. All rights reserved. 3 | # 4 | # GENERATED BY CPP-DEPENDENCIES - do not edit, your changes will be lost 5 | # If you must edit, remove these two lines to avoid regeneration 6 | 7 | project(main) 8 | 9 | 10 | add_executable(${PROJECT_NAME} 11 | main.cpp 12 | ) 13 | 14 | target_link_libraries(${PROJECT_NAME} 15 | PRIVATE 16 | UI 17 | ) 18 | 19 | -------------------------------------------------------------------------------- /example/main/main.cpp: -------------------------------------------------------------------------------- 1 | #include "Display.h" 2 | 3 | int main() 4 | { 5 | } 6 | -------------------------------------------------------------------------------- /example/newdependencies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dascandy/cpp-dependencies/3036c3a52f34bb15ae3243660f4c53c24a94cbd9/example/newdependencies.png -------------------------------------------------------------------------------- /src/Analysis.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012-2016. TomTom International BV (http://tomtom.com). 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "Analysis.h" 18 | 19 | static void StrongConnect(std::vector &stack, size_t& index, Component* c) { 20 | c->index = c->lowlink = index++; 21 | stack.push_back(c); 22 | c->onStack = true; 23 | for (auto& c2 : c->pubDeps) { 24 | if (c2->index == 0) { 25 | StrongConnect(stack, index, c2); 26 | c->lowlink = std::min(c->lowlink, c2->lowlink); 27 | } else if (c2->onStack) { 28 | c->lowlink = std::min(c->lowlink, c2->index); 29 | } 30 | } 31 | for (auto& c2 : c->privDeps) { 32 | if (c2->index == 0) { 33 | StrongConnect(stack, index, c2); 34 | c->lowlink = std::min(c->lowlink, c2->lowlink); 35 | } else if (c2->onStack) { 36 | c->lowlink = std::min(c->lowlink, c2->index); 37 | } 38 | } 39 | if (c->lowlink == c->index) { 40 | auto pos = std::find(stack.begin(), stack.end(), c); 41 | for (; pos != stack.end(); ++pos) { 42 | (*pos)->lowlink = c->lowlink; 43 | } 44 | do { 45 | Component* comp = stack.back(); 46 | stack.pop_back(); 47 | for (auto& c2 : comp->pubDeps) { 48 | if (c2->lowlink == comp->lowlink) 49 | comp->circulars.insert(c2); 50 | } 51 | for (auto& c2 : comp->privDeps) { 52 | if (c2->lowlink == comp->lowlink) 53 | comp->circulars.insert(c2); 54 | } 55 | comp->onStack = false; 56 | } while (c->onStack); 57 | } 58 | } 59 | 60 | void FindCircularDependencies(std::unordered_map &components) { 61 | std::vector stack; 62 | size_t index = 1; 63 | for (auto &c : components) { 64 | if (c.second->index == 0) { 65 | StrongConnect(stack, index, c.second); 66 | } 67 | } 68 | } 69 | 70 | void KillComponent(std::unordered_map &components, const std::string& str) { 71 | Component* target = components[str]; 72 | if (target) { 73 | for (auto& c : components) { 74 | c.second->pubDeps.erase(target); 75 | c.second->privDeps.erase(target); 76 | c.second->circulars.clear(); 77 | } 78 | delete target; 79 | } 80 | components.erase(str); 81 | } 82 | 83 | void MapFilesToComponents(std::unordered_map &components, std::unordered_map& files) { 84 | for (auto &fp : files) { 85 | std::string nameCopy = fp.first; 86 | size_t slashPos = nameCopy.find_last_of('/'); 87 | while (slashPos != nameCopy.npos) { 88 | nameCopy.resize(slashPos); 89 | auto it = components.find(nameCopy); 90 | if (it != components.end()) { 91 | fp.second.component = it->second; 92 | it->second->files.insert(&fp.second); 93 | break; 94 | } 95 | slashPos = nameCopy.find_last_of('/'); 96 | } 97 | } 98 | } 99 | 100 | void MapIncludesToDependencies(std::unordered_map &includeLookup, 101 | std::map> &ambiguous, 102 | std::unordered_map &components, 103 | std::unordered_map& files) { 104 | for (auto &fp : files) { 105 | for (auto &p : fp.second.rawIncludes) { 106 | // If this is a non-pointy bracket include, see if there's a local match first. 107 | // If so, it always takes precedence, never needs an include path added, and never is ambiguous (at least, for the compiler). 108 | std::string fullFilePath = (filesystem::path(fp.first).parent_path() / p.first).generic_string(); 109 | if (!p.second && files.count(fullFilePath)) { 110 | // This file exists as a local include. 111 | File* dep = &files.find(fullFilePath)->second; 112 | dep->hasInclude = true; 113 | fp.second.dependencies.insert(dep); 114 | } else { 115 | // We need to use an include path to find this. So let's see where we end up. 116 | std::string lowercaseInclude; 117 | std::transform(p.first.begin(), p.first.end(), std::back_inserter(lowercaseInclude), ::tolower); 118 | const std::string &fullPath = includeLookup[lowercaseInclude]; 119 | if (fullPath == "INVALID") { 120 | // We end up in more than one place. That's an ambiguous include then. 121 | ambiguous[lowercaseInclude].push_back(fp.first); 122 | } else if (fullPath.find("GENERATED:") == 0) { 123 | // We end up in a virtual file - it's not actually there yet, but it'll be generated. 124 | if (fp.second.component) { 125 | fp.second.component->buildAfters.insert(fullPath.substr(10)); 126 | Component *c = components["./" + fullPath.substr(10)]; 127 | if (c) { 128 | fp.second.component->privDeps.insert(c); 129 | } 130 | } 131 | } else if (files.count(fullPath)) { 132 | File *dep = &files.find(fullPath)->second; 133 | fp.second.dependencies.insert(dep); 134 | 135 | std::string inclpath = fullPath.substr(0, fullPath.size() - p.first.size() - 1); 136 | if (inclpath.size() == dep->component->root.generic_string().size()) { 137 | inclpath = "."; 138 | } else if (inclpath.size() > dep->component->root.generic_string().size() + 1) { 139 | inclpath = inclpath.substr(dep->component->root.generic_string().size() + 1); 140 | } else { 141 | inclpath = ""; 142 | } 143 | if (!inclpath.empty()) { 144 | dep->includePaths.insert(inclpath); 145 | } 146 | 147 | if (fp.second.component != dep->component) { 148 | fp.second.component->privDeps.insert(dep->component); 149 | dep->component->privLinks.insert(fp.second.component); 150 | dep->hasExternalInclude = true; 151 | } 152 | dep->hasInclude = true; 153 | } 154 | // else we don't know about it. Probably a system include of some sort. 155 | } 156 | } 157 | } 158 | } 159 | 160 | void PropagateExternalIncludes(std::unordered_map& files) { 161 | bool foundChange; 162 | do { 163 | foundChange = false; 164 | for (auto &fp : files) { 165 | if (fp.second.hasExternalInclude && fp.second.component) { 166 | for (auto &dep : fp.second.dependencies) { 167 | if (!dep->hasExternalInclude && dep->component == fp.second.component) { 168 | dep->hasExternalInclude = true; 169 | foundChange = true; 170 | } 171 | } 172 | } 173 | } 174 | } while (foundChange); 175 | } 176 | 177 | 178 | -------------------------------------------------------------------------------- /src/Analysis.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012-2016. TomTom International BV (http://tomtom.com). 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef __DEP_CHECKER__ANALYSIS_H 18 | #define __DEP_CHECKER__ANALYSIS_H 19 | 20 | #include "Component.h" 21 | 22 | void FindCircularDependencies(std::unordered_map& components); 23 | 24 | void MapFilesToComponents(std::unordered_map &components, std::unordered_map& files); 25 | 26 | void KillComponent(std::unordered_map &components, const std::string& str); 27 | 28 | void MapIncludesToDependencies(std::unordered_map &includeLookup, 29 | std::map> &ambiguous, 30 | std::unordered_map &components, 31 | std::unordered_map& files); 32 | 33 | void PropagateExternalIncludes(std::unordered_map& files); 34 | 35 | #endif 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # MSVC understands neither --coverage nor -O3 flag 2 | if (NOT MSVC) 3 | if(BUILD_COVERAGE) 4 | list(APPEND COMPILE_FLAGS --coverage) 5 | else() 6 | list(APPEND COMPILE_FLAGS -O3) 7 | endif() 8 | endif() 9 | 10 | add_library(cpp_dependencies_lib STATIC 11 | Analysis.h 12 | CmakeRegen.h 13 | Component.h 14 | Configuration.h 15 | Constants.h 16 | Input.h 17 | Output.h 18 | 19 | Analysis.cpp 20 | CmakeRegen.cpp 21 | Component.cpp 22 | Configuration.cpp 23 | generated.cpp 24 | Input.cpp 25 | Output.cpp 26 | ) 27 | target_compile_options(cpp_dependencies_lib 28 | PUBLIC 29 | ${COMPILE_FLAGS} 30 | ) 31 | target_link_libraries(cpp_dependencies_lib 32 | PRIVATE 33 | ${FILESYSTEM_LIBS} 34 | ) 35 | target_include_directories(cpp_dependencies_lib 36 | PUBLIC 37 | ${CMAKE_CURRENT_LIST_DIR} 38 | ${Boost_INCLUDE_DIRS} 39 | ) 40 | 41 | add_executable(cpp-dependencies 42 | main.cpp 43 | ) 44 | 45 | target_compile_features(cpp-dependencies PRIVATE cxx_range_for) 46 | 47 | target_link_libraries(cpp-dependencies 48 | PRIVATE 49 | cpp_dependencies_lib 50 | ) 51 | 52 | install(TARGETS cpp-dependencies RUNTIME DESTINATION bin) 53 | -------------------------------------------------------------------------------- /src/CmakeRegen.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012-2016. TomTom International BV (http://tomtom.com). 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "CmakeRegen.h" 18 | #include "Component.h" 19 | #include "Configuration.h" 20 | #include "FstreamInclude.h" 21 | #include "FilesystemInclude.h" 22 | #include "Input.h" 23 | #include 24 | #include 25 | 26 | 27 | static bool FilesAreDifferent(const filesystem::path &a, const filesystem::path &b) { 28 | if (filesystem::file_size(a) != filesystem::file_size(b)) { 29 | return true; 30 | } 31 | streams::ifstream as(a), bs(b); 32 | std::string l1, l2; 33 | while (as.good() && bs.good()) { 34 | getline(as, l1); 35 | getline(bs, l2); 36 | if (l1 != l2) { 37 | return true; 38 | } 39 | } 40 | return !(as.eof() && bs.eof()); 41 | } 42 | 43 | void MakeCmakeComment(std::string& cmakeComment, const std::string& contents) 44 | { 45 | cmakeComment.reserve(contents.size()); 46 | size_t lastPos = 0, findPos; 47 | while ((findPos = contents.find('\n', lastPos)) != std::string::npos) { 48 | cmakeComment.append("# "); 49 | cmakeComment.append(contents, lastPos, findPos - lastPos + 1); 50 | lastPos = findPos + 1; 51 | } 52 | if (lastPos < contents.size()) { 53 | cmakeComment.append("# "); 54 | cmakeComment.append(contents, lastPos, std::string::npos); 55 | cmakeComment.append("\n"); 56 | } 57 | } 58 | 59 | void RegenerateCmakeFilesForComponent(const Configuration& config, Component *comp, bool dryRun, bool writeToStdout) { 60 | if (comp->recreate || writeToStdout) { 61 | std::string compname = comp->CmakeName(); 62 | bool isHeaderOnly = true; 63 | std::set publicDeps, privateDeps, publicIncl, privateIncl; 64 | std::list files; 65 | for (auto &fp : comp->files) { 66 | files.push_back(fp->path.generic_string().c_str() + compname.size() + 3); 67 | filesystem::path p = fp->path; 68 | if (fp->hasInclude) { 69 | (fp->hasExternalInclude ? publicIncl : privateIncl).insert(fp->includePaths.begin(), 70 | fp->includePaths.end()); 71 | } 72 | if (IsCompileableFile(p.extension().string())) { 73 | isHeaderOnly = false; 74 | } 75 | } 76 | for (auto &d : comp->privDeps) { 77 | privateDeps.insert(d->CmakeName()); 78 | } 79 | for (auto &d : comp->pubDeps) { 80 | publicDeps.insert(d->CmakeName()); 81 | } 82 | files.sort(); 83 | if (!files.empty() && comp->type == "") { 84 | comp->type = "add_library"; 85 | } 86 | 87 | for (auto &s : publicDeps) { 88 | privateDeps.erase(s); 89 | } 90 | for (auto &s : publicIncl) { 91 | privateIncl.erase(s); 92 | } 93 | 94 | { 95 | streams::ofstream out; 96 | if (writeToStdout) { 97 | std::cout << "######## Start of " << comp->root.generic_string() << "/CMakeLists.txt\n"; 98 | } else { 99 | out.open(comp->root / "CMakeLists.txt.generated"); 100 | } 101 | std::ostream& o = writeToStdout ? std::cout : out; 102 | 103 | RegenerateCmakeHeader(o, config); 104 | 105 | if (comp->root == ".") { 106 | // Do this for the user, so that you don't get a warning on either 107 | // not doing this, or doing this in the addon file. 108 | o << "cmake_minimum_required(VERSION " << config.cmakeVersion << ")\n"; 109 | } 110 | 111 | if (!files.empty()) { 112 | o << "project(" << compname << ")" << "\n\n"; 113 | } 114 | 115 | o << "\n"; 116 | 117 | if (!files.empty()) { 118 | RegenerateCmakeAddTarget(o, config, *comp, files, isHeaderOnly); 119 | RegenerateCmakeTargetLinkLibraries(o, publicDeps, privateDeps, isHeaderOnly); 120 | RegenerateCmakeTargetIncludeDirectories(o, publicIncl, privateIncl, isHeaderOnly); 121 | RegenerateCmakeAddDependencies(o, *comp); 122 | } 123 | 124 | if (comp->hasAddonCmake) { 125 | o << "include(CMakeAddon.txt)" << "\n"; 126 | } 127 | 128 | if (config.reuseCustomSections) { 129 | o << comp->additionalCmakeDeclarations; 130 | } 131 | 132 | RegenerateCmakeAddSubdirectory(o, *comp); 133 | } 134 | if (writeToStdout) { 135 | std::cout << "######## End of " << comp->root.generic_string() << "/CMakeLists.txt\n"; 136 | // No actual file written. 137 | } else if (dryRun) { 138 | if (FilesAreDifferent(comp->root / "CMakeLists.txt.generated", comp->root / "CMakeLists.txt")) { 139 | std::cout << "Difference detected at " << comp->root << "\n"; 140 | } 141 | filesystem::remove(comp->root / "CMakeLists.txt.generated"); 142 | } else { 143 | if (FilesAreDifferent(comp->root / "CMakeLists.txt.generated", comp->root / "CMakeLists.txt")) { 144 | filesystem::rename(comp->root / "CMakeLists.txt.generated", comp->root / "CMakeLists.txt"); 145 | } else { 146 | filesystem::remove(comp->root / "CMakeLists.txt.generated"); 147 | } 148 | } 149 | } 150 | } 151 | 152 | void RegenerateCmakeHeader(std::ostream& o, const Configuration& config) { 153 | std::string licenseString; 154 | MakeCmakeComment(licenseString, config.licenseString); 155 | 156 | o << "#\n"; 157 | o << "# Copyright (c) " << config.companyName << ". All rights reserved.\n"; 158 | o << licenseString; 159 | o << "#\n\n"; 160 | 161 | o << "# " << config.regenTag << " - do not edit, your changes will be lost\n"; 162 | o << "# If you must edit, remove these two lines to avoid regeneration\n\n"; 163 | } 164 | 165 | void RegenerateCmakeAddDependencies(std::ostream& o, const Component& comp) { 166 | if (!comp.buildAfters.empty()) { 167 | o << "add_dependencies(${PROJECT_NAME}\n"; 168 | for (auto &s : comp.buildAfters) { 169 | o << " " << s << "\n"; 170 | } 171 | o << ")\n\n"; 172 | } 173 | } 174 | 175 | void RegenerateCmakeAddSubdirectory(std::ostream& o, 176 | const Component& comp) 177 | { 178 | // Temporary uses a set to sort subdirectories 179 | std::set subdirs; 180 | filesystem::directory_iterator it(comp.root), end; 181 | for (; it != end; ++it) { 182 | if (filesystem::is_regular_file(it->path() / "CMakeLists.txt")) { 183 | subdirs.insert(it->path().filename().generic_string()); 184 | } 185 | } 186 | for (auto subdir : subdirs) { 187 | o << "add_subdirectory(" << subdir << ")\n"; 188 | } 189 | 190 | } 191 | 192 | void RegenerateCmakeAddTarget(std::ostream& o, 193 | const Configuration& config, 194 | const Component& comp, 195 | const std::list& files, 196 | bool isHeaderOnly) { 197 | o << comp.type << "(${PROJECT_NAME}"; 198 | 199 | if (isHeaderOnly) { 200 | o << " INTERFACE\n"; 201 | } else { 202 | if (config.addLibraryAliases.count(comp.type) == 1) { 203 | o << " STATIC\n"; 204 | } else { 205 | o << "\n"; 206 | } 207 | 208 | for (auto &f : files) { 209 | o << " " << f << "\n"; 210 | } 211 | } 212 | o << comp.additionalTargetParameters; 213 | o << ")\n\n"; 214 | } 215 | 216 | void RegenerateCmakeTargetIncludeDirectories(std::ostream& o, 217 | const std::set& publicIncl, 218 | const std::set& privateIncl, 219 | bool isHeaderOnly) { 220 | if (!publicIncl.empty() || !privateIncl.empty()) { 221 | o << "target_include_directories(${PROJECT_NAME}\n"; 222 | if (isHeaderOnly) { 223 | o << " INTERFACE\n"; 224 | } 225 | 226 | if (!publicIncl.empty() && !isHeaderOnly) { 227 | o << " PUBLIC\n"; 228 | } 229 | for (auto &s : publicIncl) { 230 | o << " " << s << "\n"; 231 | } 232 | 233 | if (!privateIncl.empty() && !isHeaderOnly) { 234 | o << " PRIVATE\n"; 235 | } 236 | for (auto &s : privateIncl) { 237 | o << " " << s << "\n"; 238 | } 239 | o << ")\n\n"; 240 | } 241 | } 242 | 243 | void RegenerateCmakeTargetLinkLibraries(std::ostream& o, 244 | const std::set& publicDeps, 245 | const std::set& privateDeps, 246 | bool isHeaderOnly) { 247 | if (!publicDeps.empty() || !privateDeps.empty()) { 248 | o << "target_link_libraries(${PROJECT_NAME}\n"; 249 | if (isHeaderOnly) { 250 | o << " INTERFACE\n"; 251 | } 252 | 253 | if (!publicDeps.empty() && !isHeaderOnly) { 254 | o << " PUBLIC\n"; 255 | } 256 | for (auto &s : publicDeps) { 257 | o << " " << s << "\n"; 258 | } 259 | 260 | if (!privateDeps.empty() && !isHeaderOnly) { 261 | o << " PRIVATE\n"; 262 | } 263 | for (auto &s : privateDeps) { 264 | o << " " << s << "\n"; 265 | } 266 | 267 | o << ")\n\n"; 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /src/CmakeRegen.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012-2016. TomTom International BV (http://tomtom.com). 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef __DEP_CHECKER__CMAKEREGEN_H 18 | #define __DEP_CHECKER__CMAKEREGEN_H 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | struct Component; 26 | struct Configuration; 27 | 28 | void RegenerateCmakeFilesForComponent(const Configuration& config, 29 | Component *comp, 30 | bool dryRun, 31 | bool writeToStdout); 32 | 33 | void MakeCmakeComment(std::string& cmakeComment, 34 | const std::string& contents); 35 | 36 | void RegenerateCmakeAddDependencies(std::ostream& o, 37 | const Component& comp); 38 | void RegenerateCmakeAddSubdirectory(std::ostream& o, 39 | const Component& comp); 40 | void RegenerateCmakeAddTarget(std::ostream& o, 41 | const Configuration& config, 42 | const Component& comp, 43 | const std::list& files, 44 | bool isHeaderOnly); 45 | void RegenerateCmakeHeader(std::ostream& o, const Configuration& config); 46 | void RegenerateCmakeTargetIncludeDirectories(std::ostream& o, 47 | const std::set& publicIncl, 48 | const std::set& privateIncl, 49 | bool isHeaderOnly); 50 | void RegenerateCmakeTargetLinkLibraries(std::ostream& o, 51 | const std::set& publicDeps, 52 | const std::set& privateDeps, 53 | bool isHeaderOnly); 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /src/Component.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012-2016. TomTom International BV (http://tomtom.com). 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "Component.h" 18 | 19 | std::string Component::NiceName(char sub) const { 20 | if (root.string() == ".") { 21 | return std::string("ROOT"); 22 | } 23 | 24 | std::string nicename = root.generic_string().c_str() + 2; 25 | std::replace(nicename.begin(), nicename.end(), '/', sub); 26 | return nicename; 27 | } 28 | 29 | std::string Component::QuotedName() const { 30 | return std::string("\"") + NiceName('.') + std::string("\""); 31 | } 32 | 33 | std::string Component::CmakeName() const { 34 | return (recreate || name.empty()) ? NiceName('.') : name; 35 | } 36 | 37 | Component::Component(const filesystem::path &path) 38 | : root(path), name(""), recreate(false), hasAddonCmake(false), type("add_library"), index(0), lowlink(0), onStack(false) { 39 | } 40 | 41 | std::vector SortedNiceNames(const std::unordered_set &comps) { 42 | std::vector ret; 43 | ret.resize(comps.size()); 44 | std::transform(comps.begin(), comps.end(), ret.begin(), [](const Component *comp) -> std::string { 45 | return comp->NiceName('.'); 46 | }); 47 | std::sort(ret.begin(), ret.end()); 48 | 49 | return ret; 50 | } 51 | 52 | Component &AddComponentDefinition(std::unordered_map &components, 53 | const filesystem::path &path) { 54 | Component *&comp = components[path.generic_string()]; 55 | if (!comp) { 56 | comp = new Component(path); 57 | } 58 | return *comp; 59 | } 60 | 61 | size_t NodesWithCycles(std::unordered_map &components) { 62 | size_t count = 0; 63 | for (auto &c : components) { 64 | if (!c.second->circulars.empty()) { 65 | count++; 66 | } 67 | } 68 | return count; 69 | } 70 | 71 | void ExtractPublicDependencies(std::unordered_map &components) { 72 | for (auto &c : components) { 73 | Component *comp = c.second; 74 | for (auto &fp : comp->files) { 75 | if (fp->hasExternalInclude) { 76 | for (auto &dep : fp->dependencies) { 77 | comp->privDeps.erase(dep->component); 78 | comp->pubDeps.insert(dep->component); 79 | } 80 | } 81 | } 82 | comp->pubDeps.erase(comp); 83 | comp->privDeps.erase(comp); 84 | } 85 | } 86 | 87 | -------------------------------------------------------------------------------- /src/Component.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012-2016. TomTom International BV (http://tomtom.com). 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef __DEP_CHECKER__COMPONENT_H 18 | #define __DEP_CHECKER__COMPONENT_H 19 | 20 | 21 | #include "FilesystemInclude.h" 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | // Forward reference: 34 | struct Component; 35 | 36 | struct File { 37 | File(const filesystem::path& path) 38 | : path(path) 39 | , component(NULL) 40 | , loc(0) 41 | , includeCount(0) 42 | , hasExternalInclude(false) 43 | , hasInclude(false) 44 | { 45 | } 46 | 47 | void AddIncludeStmt(bool withPointyBrackets, const std::string& filename) { 48 | rawIncludes.insert(std::make_pair(filename, withPointyBrackets)); 49 | } 50 | filesystem::path path; 51 | std::map rawIncludes; 52 | std::unordered_set dependencies; 53 | std::unordered_set includePaths; 54 | Component *component; 55 | size_t loc; 56 | size_t includeCount; 57 | bool hasExternalInclude; 58 | bool hasInclude; 59 | }; 60 | 61 | struct Component { 62 | std::string NiceName(char sub) const; 63 | 64 | std::string QuotedName() const; 65 | 66 | std::string CmakeName() const; 67 | 68 | explicit Component(const filesystem::path &path); 69 | 70 | filesystem::path root; 71 | std::string name; 72 | // deps are the dependencies of your component 73 | std::unordered_set pubDeps; 74 | std::unordered_set privDeps; 75 | // links are the components which are using your component 76 | std::unordered_set pubLinks; 77 | std::unordered_set privLinks; 78 | std::unordered_set circulars; 79 | std::set buildAfters; 80 | std::unordered_set files; 81 | size_t loc() const { 82 | size_t l = 0; 83 | for (auto f : files) { l += f->loc; } 84 | return l; 85 | } 86 | bool recreate; 87 | bool hasAddonCmake; 88 | std::string type; 89 | size_t index, lowlink; 90 | std::string additionalTargetParameters; 91 | std::string additionalCmakeDeclarations; 92 | bool onStack; 93 | }; 94 | 95 | std::vector SortedNiceNames(const std::unordered_set &comps); 96 | 97 | Component &AddComponentDefinition(std::unordered_map &components, 98 | const filesystem::path &path ); 99 | 100 | size_t NodesWithCycles(std::unordered_map &components); 101 | 102 | void ExtractPublicDependencies(std::unordered_map &components); 103 | 104 | void CreateIncludeLookupTable(std::unordered_map& files, 105 | std::unordered_map &includeLookup, 106 | std::map> &collisions); 107 | 108 | #endif 109 | 110 | 111 | -------------------------------------------------------------------------------- /src/Configuration.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012-2016. TomTom International BV (http://tomtom.com). 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "Configuration.h" 18 | #include "Constants.h" 19 | #include "FstreamInclude.h" 20 | #include 21 | #include 22 | 23 | static inline std::string Trim(const std::string &s) { 24 | size_t first = s.find_first_not_of(" \t\r\n"), last = s.find_last_not_of(" \t\r\n"); 25 | return s.substr(first, last - first + 1); 26 | } 27 | 28 | static void ReadSet(std::unordered_set& set, 29 | std::istream& in) 30 | { 31 | std::string line; 32 | while (in.good()) { 33 | std::getline(in, line); 34 | size_t pos = line.find_first_of("]"); 35 | if (pos != std::string::npos) { 36 | return; 37 | } 38 | set.insert(Trim(line)); 39 | } 40 | } 41 | 42 | static std::string ReadMultilineString(std::istream& in) 43 | { 44 | std::string multiline; 45 | std::string line; 46 | while (in.good()) { 47 | std::getline(in, line); 48 | size_t pos = line.find_first_of("\"\"\""); 49 | if (pos != std::string::npos) { 50 | break; 51 | } 52 | multiline += line; 53 | multiline += "\n"; 54 | } 55 | return multiline; 56 | } 57 | 58 | Configuration::Configuration() 59 | : companyName("YourCompany") 60 | , licenseString("") 61 | , regenTag("GENERATED BY CPP-DEPENDENCIES") 62 | , cmakeVersion("3.0") 63 | , versionUsed(CURRENT_VERSION) 64 | , cycleColor("orange") 65 | , publicDepColor("blue") 66 | , privateDepColor("lightblue") 67 | , componentLinkLimit(30) 68 | , componentLocLowerLimit(200) 69 | , componentLocUpperLimit(20000) 70 | , fileLocUpperLimit(2000) 71 | , reuseCustomSections(false) 72 | { 73 | addLibraryAliases.insert("add_library"); 74 | addExecutableAliases.insert("add_executable"); 75 | } 76 | 77 | void Configuration::read(std::istream& in) 78 | { 79 | std::string line; 80 | while (in.good()) { 81 | std::getline(in, line); 82 | while (in.good() && line.back() == '\\') { 83 | line.resize(line.size() - 1); 84 | std::string nextLine; 85 | std::getline(in, nextLine); 86 | line += nextLine; 87 | } 88 | 89 | size_t pos = line.find_first_of("#"); 90 | if (pos != std::string::npos) 91 | line.resize(line.find_first_of("#")); 92 | 93 | pos = line.find(": "); 94 | if (pos == std::string::npos) 95 | continue; 96 | 97 | std::string name = line.substr(0, pos); 98 | std::string value = line.substr(pos+2); 99 | if (name == "cycleColor") { cycleColor = value; } 100 | else if (name == "publicDepColor") { publicDepColor = value; } 101 | else if (name == "versionUsed") { versionUsed = value; } 102 | else if (name == "privateDepColor") { privateDepColor = value; } 103 | else if (name == "regenTag") { regenTag = value; } 104 | else if (name == "cmakeVersion") { cmakeVersion = value; } 105 | else if (name == "companyName") { companyName = value; } 106 | else if (name == "componentLinkLimit") { componentLinkLimit = atol(value.c_str()); } 107 | else if (name == "componentLocLowerLimit") { componentLocLowerLimit = atol(value.c_str()); } 108 | else if (name == "componentLocUpperLimit") { componentLocUpperLimit = atol(value.c_str()); } 109 | else if (name == "fileLocUpperLimit") { fileLocUpperLimit = atol(value.c_str()); } 110 | else if (name == "addLibraryAlias") { ReadSet(addLibraryAliases, in); } 111 | else if (name == "addExecutableAlias") { ReadSet(addExecutableAliases, in); } 112 | else if (name == "addIgnores") { ReadSet(addIgnores, in); } 113 | else if (name == "licenseString") { licenseString = ReadMultilineString(in); } 114 | else if (name == "reuseCustomSections") { reuseCustomSections = (value == "true"); } 115 | else if (name == "blacklist") { ReadSet(blacklist, in); } 116 | else { 117 | std::cout << "Ignoring unknown tag in configuration file: " << name << "\n"; 118 | } 119 | } 120 | } 121 | 122 | 123 | -------------------------------------------------------------------------------- /src/Configuration.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012-2016. TomTom International BV (http://tomtom.com). 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef __DEP_CHECKER__CONFIGURATION_H 18 | #define __DEP_CHECKER__CONFIGURATION_H 19 | 20 | #include 21 | #include 22 | 23 | struct Configuration { 24 | Configuration(); 25 | void read(std::istream& inputFile); 26 | std::string companyName; 27 | std::string licenseString; 28 | std::string regenTag; 29 | std::string cmakeVersion; 30 | std::string versionUsed; 31 | std::string cycleColor; 32 | std::string publicDepColor; 33 | std::string privateDepColor; 34 | std::unordered_set addLibraryAliases; 35 | std::unordered_set addExecutableAliases; 36 | std::unordered_set addIgnores; 37 | std::unordered_set blacklist; 38 | size_t componentLinkLimit; 39 | size_t componentLocLowerLimit; 40 | size_t componentLocUpperLimit; 41 | size_t fileLocUpperLimit; 42 | bool reuseCustomSections; 43 | }; 44 | 45 | #endif 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/Constants.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012-2016. TomTom International BV (http://tomtom.com). 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef __DEP_CHECKER__CONSTANTS_H 18 | #define __DEP_CHECKER__CONSTANTS_H 19 | 20 | #define CONFIG_FILE "config-cpp-dependencies.txt" 21 | 22 | #define CURRENT_VERSION "2" 23 | 24 | #endif 25 | 26 | -------------------------------------------------------------------------------- /src/FilesystemInclude.h: -------------------------------------------------------------------------------- 1 | #ifndef __DEP_CHECKER__FILESYSTEMINCLUDE_H 2 | #define __DEP_CHECKER__FILESYSTEMINCLUDE_H 3 | 4 | #ifdef WITH_BOOST 5 | 6 | #include 7 | 8 | namespace filesystem = boost::filesystem; 9 | 10 | #else 11 | 12 | #include 13 | 14 | namespace filesystem = std::experimental::filesystem; 15 | 16 | #endif 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /src/FstreamInclude.h: -------------------------------------------------------------------------------- 1 | #ifndef __DEP_CHECKER__FSTREAMINCLUDE_H 2 | #define __DEP_CHECKER__FSTREAMINCLUDE_H 3 | 4 | #ifdef WITH_BOOST 5 | 6 | #include 7 | #include 8 | 9 | namespace streams = boost::filesystem; 10 | 11 | #else 12 | 13 | #include 14 | 15 | namespace streams = std; 16 | 17 | #endif 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /src/Input.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012-2016. TomTom International BV (http://tomtom.com). 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "Component.h" 18 | #include "Configuration.h" 19 | #include "FstreamInclude.h" 20 | #include "Input.h" 21 | #include 22 | #include 23 | 24 | #ifdef WITH_MMAP 25 | #include 26 | #include 27 | #include 28 | #endif 29 | 30 | #ifdef NO_MEMRCHR 31 | static const void* memrchr(const void* buffer, unsigned char value, size_t buffersize) { 32 | const unsigned char* buf = (const unsigned char*)buffer; 33 | do { 34 | buffersize--; 35 | if (buf[buffersize] == value) return buf + buffersize; 36 | } while(buffersize > 0); 37 | return NULL; 38 | } 39 | #endif 40 | 41 | bool IsCompileableFile(const std::string& ext) { 42 | static const std::unordered_set exts = { ".c", ".C", ".cc", ".cpp", ".m", ".mm" }; 43 | return exts.count(ext) > 0; 44 | } 45 | 46 | static bool IsCode(const std::string &ext) { 47 | static const std::unordered_set exts = { ".c", ".C", ".cc", ".cpp", ".m", ".mm", ".h", ".H", ".hpp", ".hh" }; 48 | return exts.count(ext) > 0; 49 | } 50 | 51 | static void ReadCodeFrom(File& f, const char* buffer, size_t buffersize, bool withLoc) { 52 | if (buffersize == 0) return; 53 | size_t offset = 0; 54 | enum State { None, AfterHash, AfterInclude, InsidePointyIncludeBrackets, InsideStraightIncludeBrackets } state = None; 55 | // Try to terminate reading the file when we've read the last possible preprocessor command 56 | const char* lastHash = static_cast(memrchr(buffer, '#', buffersize)); 57 | if (lastHash) { 58 | // Common case optimization: Header with inclusion guard 59 | if (strncmp(lastHash, "#endif", 6) == 0) { 60 | lastHash = static_cast(memrchr(buffer, '#', lastHash - buffer - 1)); 61 | } 62 | if (lastHash) { 63 | const char* nextNewline = static_cast(memchr(lastHash, '\n', buffersize - (lastHash - buffer))); 64 | if (nextNewline) { 65 | buffersize = nextNewline - buffer; 66 | } 67 | } 68 | } 69 | if (withLoc) { 70 | f.loc = 0; 71 | for (size_t n = 0; n < buffersize; n++) { 72 | if (buffer[n] == '\n') f.loc++; 73 | } 74 | } 75 | const char* nextHash = static_cast(memchr(buffer+offset, '#', buffersize-offset)); 76 | const char* nextSlash = static_cast(memchr(buffer+offset, '/', buffersize-offset)); 77 | size_t start = 0; 78 | while (offset < buffersize) { 79 | switch (state) { 80 | case None: 81 | { 82 | if (nextHash && nextHash < buffer + offset) nextHash = static_cast(memchr(buffer+offset, '#', buffersize-offset)); 83 | if (nextHash == NULL) return; 84 | if (nextSlash && nextSlash < buffer + offset) nextSlash = static_cast(memchr(buffer+offset, '/', buffersize-offset)); 85 | if (nextSlash && nextSlash < nextHash) { 86 | offset = nextSlash - buffer; 87 | if (buffer[offset + 1] == '/') { 88 | offset = static_cast(memchr(buffer+offset, '\n', buffersize-offset)) - buffer; 89 | } 90 | else if (buffer[offset + 1] == '*') { 91 | do { 92 | const char* endSlash = static_cast(memchr(buffer + offset + 1, '/', buffersize - offset)); 93 | if (!endSlash) return; 94 | offset = endSlash - buffer; 95 | } while (buffer[offset-1] != '*'); 96 | } 97 | } else { 98 | offset = nextHash - buffer; 99 | state = AfterHash; 100 | } 101 | } 102 | break; 103 | case AfterHash: 104 | switch (buffer[offset]) { 105 | case ' ': 106 | case '\t': 107 | break; 108 | case 'i': 109 | if (buffer[offset + 1] == 'm' && 110 | buffer[offset + 2] == 'p' && 111 | buffer[offset + 3] == 'o' && 112 | buffer[offset + 4] == 'r' && 113 | buffer[offset + 5] == 't') { 114 | state = AfterInclude; 115 | offset += 5; 116 | } 117 | else if (buffer[offset + 1] == 'n' && 118 | buffer[offset + 2] == 'c' && 119 | buffer[offset + 3] == 'l' && 120 | buffer[offset + 4] == 'u' && 121 | buffer[offset + 5] == 'd' && 122 | buffer[offset + 6] == 'e') { 123 | state = AfterInclude; 124 | offset += 6; 125 | } 126 | else 127 | { 128 | state = None; 129 | } 130 | break; 131 | default: 132 | state = None; 133 | break; 134 | } 135 | break; 136 | case AfterInclude: 137 | switch (buffer[offset]) { 138 | case ' ': 139 | case '\t': 140 | break; 141 | case '<': 142 | start = offset + 1; 143 | state = InsidePointyIncludeBrackets; 144 | break; 145 | case '"': 146 | start = offset + 1; 147 | state = InsideStraightIncludeBrackets; 148 | break; 149 | default: 150 | // Buggy code, skip over this include. 151 | state = None; 152 | break; 153 | } 154 | break; 155 | case InsidePointyIncludeBrackets: 156 | switch (buffer[offset]) { 157 | case '\n': 158 | state = None; // Buggy code, skip over this include. 159 | break; 160 | case '>': 161 | f.AddIncludeStmt(true, std::string(&buffer[start], &buffer[offset])); 162 | state = None; 163 | break; 164 | } 165 | break; 166 | case InsideStraightIncludeBrackets: 167 | switch (buffer[offset]) { 168 | case '\n': 169 | state = None; // Buggy code, skip over this include. 170 | break; 171 | case '\"': 172 | f.AddIncludeStmt(false, std::string(&buffer[start], &buffer[offset])); 173 | state = None; 174 | break; 175 | } 176 | break; 177 | } 178 | offset++; 179 | } 180 | } 181 | 182 | #ifdef WITH_MMAP 183 | static void ReadCode(std::unordered_map& files, const filesystem::path &path, bool withLoc) { 184 | File& f = files.insert(std::make_pair(path.generic_string(), File(path))).first->second; 185 | int fd = open(path.c_str(), O_RDONLY); 186 | size_t fileSize = filesystem::file_size(path); 187 | void* p = mmap(NULL, fileSize, PROT_READ, MAP_PRIVATE, fd, 0); 188 | ReadCodeFrom(f, static_cast(p), fileSize, withLoc); 189 | munmap(p, fileSize); 190 | close(fd); 191 | } 192 | #else 193 | static void ReadCode(std::unordered_map& files, const filesystem::path &path, bool withLoc) { 194 | File& f = files.insert(std::make_pair(path.generic_string(), File(path))).first->second; 195 | std::string buffer; 196 | buffer.resize(filesystem::file_size(path)); 197 | { 198 | streams::ifstream(path).read(&buffer[0], buffer.size()); 199 | } 200 | ReadCodeFrom(f, buffer.data(), buffer.size(), withLoc); 201 | } 202 | #endif 203 | 204 | static bool IsItemBlacklisted(const Configuration& config, const filesystem::path &path) { 205 | std::string pathS = path.generic_string(); 206 | std::string fileName = path.filename().generic_string(); 207 | for (auto& s : config.blacklist) { 208 | if (pathS.compare(2, s.size(), s) == 0) { 209 | return true; 210 | } 211 | if (s == fileName) 212 | return true; 213 | } 214 | return false; 215 | } 216 | 217 | static bool TryGetComponentType(Component& component, 218 | std::unordered_set componentTypes, 219 | const std::string& line) 220 | { 221 | for (std::unordered_set::const_iterator it = componentTypes.begin(); 222 | it != componentTypes.end(); 223 | ++it) 224 | { 225 | if (strstr(line.c_str(), it->c_str())) 226 | { 227 | component.type = *it; 228 | return true; 229 | } 230 | } 231 | return false; 232 | } 233 | 234 | static bool IsAutomaticlyGeneratedSection(std::string line) { 235 | return strstr(line.c_str(), "target_link_libraries(") || 236 | strstr(line.c_str(), "add_subdirectory(") || 237 | strstr(line.c_str(), "target_include_directories(") || 238 | strstr(line.c_str(), "add_dependencies(") || 239 | strstr(line.c_str(), "include(CMakeAddon.txt)") || 240 | strstr(line.c_str(), "source_group(\"Implementation\\") || 241 | strstr(line.c_str(), "set(IMPLEMENTATION_SOURCES") || 242 | strstr(line.c_str(), "set(IMPLEMENTATION_HEADERS") || 243 | strstr(line.c_str(), "set(INTERFACE_FILES"); 244 | } 245 | 246 | static void ReadCmakelist(const Configuration& config, std::unordered_map &components, 247 | const filesystem::path &path) { 248 | streams::ifstream in(path); 249 | std::string line; 250 | Component &comp = AddComponentDefinition(components, path.parent_path()); 251 | bool inTargetDefinition = false; 252 | bool inAutoSection = false; 253 | int parenLevel = 0; 254 | do { 255 | getline(in, line); 256 | if (strstr(line.c_str(), config.regenTag.c_str())) { 257 | comp.recreate = true; 258 | continue; 259 | } 260 | while (line.back() == '\\') { 261 | line.pop_back(); 262 | std::string nextLine; 263 | getline(in, nextLine); 264 | line += nextLine; 265 | } 266 | if (line.size() > 0 && line[0] == '#') { 267 | continue; 268 | } 269 | size_t start = line.find("\""); 270 | while (start != std::string::npos) { 271 | size_t end = line.find("\"", start+1); 272 | line.replace(start+1, end - start - 1, ""); 273 | start = line.find("\"", start+2); 274 | } 275 | int newParenLevel = parenLevel + std::count(line.begin(), line.end(), '(') 276 | - std::count(line.begin(), line.end(), ')'); 277 | if (strstr(line.c_str(), "project(") == line.c_str()) { 278 | size_t end = line.find(')'); 279 | if (end != line.npos) { 280 | comp.name = line.substr(8, end - 8); 281 | } 282 | } 283 | else if (TryGetComponentType(comp, config.addLibraryAliases, line)) { 284 | inTargetDefinition = true; 285 | } else if (TryGetComponentType(comp, config.addExecutableAliases, line)) { 286 | inTargetDefinition = true; 287 | } else if (inTargetDefinition) { 288 | const size_t endOfLine = (newParenLevel == 0) ? line.find_last_of(')') 289 | : line.length(); 290 | if (endOfLine > 0) { 291 | const std::string targetLine(line.substr(0, endOfLine)); 292 | if (!strstr(targetLine.c_str(), "${IMPLEMENTATION_SOURCES}") && 293 | !strstr(targetLine.c_str(), "${IMPLEMENTATION_HEADERS}") && 294 | !IsCode(filesystem::path(targetLine).extension().generic_string())) { 295 | comp.additionalTargetParameters.append(targetLine + '\n'); 296 | } 297 | } 298 | if (newParenLevel == 0) { 299 | inTargetDefinition = false; 300 | } 301 | } else if (config.reuseCustomSections) { 302 | if (IsAutomaticlyGeneratedSection(line)) 303 | { 304 | inAutoSection = true; 305 | } else { 306 | if (!inAutoSection && !line.empty()) { 307 | comp.additionalCmakeDeclarations.append(line + '\n'); 308 | } 309 | } 310 | if (inAutoSection && newParenLevel == 0) { 311 | inAutoSection = false; 312 | } 313 | } 314 | parenLevel = newParenLevel; 315 | } while (in.good()); 316 | assert(parenLevel == 0 || (printf("final level of parentheses=%d\n", parenLevel), 0)); 317 | } 318 | 319 | void LoadFileList(const Configuration& config, 320 | std::unordered_map &components, 321 | std::unordered_map& files, 322 | const filesystem::path& sourceDir, 323 | bool inferredComponents, 324 | bool withLoc) { 325 | filesystem::path outputpath = filesystem::current_path(); 326 | filesystem::current_path(sourceDir.c_str()); 327 | AddComponentDefinition(components, "."); 328 | for (filesystem::recursive_directory_iterator it("."), end; 329 | it != end; ++it) { 330 | const auto &parent = it->path().parent_path(); 331 | 332 | // skip hidden files and dirs 333 | std::string fileName = it->path().filename().generic_string(); 334 | if ((fileName.size() >= 2 && fileName[0] == '.') || 335 | IsItemBlacklisted(config, it->path())) { 336 | #ifdef WITH_BOOST 337 | it.no_push(); 338 | #else 339 | it.disable_recursion_pending(); 340 | #endif 341 | continue; 342 | } 343 | 344 | if (inferredComponents) AddComponentDefinition(components, parent); 345 | 346 | if (it->path().filename() == "CMakeLists.txt") { 347 | ReadCmakelist(config, components, it->path()); 348 | } else if (filesystem::is_regular_file(it->status())) { 349 | if (it->path().generic_string().find("CMakeAddon.txt") != std::string::npos) { 350 | AddComponentDefinition(components, parent).hasAddonCmake = true; 351 | } else if (IsCode(it->path().extension().generic_string())) { 352 | ReadCode(files, it->path(), withLoc); 353 | } 354 | } 355 | } 356 | filesystem::current_path(outputpath); 357 | } 358 | 359 | void ForgetEmptyComponents(std::unordered_map &components) { 360 | for (auto it = begin(components); it != end(components);) { 361 | if (it->second->files.empty()) 362 | it = components.erase(it); 363 | else 364 | ++it; 365 | } 366 | } 367 | 368 | 369 | -------------------------------------------------------------------------------- /src/Input.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012-2016. TomTom International BV (http://tomtom.com). 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef __DEP_CHECKER__INPUT_H 18 | #define __DEP_CHECKER__INPUT_H 19 | 20 | #include "FilesystemInclude.h" 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | struct File; 27 | struct Component; 28 | 29 | bool IsCompileableFile(const std::string& ext); 30 | 31 | void ForgetEmptyComponents(std::unordered_map &components); 32 | void LoadFileList(const Configuration& config, 33 | std::unordered_map &components, 34 | std::unordered_map& files, 35 | const filesystem::path& sourceDir, 36 | bool inferredComponents, 37 | bool withLoc); 38 | 39 | #endif 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/Output.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012-2016. TomTom International BV (http://tomtom.com). 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "Component.h" 18 | #include "Configuration.h" 19 | #include "FstreamInclude.h" 20 | #include "Output.h" 21 | #include 22 | #include 23 | 24 | #ifndef _WIN32 25 | #define CURSES_CYCLIC_DEPENDENCY "" 26 | #define CURSES_PUBLIC_DEPENDENCY "" 27 | #define CURSES_PRIVATE_DEPENDENCY "" 28 | #define CURSES_RESET_COLOR "" 29 | #else 30 | #define CURSES_CYCLIC_DEPENDENCY "" 31 | #define CURSES_PUBLIC_DEPENDENCY "" 32 | #define CURSES_PRIVATE_DEPENDENCY "" 33 | #define CURSES_RESET_COLOR "" 34 | #endif 35 | 36 | static const std::string& getLinkColor(const Configuration& config, Component *a, Component *b) { 37 | // One of the links in this chain must be broken because it ties together a bundle of apparently unrelated components 38 | if (a->circulars.find(b) != a->circulars.end()) { 39 | return config.cycleColor; 40 | } 41 | if (a->pubDeps.find(b) != a->pubDeps.end()) { 42 | return config.publicDepColor; 43 | } 44 | return config.privateDepColor; 45 | } 46 | 47 | static const char* getShapeForSize(Component* c) { 48 | size_t loc = c->loc(); 49 | if (loc < 1000) { 50 | return "oval"; 51 | } else if (loc < 5000) { 52 | return "doublecircle"; 53 | } else if (loc < 20000) { 54 | return "octagon"; 55 | } else if (loc < 50000) { 56 | return "doubleoctagon"; 57 | } else { 58 | return "tripleoctagon"; 59 | } 60 | } 61 | 62 | struct OstreamHolder { 63 | OstreamHolder(const filesystem::path& outFile) { 64 | if (outFile == "-") { 65 | isStdout = true; 66 | } else { 67 | isStdout = false; 68 | out.open(outFile); 69 | } 70 | } 71 | ::std::ostream& get() { return isStdout ? std::cout : out; } 72 | bool isStdout; 73 | streams::ofstream out; 74 | }; 75 | 76 | void OutputFlatDependencies(const Configuration& config, std::unordered_map &components, const filesystem::path &outfile) { 77 | OstreamHolder outHolder(outfile); 78 | std::ostream& out = outHolder.get(); 79 | out << "digraph dependencies {" << '\n'; 80 | for (const auto &c : components) { 81 | if (c.second->root.string().size() > 2 && 82 | c.second->files.size()) { 83 | out << " " << c.second->QuotedName() << " [shape=" << getShapeForSize(c.second) << "];\n"; 84 | } 85 | 86 | std::set depcomps; 87 | for (auto &d : c.second->privDeps) { 88 | if (d->root.string().size() > 2 && 89 | d->files.size()) { 90 | if (depcomps.insert(d).second) { 91 | out << " " << c.second->QuotedName() << " -> " << d->QuotedName() << " [color=" 92 | << getLinkColor(config, c.second, d) << "];" << '\n'; 93 | } 94 | } 95 | } 96 | for (auto &d : c.second->pubDeps) { 97 | if (d->root.string().size() > 2 && 98 | d->files.size()) { 99 | if (depcomps.insert(d).second) { 100 | out << " " << c.second->QuotedName() << " -> " << d->QuotedName() << " [color=" 101 | << getLinkColor(config, c.second, d) << "];" << '\n'; 102 | } 103 | } 104 | } 105 | } 106 | out << "}" << '\n'; 107 | } 108 | 109 | void OutputCircularDependencies(const Configuration& config, std::unordered_map &components, 110 | const filesystem::path &outfile) { 111 | OstreamHolder outHolder(outfile); 112 | std::ostream& out = outHolder.get(); 113 | out << "digraph dependencies {" << '\n'; 114 | for (const auto &c : components) { 115 | if (c.second->circulars.empty()) { 116 | continue; 117 | } 118 | 119 | out << " " << c.second->QuotedName() << " [shape=" << getShapeForSize(c.second) << "];\n"; 120 | 121 | for (const auto &t : c.second->circulars) { 122 | out << " " << c.second->QuotedName() << " -> " << t->QuotedName() << " [color=" 123 | << getLinkColor(config, c.second, t) << "];" << '\n'; 124 | } 125 | } 126 | out << "}" << '\n'; 127 | } 128 | 129 | void PrintGraphOnTarget(const Configuration& config, const filesystem::path &outfile, Component *c) { 130 | if (!c) { 131 | std::cout << "Component does not exist (double-check spelling)\n"; 132 | return; 133 | } 134 | OstreamHolder outHolder(outfile); 135 | std::ostream& out = outHolder.get(); 136 | 137 | std::stack todo; 138 | std::set comps; 139 | todo.push(c); 140 | comps.insert(c); 141 | out << "digraph dependencies {" << '\n'; 142 | while (!todo.empty()) { 143 | Component *c2 = todo.top(); 144 | todo.pop(); 145 | 146 | out << " " << c2->QuotedName() << " [shape=" << getShapeForSize(c2) << "];\n"; 147 | 148 | std::set depcomps; 149 | for (auto &d : c2->privDeps) { 150 | if (d->root.string().size() > 2 && 151 | d->files.size()) { 152 | if (depcomps.insert(d).second) { 153 | out << " " << c2->QuotedName() << " -> " << d->QuotedName() << " [color=" << getLinkColor(config, c2, d) 154 | << "];" << '\n'; 155 | } 156 | if (comps.insert(d).second) { 157 | todo.push(d); 158 | } 159 | } 160 | } 161 | for (auto &d : c2->pubDeps) { 162 | if (d->root.string().size() > 2 && 163 | d->files.size()) { 164 | if (depcomps.insert(d).second) { 165 | out << " " << c2->QuotedName() << " -> " << d->QuotedName() << " [color=" << getLinkColor(config, c2, d) 166 | << "];" << '\n'; 167 | } 168 | if (comps.insert(d).second) { 169 | todo.push(d); 170 | } 171 | } 172 | } 173 | } 174 | out << "}" << '\n'; 175 | } 176 | 177 | void FindAndPrintCycleFrom(Component *origin, Component *c, std::unordered_set alreadyHad, 178 | std::vector order) { 179 | if (!alreadyHad.insert(c).second) { 180 | return; 181 | } 182 | order.push_back(c); 183 | for (auto &d : c->circulars) { 184 | if (d == origin) { 185 | for (auto &comp : order) { 186 | std::cout << comp->NiceName('.') << " -> "; 187 | } 188 | std::cout << origin->NiceName('.') << "\n"; 189 | } else { 190 | FindAndPrintCycleFrom(origin, d, alreadyHad, order); 191 | } 192 | } 193 | } 194 | 195 | void PrintCyclesForTarget(Component *c) { 196 | std::unordered_set alreadyHad; 197 | std::vector order; 198 | FindAndPrintCycleFrom(c, c, alreadyHad, order); 199 | } 200 | 201 | void PrintLinksForTarget(Component *c) { 202 | std::vector sortedPubLinks(SortedNiceNames(c->pubLinks)); 203 | std::cout << "Public linked (" << sortedPubLinks.size() << "):"; 204 | for (auto &d : sortedPubLinks) { 205 | std::cout << ' ' << d; 206 | } 207 | 208 | std::vector sortedPrivLinks(SortedNiceNames(c->privLinks)); 209 | std::cout << "Private linked (" << sortedPrivLinks.size() << "):"; 210 | for (auto &d : sortedPrivLinks) { 211 | std::cout << ' ' << d; 212 | } 213 | std::cout << '\n'; 214 | } 215 | 216 | void PrintInfoOnTarget(Component *c) { 217 | if (!c) { 218 | std::cout << "Component does not exist (double-check spelling)\n"; 219 | return; 220 | } 221 | std::cout << "Root: " << c->root.string() << '\n'; 222 | std::cout << "Name: " << c->name.c_str() << '\n'; 223 | std::cout << "Type: " << c->type.c_str() << '\n'; 224 | std::cout << "Lines of Code: " << c->loc() << '\n'; 225 | std::cout << "Recreate: " << (c->recreate ? "true" : "false") << '\n'; 226 | std::cout << "Has CMakeAddon.txt: " << (c->hasAddonCmake ? "true" : "false") << '\n'; 227 | 228 | std::vector sortedPubDeps(SortedNiceNames(c->pubDeps)); 229 | std::cout << "Public dependencies (" << sortedPubDeps.size() << "): "; 230 | for (auto &d : sortedPubDeps) { 231 | std::cout << ' ' << d; 232 | } 233 | std::vector sortedPrivDeps(SortedNiceNames(c->privDeps)); 234 | std::cout << "\nPrivate dependencies (" << sortedPrivDeps.size() << "): "; 235 | for (auto &d : sortedPrivDeps) { 236 | std::cout << ' ' << d; 237 | } 238 | std::cout << "\nBuild-afters:"; 239 | for (auto &d : c->buildAfters) { 240 | std::cout << ' ' << d; 241 | } 242 | std::cout << "\nFiles (" << c->files.size() << "):"; 243 | for (auto &d : c->files) { 244 | std::cout << ' ' << d->path.string(); 245 | } 246 | std::cout << '\n'; 247 | PrintLinksForTarget(c); 248 | std::cout << '\n'; 249 | } 250 | 251 | void PrintAllComponents(std::unordered_map &components, const char* description, std::function predicate) { 252 | std::vector selected; 253 | for (auto& c : components) { 254 | if (predicate(*c.second)) { 255 | selected.push_back(c.second->NiceName('.')); 256 | } 257 | } 258 | if (selected.empty()) return; 259 | 260 | std::cout << description << '\n'; 261 | std::sort(selected.begin(), selected.end()); 262 | for (auto& c : selected) { 263 | std::cout << " " << c << '\n'; 264 | } 265 | std::cout << '\n'; 266 | } 267 | 268 | void PrintAllFiles(std::unordered_map& files, const char* description, std::function predicate) { 269 | std::vector selected; 270 | for (auto& f : files) { 271 | if (predicate(f.second)) { 272 | selected.push_back(f.second.path.string().c_str()); 273 | } 274 | } 275 | if (selected.empty()) return; 276 | 277 | std::cout << description << '\n'; 278 | std::sort(selected.begin(), selected.end()); 279 | for (auto& c : selected) { 280 | std::cout << " " << c << '\n'; 281 | } 282 | std::cout << '\n'; 283 | } 284 | 285 | void FindSpecificLink(const Configuration& config, std::unordered_map& files, Component *from, Component *to) { 286 | std::unordered_map parents; 287 | std::unordered_set alreadyHad; 288 | std::deque tocheck; 289 | tocheck.push_back(from); 290 | while (!tocheck.empty()) { 291 | Component *c = tocheck.front(); 292 | tocheck.pop_front(); 293 | if (alreadyHad.find(c) == alreadyHad.end()) { 294 | alreadyHad.insert(c); 295 | if (c == to) { 296 | std::vector links; 297 | while (c != from) { 298 | links.push_back(c); 299 | c = parents[c]; 300 | } 301 | Component *p = c; 302 | while (!links.empty()) { 303 | Component *c2 = links.back(); 304 | links.pop_back(); 305 | std::string color = getLinkColor(config, p, c2); 306 | if (color == config.cycleColor) { 307 | std::cout << CURSES_CYCLIC_DEPENDENCY; 308 | } else if (color == config.publicDepColor) { 309 | std::cout << CURSES_PUBLIC_DEPENDENCY; 310 | } else { 311 | std::cout << CURSES_PRIVATE_DEPENDENCY; 312 | } 313 | std::cout << p->NiceName('.') << " -> " << c2->NiceName('.') << '\n'; 314 | std::cout << CURSES_RESET_COLOR; 315 | for (auto &f : files) { 316 | if (f.second.component == p) { 317 | for (auto &f2 : f.second.dependencies) { 318 | if (f2->component == c2) { 319 | std::cout << " " << f.second.path.string() << " includes " << f2->path.string() << '\n'; 320 | } 321 | } 322 | } 323 | } 324 | p = c2; 325 | } 326 | return; 327 | } 328 | for (auto &d : c->pubDeps) { 329 | tocheck.push_back(d); 330 | Component *&p = parents[d]; 331 | if (!p) { 332 | p = c; 333 | } 334 | } 335 | for (auto &d : c->privDeps) { 336 | tocheck.push_back(d); 337 | Component *&p = parents[d]; 338 | if (!p) { 339 | p = c; 340 | } 341 | } 342 | } 343 | } 344 | 345 | std::cout << "No path could be found from " << from->NiceName('.') << " to " << to->NiceName('.') << '\n'; 346 | } 347 | 348 | static void UpdateIncludeFor(std::unordered_map& files, std::unordered_map &includeLookup, File* from, Component* comp, const std::string& desiredPath, bool isAbsolute) { 349 | filesystem::path newName = from->path.generic_string() + ".new"; 350 | { 351 | streams::ifstream in(from->path); 352 | streams::ofstream out(newName.generic_string().c_str()); 353 | while (in.good()) { 354 | bool isReplacement = false; 355 | std::string line; 356 | std::getline(in, line); 357 | const char* l = strstr(line.c_str(), "#"); 358 | if (l && (l = strstr(l, "include"))) { 359 | const char* start = strstr(l, "<"); 360 | const char* end; 361 | if (start) { 362 | end = strstr(start + 1, ">"); 363 | } else { 364 | start = strstr(l, "\""); 365 | end = start ? strstr(start+1, "\"") : nullptr; 366 | } 367 | if (start && end) { 368 | std::string includePath(start+1, end); 369 | std::string lowerPath; 370 | std::transform(includePath.begin(), includePath.end(), std::back_inserter(lowerPath), ::tolower); 371 | std::string postLookup = includeLookup[lowerPath]; 372 | if (!postLookup.empty() && postLookup != "INVALID" && files.find(postLookup) != files.end()) { 373 | File* f = &files.find(postLookup)->second; 374 | std::string path = f->path.generic_string(); 375 | std::string pathToStrip = (isAbsolute ? "." : comp->root.generic_string()) + "/"; 376 | if (desiredPath != ".") pathToStrip += desiredPath + "/"; 377 | std::string newInclude = path.substr(pathToStrip.size()); 378 | std::string componentPath = comp->root.generic_string(); 379 | // Don't make an include ambiguous by doing this change 380 | if (includeLookup[newInclude] != "INVALID" && path.compare(0, componentPath.size(), componentPath) == 0 && path.compare(0, pathToStrip.size(), pathToStrip) == 0) { 381 | isReplacement = true; 382 | if (from->component == f->component) { 383 | out << "#include \"" + newInclude + "\"\n"; 384 | } else { 385 | out << "#include <" + newInclude + ">\n"; 386 | } 387 | } 388 | } 389 | } 390 | } 391 | if (!isReplacement) { 392 | out << line << '\n'; 393 | } 394 | } 395 | } 396 | filesystem::rename(newName, from->path); 397 | } 398 | 399 | void UpdateIncludes(std::unordered_map& files, std::unordered_map &includeLookup, Component* component, const std::string& desiredPath, bool isAbsolute) { 400 | for (auto& p : files) { 401 | for (auto& d : p.second.dependencies) { 402 | if (component->files.find(d) != component->files.end()) { 403 | UpdateIncludeFor(files, includeLookup, &p.second, component, desiredPath, isAbsolute); 404 | std::cout << p.second.path.generic_string() << "\n"; 405 | break; 406 | } 407 | } 408 | } 409 | } 410 | 411 | 412 | -------------------------------------------------------------------------------- /src/Output.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012-2016. TomTom International BV (http://tomtom.com). 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef __DEP_CHECKER__OUTPUT_H 18 | #define __DEP_CHECKER__OUTPUT_H 19 | 20 | #include "FilesystemInclude.h" 21 | #include 22 | #include 23 | #include 24 | 25 | struct Component; 26 | 27 | void OutputFlatDependencies(const Configuration& config, std::unordered_map &components, 28 | const filesystem::path &outfile); 29 | void OutputCircularDependencies(const Configuration& config, std::unordered_map &components, 30 | const filesystem::path &outfile); 31 | void PrintGraphOnTarget(const Configuration& config, const filesystem::path &outfile, Component *c); 32 | void PrintAllComponents(std::unordered_map &components, 33 | const char* description, 34 | std::function); 35 | void PrintAllFiles(std::unordered_map& files, const char* description, std::function); 36 | void FindAndPrintCycleFrom(Component *origin, Component *c, std::unordered_set alreadyHad, 37 | std::vector order); 38 | void PrintCyclesForTarget(Component *c); 39 | void PrintLinksForTarget(Component *c); 40 | void PrintInfoOnTarget(Component *c); 41 | void FindSpecificLink(const Configuration& config, std::unordered_map& files, Component *from, Component *to); 42 | void UpdateIncludes(std::unordered_map& files, std::unordered_map &includeLookup, Component* component, const std::string& desiredPath, bool isAbsolute); 43 | 44 | #endif 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/generated.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012-2016. TomTom International BV (http://tomtom.com). 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "Component.h" 18 | 19 | void CreateIncludeLookupTable(std::unordered_map& files, 20 | std::unordered_map &includeLookup, 21 | std::map> &collisions) { 22 | for (auto &p : files) { 23 | std::string lowercasePath; 24 | std::transform(p.first.begin(), p.first.end(), std::back_inserter(lowercasePath), ::tolower); 25 | const char *pa = lowercasePath.c_str(); 26 | while ((pa = strstr(pa + 1, "/"))) { 27 | std::string &ref = includeLookup[pa + 1]; 28 | if (ref.size() == 0) { 29 | ref = p.first; 30 | } else { 31 | collisions[pa + 1].insert(p.first); 32 | if (ref != "INVALID") { 33 | collisions[pa + 1].insert(ref); 34 | } 35 | ref = "INVALID"; 36 | } 37 | } 38 | } 39 | } 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012-2016. TomTom International BV (http://tomtom.com). 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "Analysis.h" 18 | #include "CmakeRegen.h" 19 | #include "Component.h" 20 | #include "Configuration.h" 21 | #include "Constants.h" 22 | #include "FilesystemInclude.h" 23 | #include "FstreamInclude.h" 24 | #include "Input.h" 25 | #include "Output.h" 26 | #include 27 | 28 | static bool CheckVersionFile(const Configuration& config) { 29 | const std::string currentVersion = CURRENT_VERSION; 30 | return currentVersion == config.versionUsed; 31 | } 32 | 33 | static std::string targetFrom(const std::string &arg) { 34 | if (arg == "ROOT") { 35 | return "."; 36 | } 37 | std::string target = arg; 38 | if (!target.empty() && target.back() == '/') { 39 | target.erase(target.end() - 1); 40 | } 41 | std::replace(target.begin(), target.end(), '.', '/'); 42 | return "./" + target; 43 | } 44 | 45 | class Operations { 46 | public: 47 | Operations(int argc, const char** argv) 48 | : loadStatus(Unloaded) 49 | , inferredComponents(false) 50 | , programName(argv[0]) 51 | , allArgs(argv+1, argv+argc) 52 | , recursive(false) 53 | { 54 | if (filesystem::is_regular_file(CONFIG_FILE)) { 55 | streams::ifstream in(CONFIG_FILE); 56 | config.read(in); 57 | } 58 | RegisterCommands(); 59 | projectRoot = outputRoot = filesystem::current_path(); 60 | } 61 | void RunCommands() { 62 | if (allArgs.empty()) { 63 | allArgs.push_back("--help"); 64 | } 65 | std::vector::iterator it = allArgs.begin(), end = allArgs.end(); 66 | while (it != end) { 67 | std::vector::iterator localEnd = it; 68 | localEnd++; 69 | while (localEnd != end && ((*localEnd)[0] != '-' || (*localEnd)[1] != '-')) localEnd++; 70 | RunCommand(it, localEnd); 71 | it = localEnd; 72 | } 73 | if (lastCommandDidNothing) { 74 | std::cout << "\nThe last command you entered did not result in output, so in effect it did nothing.\n"; 75 | std::cout << "Remember that commands are executed in the order in which they appear on the command-line, so\n"; 76 | std::cout << "if you want an analysis-changing command (such as --infer) to change the analysis, it has to\n"; 77 | std::cout << "come before the analysis you want it to affect.\n\n"; 78 | std::cout << "You can also use this to run an analysis multiple times with a single change between them, or\n"; 79 | std::cout << "to get various outputs from a single analysis run.\n"; 80 | } 81 | } 82 | private: 83 | typedef void (Operations::*Command)(std::vector); 84 | void RegisterCommands() { 85 | commands["--ambiguous"] = &Operations::Ambiguous; 86 | commands["--cycles"] = &Operations::Cycles; 87 | commands["--dir"] = &Operations::Dir; 88 | commands["--drop"] = &Operations::Drop; 89 | commands["--dryregen"] = &Operations::DryRegen; 90 | commands["--fixincludes"] = &Operations::FixIncludes; 91 | commands["--graph-cycles"] = &Operations::GraphCycles; 92 | commands["--graph"] = &Operations::Graph; 93 | commands["--graph-target"] = &Operations::GraphTarget; 94 | commands["--help"] = &Operations::Help; 95 | commands["--ignore"] = &Operations::Ignore; 96 | commands["--includesize"] = &Operations::IncludeSize; 97 | commands["--infer"] = &Operations::Infer; 98 | commands["--info"] = &Operations::Info; 99 | commands["--inout"] = &Operations::InOut; 100 | commands["--outliers"] = &Operations::Outliers; 101 | commands["--recursive"] = &Operations::Recursive; 102 | commands["--regen"] = &Operations::Regen; 103 | commands["--shortest"] = &Operations::Shortest; 104 | commands["--stats"] = &Operations::Stats; 105 | commands["--usedby"] = &Operations::UsedBy; 106 | commands["--includeorigin"] = &Operations::IncludeOrigin; 107 | } 108 | void RunCommand(std::vector::iterator &arg, std::vector::iterator &end) { 109 | std::string lowerCommand; 110 | std::transform(arg->begin(), arg->end(), std::back_inserter(lowerCommand), ::tolower); 111 | ++arg; 112 | 113 | Command c = commands[lowerCommand]; 114 | if (!c) c = commands["--help"]; 115 | (this->*c)(std::vector(arg, end)); 116 | } 117 | void LoadProject(bool withLoc = false) { 118 | if (!withLoc && loadStatus >= FastLoad) return; 119 | if (withLoc && loadStatus >= FullLoad) return; 120 | LoadFileList(config, components, files, projectRoot, inferredComponents, withLoc); 121 | CreateIncludeLookupTable(files, includeLookup, collisions); 122 | MapFilesToComponents(components, files); 123 | ForgetEmptyComponents(components); 124 | if (components.size() < 3) { 125 | std::cout << "Warning: Analyzing your project resulted in a very low amount of components. This either points to a small project, or\n"; 126 | std::cout << "to cpp-dependencies not recognizing the components.\n\n"; 127 | 128 | std::cout << "It tries to recognize components by the existence of project build files - CMakeLists.txt, Makefiles, MyProject.vcxproj\n"; 129 | std::cout << "or similar files. If it does not recognize any such files, it will assume everything belongs to the project it is\n"; 130 | std::cout << "contained in. You can invert this behaviour to assume that any code file will belong to a component local to it - in\n"; 131 | std::cout << "effect, making every folder of code a single component - by using the --infer option.\n\n"; 132 | 133 | std::cout << "Another reason for this warning may be running the tool in a folder that doesn't have any code. You can either change\n"; 134 | std::cout << "to the desired directory, or use the --dir option to make it analyze another directory.\n\n"; 135 | } 136 | MapIncludesToDependencies(includeLookup, ambiguous, components, files); 137 | for (auto &i : ambiguous) { 138 | for (auto &c : collisions[i.first]) { 139 | files.find(c)->second.hasInclude = true; // There is at least one include that might end up here. 140 | } 141 | } 142 | PropagateExternalIncludes(files); 143 | ExtractPublicDependencies(components); 144 | FindCircularDependencies(components); 145 | for (auto& c : deleteComponents) { 146 | KillComponent(components, c); 147 | } 148 | loadStatus = (withLoc ? FullLoad : FastLoad); 149 | lastCommandDidNothing = false; 150 | } 151 | void UnloadProject() { 152 | components.clear(); 153 | files.clear(); 154 | collisions.clear(); 155 | includeLookup.clear(); 156 | ambiguous.clear(); 157 | loadStatus = Unloaded; 158 | lastCommandDidNothing = true; 159 | } 160 | void Dir(std::vector args) { 161 | if (args.empty()) { 162 | std::cout << "No directory specified after --dir\n"; 163 | } else { 164 | projectRoot = args[0]; 165 | } 166 | UnloadProject(); 167 | } 168 | void Ignore(std::vector args) { 169 | if (args.empty()) 170 | std::cout << "No files specified to ignore?\n"; 171 | for (auto& s : args) config.blacklist.insert(s); 172 | UnloadProject(); 173 | } 174 | void Infer(std::vector ) { 175 | inferredComponents = true; 176 | UnloadProject(); 177 | } 178 | void Drop(std::vector args) { 179 | if (args.empty()) 180 | std::cout << "No files specified to ignore?\n"; 181 | for (auto& s : args) deleteComponents.insert(s); 182 | UnloadProject(); 183 | } 184 | void Graph(std::vector args) { 185 | LoadProject(); 186 | if (args.empty()) { 187 | std::cout << "No output file specified for graph\n"; 188 | } else { 189 | OutputFlatDependencies(config, components, args[0]); 190 | } 191 | } 192 | void GraphCycles(std::vector args) { 193 | LoadProject(); 194 | if (args.empty()) { 195 | std::cout << "No output file specified for cycle graph\n"; 196 | } else { 197 | OutputCircularDependencies(config, components, args[0]); 198 | } 199 | } 200 | void GraphTarget(std::vector args) { 201 | LoadProject(); 202 | if (args.size() != 2) { 203 | std::cout << "--graph-target requires a single component and a single output file name.\n"; 204 | } else { 205 | PrintGraphOnTarget(config, args[1], components[targetFrom(args[0])]); 206 | } 207 | } 208 | void Cycles(std::vector args) { 209 | LoadProject(); 210 | if (args.empty()) { 211 | std::cout << "No targets specified for finding in- and out-going links.\n"; 212 | } else { 213 | PrintCyclesForTarget(components[targetFrom(args[0])]); 214 | } 215 | } 216 | void Stats(std::vector) { 217 | LoadProject(true); 218 | std::size_t totalPublicLinks(0), totalPrivateLinks(0); 219 | for (const auto &c : components) { 220 | totalPublicLinks += c.second->pubDeps.size(); 221 | totalPrivateLinks += c.second->privDeps.size(); 222 | } 223 | std::cout << components.size() << " components with " 224 | << totalPublicLinks << " public dependencies, " 225 | << totalPrivateLinks << " private dependencies\n"; 226 | std::cout << "Detected " << NodesWithCycles(components) << " nodes in cycles\n"; 227 | } 228 | void InOut(std::vector args) { 229 | LoadProject(); 230 | if (args.empty()) 231 | std::cout << "No targets specified for finding in- and out-going links.\n"; 232 | for (auto& s : args) { 233 | PrintLinksForTarget(components[targetFrom(s)]); 234 | } 235 | } 236 | void Shortest(std::vector args) { 237 | LoadProject(); 238 | if (args.size() != 2) { 239 | std::cout << "Need two arguments to find the shortest path from one to the other\n"; 240 | return; 241 | } 242 | Component* from = components[targetFrom(args[0])], 243 | * to = components[targetFrom(args[1])]; 244 | if (!from) { 245 | std::cout << "No such component " << args[0] << "\n"; 246 | } else if (!to) { 247 | std::cout << "No such component " << args[1] << "\n"; 248 | } else { 249 | FindSpecificLink(config, files, from, to); 250 | } 251 | } 252 | void Info(std::vector args) { 253 | LoadProject(); 254 | if (args.empty()) 255 | std::cout << "No targets specified to print info on...\n"; 256 | for (auto& s : args) { 257 | PrintInfoOnTarget(components[targetFrom(s)]); 258 | } 259 | } 260 | void UsedBy(std::vector args) { 261 | LoadProject(); 262 | if (args.empty()) 263 | std::cout << "No files specified to find usage of...\n"; 264 | for (auto& s : args) { 265 | std::cout << "File " << s << " is used by:\n"; 266 | File* f = &files.find("./" + s)->second; 267 | for (auto &p : files) { 268 | if (p.second.dependencies.find(f) != p.second.dependencies.end()) { 269 | std::cout << " " << p.second.path.string() << "\n"; 270 | } 271 | } 272 | } 273 | } 274 | void IncludeOrigin(std::vector args) { 275 | LoadProject(); 276 | if (args.size() != 2) { 277 | std::cout << "IncludeOrigin requires origin file and include to find.\n"; 278 | return; 279 | } 280 | std::unordered_map localReverseIncludeMapping; 281 | std::vector todo; 282 | if (files.find("./" + args[0]) == files.end()) { 283 | std::cout << "Cannot find target file\n"; 284 | return; 285 | } 286 | if (files.find("./" + args[1]) == files.end()) { 287 | std::cout << "Cannot find source file\n"; 288 | return; 289 | } 290 | todo.push_back(&files.find("./" + args[0])->second); 291 | 292 | // Build mapping of all includes reached from this file, and the nearer file that includes it 293 | while (!todo.empty()) { 294 | File* current = todo.back(); 295 | todo.pop_back(); 296 | for (auto& dep : current->dependencies) { 297 | if (localReverseIncludeMapping.find(dep) != localReverseIncludeMapping.end()) continue; 298 | localReverseIncludeMapping[dep] = current; 299 | todo.push_back(dep); 300 | } 301 | } 302 | 303 | // Now the target file has to be in the mapping. Find the file, and then print the path until we get to the root file. 304 | File* target = &files.find("./" + args[1])->second; 305 | if (localReverseIncludeMapping.find(target) == localReverseIncludeMapping.end()) { 306 | std::cout << args[0] << " does not include " << args[1] << "\n"; 307 | } else { 308 | while (target) { 309 | std::cout << target->path.string() << "\n"; 310 | target = localReverseIncludeMapping[target]; 311 | } 312 | } 313 | } 314 | void DoActualRegen(std::vector args, bool dryRun) { 315 | LoadProject(); 316 | filesystem::current_path(projectRoot); 317 | if (args.empty()) { 318 | for (auto &c : components) { 319 | RegenerateCmakeFilesForComponent(config, c.second, dryRun, false); 320 | } 321 | } else { 322 | bool writeToStdoutInstead = false; 323 | if (args[0] == "-") { 324 | dryRun = true; // Can't rewrite actual CMakeFiles if you asked them to be sent to stdout. 325 | writeToStdoutInstead = true; 326 | args.erase(args.begin()); 327 | } 328 | for (auto& s : args) { 329 | const std::string target = targetFrom(s); 330 | if (recursive) { 331 | for (auto& c : components) { 332 | if (strstr(c.first.c_str(), target.c_str())) { 333 | RegenerateCmakeFilesForComponent(config, c.second, dryRun, writeToStdoutInstead); 334 | } 335 | } 336 | } else { 337 | if (components.find(target) != components.end()) { 338 | RegenerateCmakeFilesForComponent(config, components[target], dryRun, writeToStdoutInstead); 339 | } else { 340 | std::cout << "Target '" << target << "' not found\n"; 341 | } 342 | } 343 | } 344 | } 345 | } 346 | void Regen(std::vector args) { 347 | bool versionIsCorrect = CheckVersionFile(config); 348 | if (!versionIsCorrect) 349 | std::cout << "Version of dependency checker not the same as the one used to generate the existing cmakelists. Refusing to regen\n" 350 | "Please update " CONFIG_FILE " to version \"" CURRENT_VERSION "\" if you really want to do this.\n"; 351 | 352 | DoActualRegen(args, !versionIsCorrect); 353 | } 354 | void DryRegen(std::vector args) { 355 | DoActualRegen(args, true); 356 | } 357 | void FixIncludes(std::vector args) { 358 | if (args.size() < 2 || args.size() > 3) { 359 | std::cout << "Invalid input to fixincludes command\n"; 360 | std::cout << "Required: --fixincludes []\n"; 361 | std::cout << "Relative root can be \"project\" for absolute paths or \"component\" for component-relative paths\n"; 362 | return; 363 | } 364 | 365 | LoadProject(); 366 | bool absolute = args.size() == 3 && args[2] == "project"; 367 | Component* c = components[targetFrom(args[0])]; 368 | if (!c) { 369 | std::cout << "No such component " << args[0] << "\n"; 370 | } else { 371 | UpdateIncludes(files, includeLookup, c, args[1], absolute); 372 | } 373 | } 374 | void Outliers(std::vector) { 375 | LoadProject(true); 376 | PrintAllComponents(components, "Libraries with no links in:", [this](const Component& c){ 377 | return config.addLibraryAliases.count(c.type) == 1 && 378 | !c.files.empty() && 379 | c.pubLinks.empty() && c.privLinks.empty(); 380 | }); 381 | PrintAllComponents(components, "Libraries with too many outward links:", [this](const Component& c){ return c.pubDeps.size() + c.privDeps.size() > config.componentLinkLimit; }); 382 | PrintAllComponents(components, "Libraries with too few lines of code:", [this](const Component& c) { return !c.files.empty() && c.loc() < config.componentLocLowerLimit; }); 383 | PrintAllComponents(components, "Libraries with too many lines of code:", [this](const Component& c) { return c.loc() > config.componentLocUpperLimit; }); 384 | FindCircularDependencies(components); 385 | PrintAllComponents(components, "Libraries that are part of a cycle:", [](const Component& c) { return !c.circulars.empty(); }); 386 | PrintAllFiles(files, "Files that are never used:", [](const File& f) { return !IsCompileableFile(f.path.extension().string()) && !f.hasInclude; }); 387 | PrintAllFiles(files, "Files with too many lines of code:", [this](const File& f) { return f.loc > config.fileLocUpperLimit; }); 388 | } 389 | void IncludeSize(std::vector) { 390 | LoadProject(true); 391 | std::unordered_map includeCount; 392 | for (auto& f : files) { 393 | if (f.second.hasInclude) continue; 394 | std::set filesIncluded; 395 | std::vector todo; 396 | todo.push_back(&f.second); 397 | while (!todo.empty()) { 398 | File* file_todo = todo.back(); 399 | todo.pop_back(); 400 | for (auto& d : file_todo->dependencies) { 401 | if (filesIncluded.insert(d).second) todo.push_back(d); 402 | } 403 | } 404 | 405 | size_t total = 0; 406 | for (auto& i : filesIncluded) { 407 | total += i->loc; 408 | i->includeCount++; 409 | } 410 | } 411 | struct entry { 412 | std::string path; 413 | size_t includecount, loc; 414 | size_t impact; 415 | entry(const std::string& path, size_t includecount, size_t loc) 416 | : path(path) 417 | , includecount(includecount) 418 | , loc(loc) 419 | { 420 | impact = includecount * loc; 421 | } 422 | bool operator<(const entry& other) const { 423 | return impact > other.impact; 424 | } 425 | }; 426 | std::vector entries; 427 | for (auto& f : files) { 428 | if (!f.second.hasInclude) continue; 429 | std::set filesIncluded; 430 | std::vector todo; 431 | todo.push_back(&f.second); 432 | while (!todo.empty()) { 433 | File* todo_file = todo.back(); 434 | todo.pop_back(); 435 | for (auto& d : todo_file->dependencies) { 436 | if (filesIncluded.insert(d).second) todo.push_back(d); 437 | } 438 | } 439 | size_t total = 0; 440 | for (auto& i : filesIncluded) total += i->loc; 441 | if (f.second.includeCount > 0 && total > 0) { 442 | entries.push_back(entry(f.second.path.string(), f.second.includeCount, total)); 443 | } 444 | } 445 | std::sort(entries.begin(), entries.end()); 446 | for (auto& entry : entries) { 447 | std::cout << "impact=" << entry.impact << " LOC=" << entry.loc << " count=" << entry.includecount << " name=" << entry.path << "\n"; 448 | } 449 | } 450 | void Ambiguous(std::vector) { 451 | LoadProject(); 452 | std::cout << "Found " << ambiguous.size() << " ambiguous includes\n\n"; 453 | for (auto &i : ambiguous) { 454 | std::cout << "Include for " << i.first << "\nFound in : \n"; 455 | for (auto &s : i.second) { 456 | std::cout << " included from " << s << "\n"; 457 | } 458 | std::cout << "Options for file:\n"; 459 | for (auto &c : collisions[i.first]) { 460 | std::cout << " " << c << "\n"; 461 | } 462 | std::cout << "\n"; 463 | } 464 | } 465 | void Recursive(std::vector) { 466 | recursive = true; 467 | UnloadProject(); 468 | } 469 | void Help(std::vector) { 470 | std::cout << "C++ Dependencies -- a tool to analyze large C++ code bases for #include dependency information\n"; 471 | std::cout << "Copyright (C) 2016, TomTom International BV\n"; 472 | std::cout << "Version " CURRENT_VERSION "\n"; 473 | std::cout << "\n"; 474 | std::cout << " Usage:\n"; 475 | std::cout << " " << programName << " [--dir ] \n"; 476 | std::cout << " Source directory is assumed to be the current one if unspecified\n"; 477 | std::cout << "\n"; 478 | std::cout << " Commands:\n"; 479 | std::cout << " --help : Produce this help text\n"; 480 | std::cout << "\n"; 481 | std::cout << " Extracting graphs:\n"; 482 | std::cout << " --graph : Graph of all components with dependencies\n"; 483 | std::cout << " --graph-cycles : Graph of components with cyclic dependencies on other components\n"; 484 | std::cout << " --graph-target : Graph for all dependencies of a specific target\n"; 485 | std::cout << "\n"; 486 | std::cout << " Getting information:\n"; 487 | std::cout << " --stats : Info about code base size, complexity and cyclic dependency count\n"; 488 | std::cout << " --cycles : Find all possible paths from this target back to itself\n"; 489 | std::cout << " --shortest : Determine shortest path between components and its reason\n"; 490 | std::cout << " --outliers : Finds all components and files that match a criterium for being out of the ordinary\n"; 491 | std::cout << " - libraries that are not used\n"; 492 | std::cout << " - components that use a lot of other components\n"; 493 | std::cout << " - components with dependencies towards executables\n"; 494 | std::cout << " - components with less than 200 LoC\n"; 495 | std::cout << " - components with more than 20 kLoC\n"; 496 | std::cout << " - components that are part of a cycle\n"; 497 | std::cout << " - files that are more than 2000 LoC\n"; 498 | std::cout << " - files that are not compiled and never included\n"; 499 | std::cout << " --includesize : Calculate the total number of lines added to each file through #include\n"; 500 | std::cout << "\n"; 501 | std::cout << " Target information:\n"; 502 | std::cout << " --info : Show all information on a given specific target\n"; 503 | std::cout << " --usedby : Find all references to a specific header file\n"; 504 | std::cout << " --includeorigin : Find a path from a given source file to a given header file. Both should be full paths\n"; 505 | std::cout << " --inout : Find all incoming and outgoing links for a target\n"; 506 | std::cout << " --ambiguous : Find all include statements that could refer to more than one header\n"; 507 | std::cout << "\n"; 508 | std::cout << " Refactoring:\n"; 509 | std::cout << " --fixincludes []\n"; 510 | std::cout << " : Unify include paths for headers in in all source files.\n"; 511 | std::cout << " can be:\n"; 512 | std::cout << " - \"project\" for absolute paths (default);\n"; 513 | std::cout << " - \"component\" for component-relative paths.\n"; 514 | std::cout << "\n"; 515 | std::cout << " Automatic CMakeLists.txt generation:\n"; 516 | std::cout << " Note: These commands only have any effect on CMakeLists.txt marked with \"" << config.regenTag << "\"\n"; 517 | std::cout << " --regen : Re-generate all marked CMakeLists.txt with the component information derived.\n"; 518 | std::cout << " --dryregen : Verify which CMakeLists would be regenerated if you were to run --regen now.\n"; 519 | std::cout << "\n"; 520 | std::cout << " What-if analysis:\n"; 521 | std::cout << " Note: These commands modify the analysis results and are intended for interactive analysis.\n"; 522 | std::cout << " They only affect the commands after their own position in the argument list. You can use them to\n"; 523 | std::cout << " analyze twice with a given what-if in between. For example: --stats --ignore myFile.h --stats\n"; 524 | std::cout << " --ignore : Ignore the following path(s) or file names in the analysis.\n"; 525 | std::cout << " --drop : Completely remove knowledge of a given component and re-analyze dependencies. Differs\n"; 526 | std::cout << " from ignoring the component as it will not disambiguate headers that are ambiguous\n"; 527 | std::cout << " because of this component\n"; 528 | std::cout << " --infer : Pretend that every folder that holds a source file is also a component.\n"; 529 | std::cout << " --dir : Source directory to run in. Assumed current one if unspecified.\n"; 530 | std::cout << " --recursive : If for the following command a single target/directory is specified\n"; 531 | std::cout << " recursively process the underlying targets/directories too.\n"; 532 | } 533 | Configuration config; 534 | enum LoadStatus { 535 | Unloaded, 536 | FastLoad, 537 | FullLoad, 538 | } loadStatus; 539 | bool inferredComponents; 540 | bool lastCommandDidNothing; 541 | std::string programName; 542 | std::map commands; 543 | std::vector allArgs; 544 | std::unordered_map components; 545 | std::unordered_map files; 546 | std::map> collisions; 547 | std::unordered_map includeLookup; 548 | std::map> ambiguous; 549 | std::set deleteComponents; 550 | filesystem::path outputRoot, projectRoot; 551 | bool recursive; 552 | }; 553 | 554 | int main(int argc, const char **argv) { 555 | Operations op(argc, argv); 556 | op.RunCommands(); 557 | return 0; 558 | } 559 | -------------------------------------------------------------------------------- /test/AnalysisCircularDependencies.cpp: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | #include "Analysis.h" 3 | 4 | 5 | TEST(FindCircularDependenciesFindsNothingInADisconnectedGraph) { 6 | std::unordered_map components; 7 | for (int n = 0; n < 10; n++) { 8 | char compName[12]; 9 | sprintf(compName, "%d", n); 10 | components[compName] = new Component(compName); 11 | } 12 | 13 | FindCircularDependencies(components); 14 | for (auto& p : components) { 15 | ASSERT(p.second->circulars.empty()); 16 | } 17 | } 18 | 19 | TEST(FindCircularDependenciesFindsNothingInAHeavilyConnectedTree) { 20 | std::unordered_map components; 21 | std::vector allSoFar; 22 | for (int n = 0; n < 10; n++) { 23 | char compName[12]; 24 | sprintf(compName, "%d", n); 25 | Component* thisOne = components[compName] = new Component(compName); 26 | for (auto& c : allSoFar) { 27 | thisOne->pubDeps.insert(c); 28 | } 29 | allSoFar.push_back(thisOne); 30 | } 31 | 32 | FindCircularDependencies(components); 33 | for (auto& p : components) { 34 | ASSERT(p.second->circulars.empty()); 35 | } 36 | } 37 | 38 | TEST(FindCircularDependenciesFindsASimpleCycle) { 39 | std::unordered_map components; 40 | Component* a = components["a"] = new Component("a"); 41 | Component* b = components["b"] = new Component("b"); 42 | a->pubDeps.insert(b); 43 | b->pubDeps.insert(a); 44 | 45 | FindCircularDependencies(components); 46 | 47 | ASSERT(a->circulars.size() == 1); 48 | ASSERT(*a->circulars.begin() == b); 49 | ASSERT(b->circulars.size() == 1); 50 | ASSERT(*b->circulars.begin() == a); 51 | } 52 | 53 | TEST(FindCircularDependenciesSeesLargeCycle) { 54 | std::unordered_map components; 55 | Component* a = components["a"] = new Component("a"); 56 | Component* b = components["b"] = new Component("b"); 57 | Component* c = components["c"] = new Component("c"); 58 | Component* d = components["d"] = new Component("d"); 59 | Component* e = components["e"] = new Component("e"); 60 | a->pubDeps.insert(b); 61 | b->privDeps.insert(c); 62 | c->pubDeps.insert(d); 63 | d->privDeps.insert(e); 64 | e->pubDeps.insert(a); 65 | 66 | FindCircularDependencies(components); 67 | 68 | ASSERT(NodesWithCycles(components) == 5); 69 | ASSERT(a->circulars.size() == 1); 70 | ASSERT(*a->circulars.begin() == b); 71 | ASSERT(b->circulars.size() == 1); 72 | ASSERT(*b->circulars.begin() == c); 73 | ASSERT(c->circulars.size() == 1); 74 | ASSERT(*c->circulars.begin() == d); 75 | ASSERT(d->circulars.size() == 1); 76 | ASSERT(*d->circulars.begin() == e); 77 | ASSERT(e->circulars.size() == 1); 78 | ASSERT(*e->circulars.begin() == a); 79 | } 80 | 81 | TEST(FindCircularDependenciesSeesNoCycleAfterDroppingOneComponent) { 82 | std::unordered_map components; 83 | Component* a = components["a"] = new Component("a"); 84 | Component* b = components["b"] = new Component("b"); 85 | Component* c = components["c"] = new Component("c"); 86 | Component* d = components["d"] = new Component("d"); 87 | Component* e = components["e"] = new Component("e"); 88 | a->pubDeps.insert(b); 89 | b->privDeps.insert(c); 90 | c->pubDeps.insert(d); 91 | d->privDeps.insert(e); 92 | e->pubDeps.insert(a); 93 | 94 | FindCircularDependencies(components); 95 | KillComponent(components, "a"); 96 | ASSERT(NodesWithCycles(components) == 0); 97 | } 98 | 99 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(unittests 2 | AnalysisCircularDependencies.cpp 3 | CmakeRegenTest.cpp 4 | ConfigurationTest.cpp 5 | InputTest.cpp 6 | test.cpp 7 | ) 8 | target_link_libraries(unittests 9 | PRIVATE 10 | cpp_dependencies_lib 11 | ) 12 | target_compile_definitions(unittests 13 | PRIVATE 14 | _CRT_SECURE_NO_WARNINGS 15 | ) 16 | 17 | if (NOT MSVC AND BUILD_COVERAGE) 18 | set_property(TARGET unittests APPEND PROPERTY LINK_FLAGS --coverage) 19 | endif () 20 | add_test(NAME unittests COMMAND unittests) 21 | -------------------------------------------------------------------------------- /test/CmakeRegenTest.cpp: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | #include "TestUtils.h" 3 | 4 | #include 5 | #include 6 | 7 | #include "CmakeRegen.h" 8 | #include "Component.h" 9 | #include "Configuration.h" 10 | #include "FstreamInclude.h" 11 | 12 | TEST(MakeCmakeComment_Oneline) { 13 | const std::string contents("A oneline comment"); 14 | const std::string expectedComment("# A oneline comment\n"); 15 | 16 | std::string comment; 17 | MakeCmakeComment(comment, contents); 18 | ASSERT(comment == expectedComment); 19 | } 20 | 21 | TEST(MakeCmakeComment_Multiline) { 22 | const std::string contents("Multiple\n" 23 | "lines\n" 24 | "in\n" 25 | "comment"); 26 | const std::string expectedComment("# Multiple\n" 27 | "# lines\n" 28 | "# in\n" 29 | "# comment\n"); 30 | 31 | std::string comment; 32 | MakeCmakeComment(comment, contents); 33 | ASSERT(comment == expectedComment); 34 | } 35 | 36 | TEST(MakeCmakeComment_Multiline_LastWithEndLine) { 37 | const std::string contents("Multiple\n" 38 | "lines\n" 39 | "in\n" 40 | "comment\n"); 41 | const std::string expectedComment("# Multiple\n" 42 | "# lines\n" 43 | "# in\n" 44 | "# comment\n"); 45 | 46 | std::string comment; 47 | MakeCmakeComment(comment, contents); 48 | ASSERT(comment == expectedComment); 49 | } 50 | 51 | TEST(RegenerateCmakeHeader) { 52 | Configuration config; 53 | config.companyName = "My Company"; 54 | config.licenseString = "My\nmultiline\nlicense."; 55 | config.regenTag = "AUTOGENERATED"; 56 | 57 | const std::string expectedOutput( 58 | "#\n" 59 | "# Copyright (c) My Company. All rights reserved.\n" 60 | "# My\n" 61 | "# multiline\n" 62 | "# license.\n" 63 | "#\n" 64 | "\n" 65 | "# AUTOGENERATED - do not edit, your changes will be lost\n" 66 | "# If you must edit, remove these two lines to avoid regeneration\n" 67 | "\n"); 68 | 69 | std::ostringstream oss; 70 | RegenerateCmakeHeader(oss, config); 71 | 72 | ASSERT(oss.str() == expectedOutput); 73 | } 74 | 75 | TEST(RegenerateCmakeAddDependencies) { 76 | Component comp("./MyComponent"); 77 | comp.buildAfters.insert("ComponentA"); 78 | comp.buildAfters.insert("ComponentB"); 79 | 80 | const std::string expectedOutput( 81 | "add_dependencies(${PROJECT_NAME}\n" 82 | " ComponentA\n" 83 | " ComponentB\n" 84 | ")\n" 85 | "\n"); 86 | 87 | std::ostringstream oss; 88 | RegenerateCmakeAddDependencies(oss, comp); 89 | 90 | ASSERT(oss.str() == expectedOutput); 91 | } 92 | 93 | TEST(RegenerateCmakeAddDependencies_Empty) { 94 | Component comp("./MyComponent"); 95 | 96 | std::ostringstream oss; 97 | RegenerateCmakeAddDependencies(oss, comp); 98 | 99 | ASSERT(oss.str().empty()); 100 | } 101 | 102 | TEST(RegenerateCmakeTargetIncludeDirectories_PublicPrivate) { 103 | bool isHeaderOnly = false; 104 | const std::set publicIncl{ 105 | "public_include_1", 106 | "public_include_2" 107 | }; 108 | const std::set privateIncl{ 109 | "private_include_1", 110 | "private_include_2" 111 | }; 112 | 113 | const std::string expectedOutput( 114 | "target_include_directories(${PROJECT_NAME}\n" 115 | " PUBLIC\n" 116 | " public_include_1\n" 117 | " public_include_2\n" 118 | " PRIVATE\n" 119 | " private_include_1\n" 120 | " private_include_2\n" 121 | ")\n" 122 | "\n"); 123 | 124 | std::ostringstream oss; 125 | RegenerateCmakeTargetIncludeDirectories(oss, publicIncl, privateIncl, isHeaderOnly); 126 | 127 | ASSERT(oss.str() == expectedOutput); 128 | } 129 | 130 | TEST(RegenerateCmakeTargetIncludeDirectories_Public) { 131 | bool isHeaderOnly = false; 132 | const std::set publicIncl{ 133 | "public_include_1", 134 | "public_include_2" 135 | }; 136 | const std::set privateIncl{}; 137 | const std::string expectedOutput( 138 | "target_include_directories(${PROJECT_NAME}\n" 139 | " PUBLIC\n" 140 | " public_include_1\n" 141 | " public_include_2\n" 142 | ")\n" 143 | "\n"); 144 | 145 | std::ostringstream oss; 146 | RegenerateCmakeTargetIncludeDirectories(oss, publicIncl, privateIncl, isHeaderOnly); 147 | 148 | ASSERT(oss.str() == expectedOutput); 149 | } 150 | 151 | TEST(RegenerateCmakeTargetIncludeDirectories_Private) { 152 | bool isHeaderOnly = false; 153 | const std::set publicIncl{}; 154 | const std::set privateIncl{ 155 | "private_include_1", 156 | "private_include_2" 157 | }; 158 | 159 | const std::string expectedOutput( 160 | "target_include_directories(${PROJECT_NAME}\n" 161 | " PRIVATE\n" 162 | " private_include_1\n" 163 | " private_include_2\n" 164 | ")\n" 165 | "\n"); 166 | 167 | std::ostringstream oss; 168 | RegenerateCmakeTargetIncludeDirectories(oss, publicIncl, privateIncl, isHeaderOnly); 169 | 170 | ASSERT(oss.str() == expectedOutput); 171 | } 172 | 173 | TEST(RegenerateCmakeTargetIncludeDirectories_Interface) { 174 | bool isHeaderOnly = true; 175 | const std::set publicIncl{ 176 | "public_include_1", 177 | "public_include_2" 178 | }; 179 | const std::set privateIncl{ 180 | "private_include_1", 181 | "private_include_2" 182 | }; 183 | 184 | const std::string expectedOutput( 185 | "target_include_directories(${PROJECT_NAME}\n" 186 | " INTERFACE\n" 187 | " public_include_1\n" 188 | " public_include_2\n" 189 | " private_include_1\n" 190 | " private_include_2\n" 191 | ")\n" 192 | "\n"); 193 | 194 | std::ostringstream oss; 195 | RegenerateCmakeTargetIncludeDirectories(oss, publicIncl, privateIncl, isHeaderOnly); 196 | 197 | ASSERT(oss.str() == expectedOutput); 198 | } 199 | 200 | TEST(RegenerateCmakeTargetLinkLibraries_PublicPrivate) { 201 | bool isHeaderOnly = false; 202 | const std::set publicDeps{ 203 | "ComponentA", 204 | "ComponentB" 205 | }; 206 | const std::set privateDeps{ 207 | "ComponentZ", 208 | "ComponentY" 209 | }; 210 | 211 | const std::string expectedOutput( 212 | "target_link_libraries(${PROJECT_NAME}\n" 213 | " PUBLIC\n" 214 | " ComponentA\n" 215 | " ComponentB\n" 216 | " PRIVATE\n" 217 | " ComponentY\n" 218 | " ComponentZ\n" 219 | ")\n" 220 | "\n"); 221 | 222 | std::ostringstream oss; 223 | RegenerateCmakeTargetLinkLibraries(oss, publicDeps, privateDeps, isHeaderOnly); 224 | 225 | ASSERT(oss.str() == expectedOutput); 226 | } 227 | 228 | TEST(RegenerateCmakeTargetLinkLibraries_Public) { 229 | bool isHeaderOnly = false; 230 | const std::set publicDeps{ 231 | "ComponentA", 232 | "ComponentB" 233 | }; 234 | const std::set privateDeps{}; 235 | 236 | const std::string expectedOutput( 237 | "target_link_libraries(${PROJECT_NAME}\n" 238 | " PUBLIC\n" 239 | " ComponentA\n" 240 | " ComponentB\n" 241 | ")\n" 242 | "\n"); 243 | 244 | std::ostringstream oss; 245 | RegenerateCmakeTargetLinkLibraries(oss, publicDeps, privateDeps, isHeaderOnly); 246 | 247 | ASSERT(oss.str() == expectedOutput); 248 | } 249 | 250 | TEST(RegenerateCmakeTargetLinkLibraries_Private) { 251 | bool isHeaderOnly = false; 252 | const std::set publicDeps{}; 253 | const std::set privateDeps{ 254 | "ComponentZ", 255 | "ComponentY" 256 | }; 257 | 258 | const std::string expectedOutput( 259 | "target_link_libraries(${PROJECT_NAME}\n" 260 | " PRIVATE\n" 261 | " ComponentY\n" 262 | " ComponentZ\n" 263 | ")\n" 264 | "\n"); 265 | 266 | std::ostringstream oss; 267 | RegenerateCmakeTargetLinkLibraries(oss, publicDeps, privateDeps, isHeaderOnly); 268 | 269 | ASSERT(oss.str() == expectedOutput); 270 | } 271 | 272 | TEST(RegenerateCmakeTargetLinkLibraries_Interface) { 273 | bool isHeaderOnly = true; 274 | const std::set publicDeps{ 275 | "ComponentA", 276 | "ComponentB" 277 | }; 278 | const std::set privateDeps{ 279 | "ComponentZ", 280 | "ComponentY" 281 | }; 282 | 283 | const std::string expectedOutput( 284 | "target_link_libraries(${PROJECT_NAME}\n" 285 | " INTERFACE\n" 286 | " ComponentA\n" 287 | " ComponentB\n" 288 | " ComponentY\n" 289 | " ComponentZ\n" 290 | ")\n" 291 | "\n"); 292 | 293 | std::ostringstream oss; 294 | RegenerateCmakeTargetLinkLibraries(oss, publicDeps, privateDeps, isHeaderOnly); 295 | 296 | ASSERT(oss.str() == expectedOutput); 297 | } 298 | 299 | TEST(RegenerateCmakeAddTarget_Interface) { 300 | Component comp("./MyComponent/"); 301 | comp.type = "add_library"; 302 | comp.additionalTargetParameters = " EXCLUDE_FROM_ALL\n"; 303 | 304 | Configuration config; 305 | 306 | bool isHeaderOnly = true; 307 | 308 | const std::list files{ 309 | "Analysis.cpp", 310 | "Analysis.h", 311 | "main.cpp" 312 | }; 313 | 314 | const std::string expectedOutput( 315 | "add_library(${PROJECT_NAME} INTERFACE\n" 316 | " EXCLUDE_FROM_ALL\n" 317 | ")\n" 318 | "\n"); 319 | 320 | std::ostringstream oss; 321 | RegenerateCmakeAddTarget(oss, config, comp, files, isHeaderOnly); 322 | 323 | ASSERT(oss.str() == expectedOutput); 324 | } 325 | 326 | TEST(RegenerateCmakeAddTarget_Library) { 327 | Component comp("./MyComponent/"); 328 | comp.type = "add_library"; 329 | comp.additionalTargetParameters = " EXCLUDE_FROM_ALL\n"; 330 | 331 | Configuration config; 332 | 333 | bool isHeaderOnly = false; 334 | 335 | const std::list files{ 336 | "Analysis.cpp", 337 | "Analysis.h", 338 | "main.cpp" 339 | }; 340 | 341 | const std::string expectedOutput( 342 | "add_library(${PROJECT_NAME} STATIC\n" 343 | " Analysis.cpp\n" 344 | " Analysis.h\n" 345 | " main.cpp\n" 346 | " EXCLUDE_FROM_ALL\n" 347 | ")\n" 348 | "\n"); 349 | 350 | std::ostringstream oss; 351 | RegenerateCmakeAddTarget(oss, config, comp, files, isHeaderOnly); 352 | 353 | ASSERT(oss.str() == expectedOutput); 354 | } 355 | 356 | TEST(RegenerateCmakeAddTarget_LibraryAlias) { 357 | Component comp("./MyComponent/"); 358 | comp.type = "add_library_alias"; 359 | comp.additionalTargetParameters = " EXCLUDE_FROM_ALL\n"; 360 | 361 | Configuration config; 362 | config.addLibraryAliases.insert("add_library_alias"); 363 | 364 | bool isHeaderOnly = false; 365 | 366 | const std::list files{ 367 | "Analysis.cpp", 368 | "Analysis.h", 369 | "main.cpp" 370 | }; 371 | 372 | const std::string expectedOutput( 373 | "add_library_alias(${PROJECT_NAME} STATIC\n" 374 | " Analysis.cpp\n" 375 | " Analysis.h\n" 376 | " main.cpp\n" 377 | " EXCLUDE_FROM_ALL\n" 378 | ")\n" 379 | "\n"); 380 | 381 | std::ostringstream oss; 382 | RegenerateCmakeAddTarget(oss, config, comp, files, isHeaderOnly); 383 | 384 | ASSERT(oss.str() == expectedOutput); 385 | } 386 | 387 | TEST(RegenerateCmakeAddTarget_Executable) { 388 | Component comp("./MyComponent/"); 389 | comp.type = "add_executable"; 390 | comp.additionalTargetParameters = " EXCLUDE_FROM_ALL\n"; 391 | 392 | Configuration config; 393 | 394 | bool isHeaderOnly = false; 395 | 396 | const std::list files{ 397 | "Analysis.cpp", 398 | "Analysis.h", 399 | "main.cpp" 400 | }; 401 | 402 | const std::string expectedOutput( 403 | "add_executable(${PROJECT_NAME}\n" 404 | " Analysis.cpp\n" 405 | " Analysis.h\n" 406 | " main.cpp\n" 407 | " EXCLUDE_FROM_ALL\n" 408 | ")\n" 409 | "\n"); 410 | 411 | std::ostringstream oss; 412 | RegenerateCmakeAddTarget(oss, config, comp, files, isHeaderOnly); 413 | 414 | ASSERT(oss.str() == expectedOutput); 415 | } 416 | 417 | TEST(RegenerateCmakeAddSubdirectory_NoSubDirs) { 418 | TemporaryWorkingDirectory workdir(name); 419 | filesystem::create_directories(workdir() / "MyComponent"); 420 | 421 | Component comp("./MyComponent/"); 422 | 423 | const std::string expectedOutput(""); 424 | 425 | std::ostringstream oss; 426 | RegenerateCmakeAddSubdirectory(oss, comp); 427 | 428 | ASSERT(oss.str() == expectedOutput); 429 | } 430 | 431 | TEST(RegenerateCmakeAddSubdirectory_SubDirsWithAndWithoutCmakeLists) { 432 | TemporaryWorkingDirectory workdir(name); 433 | filesystem::create_directories(workdir() / "MyComponent"); 434 | filesystem::create_directories(workdir() / "MyComponent" / "SubComponentA"); 435 | std::ofstream((workdir() / "MyComponent" / "SubComponentA" / "CMakeLists.txt").c_str()).close(); 436 | filesystem::create_directories(workdir() / "MyComponent" / "SubComponentB"); 437 | std::ofstream((workdir() / "MyComponent" / "SubComponentB" / "CMakeLists.txt").c_str()).close(); 438 | filesystem::create_directories(workdir() / "MyComponent" / "Data"); 439 | 440 | Component comp("./MyComponent/"); 441 | 442 | const std::string expectedOutput( 443 | "add_subdirectory(SubComponentA)\n" 444 | "add_subdirectory(SubComponentB)\n"); 445 | 446 | std::ostringstream oss; 447 | RegenerateCmakeAddSubdirectory(oss, comp); 448 | 449 | ASSERT(oss.str() == expectedOutput); 450 | } 451 | -------------------------------------------------------------------------------- /test/ConfigurationTest.cpp: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | #include "Constants.h" 3 | #include "Configuration.h" 4 | #include "FilesystemInclude.h" 5 | #include "FstreamInclude.h" 6 | 7 | // tmp 8 | #include 9 | 10 | TEST(DefaultConfiguration) 11 | { 12 | Configuration config; 13 | ASSERT(config.companyName == "YourCompany"); 14 | ASSERT(config.regenTag == "GENERATED BY CPP-DEPENDENCIES"); 15 | ASSERT(config.versionUsed == CURRENT_VERSION); 16 | ASSERT(config.cycleColor == "orange"); 17 | ASSERT(config.publicDepColor == "blue"); 18 | ASSERT(config.privateDepColor == "lightblue"); 19 | ASSERT(config.componentLinkLimit == 30); 20 | ASSERT(config.componentLocLowerLimit == 200); 21 | ASSERT(config.componentLocUpperLimit == 20000); 22 | ASSERT(config.fileLocUpperLimit == 2000); 23 | ASSERT(config.addLibraryAliases.size() == 1); 24 | ASSERT(config.addLibraryAliases.count("add_library") == 1); 25 | ASSERT(config.addExecutableAliases.size() == 1); 26 | ASSERT(config.addExecutableAliases.count("add_executable") == 1); 27 | } 28 | 29 | TEST(ReadConfigurationFile) 30 | { 31 | std::stringstream ss(CONFIG_FILE); 32 | ss << "versionUsed: 3\n" 33 | << "companyName: MyCompany\n" 34 | << "regenTag: MY_REGEN_TAG\n" 35 | << "cycleColor: brown\n" 36 | << "publicDepColor: black\n" 37 | << "privateDepColor: grey\n" 38 | << "componentLink\\\n" // Line continuation support 39 | << "Limit: 2\n" 40 | << "componentLocLowerLimit: 1\n" 41 | << "componentLocUpperLimit: 123\n" 42 | << "fileLocUpperLimit: 567 # could have a comment here\n" 43 | << "reuseCustomSections: true\n" 44 | << "blacklist: [\n" 45 | << " a.h\n" 46 | << " b.h\n" 47 | << "]\n" 48 | << "wrongTag: 567\n\n\n"; 49 | 50 | Configuration config; 51 | config.read(ss); 52 | ASSERT(config.companyName == "MyCompany"); 53 | ASSERT(config.regenTag == "MY_REGEN_TAG"); 54 | ASSERT(config.versionUsed == "3"); 55 | ASSERT(config.cycleColor == "brown"); 56 | ASSERT(config.publicDepColor == "black"); 57 | ASSERT(config.privateDepColor == "grey"); 58 | ASSERT(config.componentLinkLimit == 2); 59 | ASSERT(config.componentLocLowerLimit == 1); 60 | ASSERT(config.componentLocUpperLimit == 123); 61 | ASSERT(config.fileLocUpperLimit == 567); 62 | ASSERT(config.addLibraryAliases.size() == 1); 63 | ASSERT(config.addLibraryAliases.count("add_library") == 1); 64 | ASSERT(config.addExecutableAliases.size() == 1); 65 | ASSERT(config.addExecutableAliases.count("add_executable") == 1); 66 | ASSERT(config.blacklist.size() == 2); 67 | ASSERT(config.blacklist.count("a.h") == 1); 68 | ASSERT(config.blacklist.count("b.h") == 1); 69 | ASSERT(config.blacklist.count("stdint.h") == 0); 70 | ASSERT(config.reuseCustomSections); 71 | } 72 | 73 | TEST(ReadConfigurationFile_Aliases) 74 | { 75 | std::stringstream ss(CONFIG_FILE); 76 | ss << "addLibraryAlias: [\n" 77 | << " add_special_library \n" 78 | << " add_test_lib\n" 79 | << " ]\n" 80 | << "addExecutableAlias: [\n" 81 | << " add_special_exe\n" 82 | << "add_test\n" 83 | << "]\n"; 84 | 85 | Configuration config; 86 | config.read(ss); 87 | ASSERT(config.addLibraryAliases.size() == 3); 88 | ASSERT(config.addLibraryAliases.count("add_library") == 1); 89 | ASSERT(config.addLibraryAliases.count("add_special_library") == 1); 90 | ASSERT(config.addLibraryAliases.count("add_test_lib") == 1); 91 | 92 | ASSERT(config.addExecutableAliases.size() == 3); 93 | ASSERT(config.addExecutableAliases.count("add_executable") == 1); 94 | ASSERT(config.addExecutableAliases.count("add_special_exe") == 1); 95 | ASSERT(config.addExecutableAliases.count("add_test") == 1); 96 | } 97 | 98 | TEST(ReadConfigurationFile_LicenseString) 99 | { 100 | std::string licenseString( 101 | "My license\n" 102 | "It has multiple lines\n" 103 | "\n" 104 | " including whitespace \n"); 105 | 106 | std::stringstream ss(CONFIG_FILE); 107 | ss << "licenseString: \"\"\"\n" 108 | << licenseString 109 | << " \"\"\"\n"; 110 | 111 | Configuration config; 112 | config.read(ss); 113 | ASSERT(config.licenseString == licenseString); 114 | } 115 | -------------------------------------------------------------------------------- /test/InputTest.cpp: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | #include "TestUtils.h" 3 | 4 | #include "Component.h" 5 | #include "Configuration.h" 6 | #include "Constants.h" 7 | #include "Input.h" 8 | #include "FilesystemInclude.h" 9 | #include "FstreamInclude.h" 10 | #include 11 | 12 | void CreateCMakeProject(const std::string& projectName, 13 | const std::string& alias, 14 | const filesystem::path& workDir) 15 | { 16 | filesystem::create_directories(workDir / projectName); 17 | streams::ofstream out(workDir / projectName / "CMakeLists.txt"); 18 | out << "project(" << projectName << ")\n" 19 | << alias << "(${PROJECT_NAME}\n" 20 | << " somesourcefile.cpp\n" 21 | << ")\n"; 22 | } 23 | 24 | TEST(Input_Aliases) 25 | { 26 | TemporaryWorkingDirectory workDir(name); 27 | 28 | CreateCMakeProject("Renderer", "add_render_library", workDir()); 29 | CreateCMakeProject("UI", "add_ui_library", workDir()); 30 | CreateCMakeProject("DrawTest", "add_test", workDir()); 31 | CreateCMakeProject("Service", "add_service", workDir()); 32 | 33 | { 34 | streams::ofstream out(workDir() / "CMakeLists.txt"); 35 | out << "add_subdirectory(Renderer)\n" 36 | << "add_subdirectory(UI)\n" 37 | << "add_subdirectory(DrawTest)\n" 38 | << "add_subdirectory(Service)\n"; 39 | } 40 | 41 | std::stringstream ss; 42 | ss << "addLibraryAlias: [\n" 43 | << " add_render_library\n" 44 | << " add_ui_library\n" 45 | << " ]\n" 46 | << "addExecutableAlias: [\n" 47 | << " add_service\n" 48 | << " add_test\n" 49 | << " ]\n"; 50 | 51 | Configuration config; 52 | config.read(ss); 53 | 54 | std::unordered_map components; 55 | std::unordered_map files; 56 | 57 | LoadFileList(config, components, files, workDir(), true, false); 58 | 59 | ASSERT(components.size() == 5); 60 | 61 | for (auto& pair : components) { 62 | const Component& comp = *pair.second; 63 | 64 | if (comp.CmakeName() == "Renderer") { 65 | ASSERT(comp.type == "add_render_library"); 66 | } else if (comp.CmakeName() == "UI") { 67 | ASSERT(comp.type == "add_ui_library"); 68 | } else if (comp.CmakeName() == "DrawTest") { 69 | ASSERT(comp.type == "add_test"); 70 | } else if (comp.CmakeName() == "Service") { 71 | ASSERT(comp.type == "add_service"); 72 | } else { 73 | ASSERT(comp.CmakeName() == "ROOT"); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /test/TestUtils.h: -------------------------------------------------------------------------------- 1 | #include "FilesystemInclude.h" 2 | 3 | class TemporaryWorkingDirectory 4 | { 5 | public: 6 | TemporaryWorkingDirectory(const char* name) 7 | { 8 | originalDir = filesystem::current_path(); 9 | workDir = filesystem::temp_directory_path() / name; 10 | ASSERT(filesystem::create_directories(workDir)); 11 | filesystem::current_path(workDir); 12 | } 13 | 14 | ~TemporaryWorkingDirectory() 15 | { 16 | filesystem::current_path(originalDir); 17 | filesystem::remove_all(workDir); 18 | } 19 | 20 | const filesystem::path& operator()() const 21 | { 22 | return workDir; 23 | } 24 | 25 | private: 26 | filesystem::path originalDir; 27 | filesystem::path workDir; 28 | }; 29 | 30 | -------------------------------------------------------------------------------- /test/test.cpp: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | 3 | Test* Test::firstTest; 4 | 5 | int Test::RunAll() { 6 | Test* t = Test::firstTest; 7 | Test* c = t; 8 | int count = 0, current = 0, fails = 0; 9 | while (c) { c = c->nextTest; count++; } 10 | while (t) { 11 | current++; 12 | printf("\rRunning test %d/%d: %s", current, count, t->name); 13 | try { 14 | t->Run(); 15 | } catch (std::exception& e) { 16 | printf("\nFailed: %s\n", e.what()); 17 | fails++; 18 | } 19 | t = t->nextTest; 20 | } 21 | 22 | if (fails) { 23 | printf("\r%d/%d failed \n", fails, count); 24 | } else { 25 | printf("\rAll tests successful! \n"); 26 | } 27 | return (fails > 0); 28 | } 29 | 30 | int main() { 31 | return Test::RunAll(); 32 | } 33 | 34 | -------------------------------------------------------------------------------- /test/test.h: -------------------------------------------------------------------------------- 1 | #ifndef TEST_H 2 | #define TEST_H 3 | 4 | #include 5 | 6 | class Test { 7 | public: 8 | virtual void Run() = 0; 9 | static int RunAll(); 10 | protected: 11 | Test(const char* name) : name(name), nextTest(firstTest) 12 | { 13 | firstTest = this; 14 | } 15 | protected: 16 | const char* name; 17 | private: 18 | Test* nextTest; 19 | static Test* firstTest; 20 | }; 21 | 22 | #define TEST(x) static struct Test##x : public Test { Test##x() : Test(#x) {} void Run(); } __inst_##x; void Test##x::Run() 23 | #define ASSERT(x) do { auto v = (x); if (!v) { throw std::runtime_error("Assertion failed: " #x); } } while(0) 24 | 25 | #endif 26 | 27 | 28 | --------------------------------------------------------------------------------