In my previous post I wrote about include dependencies in general - why they matter and how to keep them at a minimum. In this article I’ll give a quick overview about the tools I evaluated and used to examine and adjust include dependencies of a large C++ project.
I’ll cover some useful gcc compiler options, doxygen, cinclude2dot and the powerful include-what-you-use.
GCC compiler options
GCC provides some helpful options to determine/analyze include dependencies.
-M… output a rule suitable for make describing the dependencies of the main source file.
-MM… like -M but do not mention header files that are found in system header directories, nor header files that are included, directly or indirectly, from such a header.
-MF… when used with -M or -MM, specifies a file to write the dependencies to.
-H… prints the name of each header file used. Each name is indented to show how deep in the #include stack it is.
Applied to the minimal example located here on github the truncated outputs look as follows:
$gcc -M main.cpp main.o: main.cpp /usr/include/stdc-predef.h a.h b.h c.h d.h \ /usr/include/c++/4.9.0/iostream \ /usr/include/c++/4.9.0/x86_64-unknown-linux-gnu/bits/c++config.h \ /usr/include/c++/4.9.0/x86_64-unknown-linux-gnu/bits/os_defines.h \ /usr/include/features.h /usr/include/sys/cdefs.h \ /usr/include/bits/wordsize.h /usr/include/gnu/stubs.h \ /usr/include/gnu/stubs-64.h \ /usr/include/c++/4.9.0/x86_64-unknown-linux-gnu/bits/cpu_defines.h \ ... <truncated>
$ gcc -MM main.cpp main.o: main.cpp a.h b.h c.h d.h
$ gcc -MM -H main.cpp . a.h .. b.h ... c.h .... d.h . /usr/include/c++/4.9.0/iostream .. /usr/include/c++/4.9.0/x86_64-unknown-linux-gnu/bits/c++config.h ... /usr/include/c++/4.9.0/x86_64-unknown-linux-gnu/bits/os_defines.h .... /usr/include/features.h ..... /usr/include/sys/cdefs.h ...... /usr/include/bits/wordsize.h ..... /usr/include/gnu/stubs.h ...... /usr/include/gnu/stubs-64.h ... /usr/include/c++/4.9.0/x86_64-unknown-linux-gnu/bits/cpu_defines.h .. /usr/include/c++/4.9.0/ostream ... /usr/include/c++/4.9.0/ios ... <truncated>
Doxygen can generate nice interactive include graphs. This is how you can enable it in the doxygen configuration file (Doxyfile) - directly from the doxygen documentation:
If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to YES then doxygen will generate a graph for each documented file showing the direct and indirect include dependencies of the file with other documented files. The default value is: YES. This tag requires that the tag HAVE_DOT is set to YES.
You can also take a look at generated doxygen “documentation” committed to the includeDependenciesDemo example at github (but this is not fully working remotely in the github html preview mode).
cinclude2dot is a nice little perl script that can generate an include dependency graph for C/C++ projects. It basically generates a dot graph that can be converted by the dot tool provided by the Graphviz package to an “image” e.g. ps or png. Basic usage is quite simple:
cd ~/program/src ./cinclude2dot.pl > main.cpp.dot dot -Tpng main.cpp.dot > main.cpp.png
This is the generated graph generated for the includeDependenciesDemo:
cppdeps is another nice Perl script that produces a graphical visualization and levelizes and scores your project using the “normalized cumulative component dependency (NCCD)” metric.
cd ~/program/src ./cppdeps.pl -I /usr/include/c++/4.9.0 -p deps.png
This is the truncated text output applied to our includeDependenciesDemo:
/usr/include/c++/4.9.0/backward/binders: /usr/include/c++/4.9.0/bits/allocator: /usr/include/c++/4.9.0/bits/memoryfwd /usr/include/c++/4.9.0/type_traits /usr/include/c++/4.9.0/bits/atomic_lockfree_defines: /usr/include/c++/4.9.0/bits/basic_ios: /usr/include/c++/4.9.0/bits/locale_classes /usr/include/c++/4.9.0/bits/locale_facets /usr/include/c++/4.9.0/bits/localefwd /usr/include/c++/4.9.0/bits/streambuf_iterator ... <truncated> ... LEVEL 99: /usr/include/c++/4.9.0/ios LEVEL 100: /usr/include/c++/4.9.0/iostream /usr/include/c++/4.9.0/istream /usr/include/c++/4.9.0/ostream LEVEL 101: main COUPLING_METRIC = 2.5172
This is the generated graphics deps.png:
The tool include-what-you-use is a very powerful tool created by google that uses clang to analyze #includes in C and C++ source files. It can analyze include dependencies, automatically remove superfluous includes, add forward declarations when possible and add missing includes to make headers self-sufficient. If you have a large code-base and you want to adjust your include dependencies, this is the tool to go. But it also requires some manual work to get it up and running (I had to compile clang and include-what-you-use from source), integrate it into your “build system”, and you might have to tweak some of its parameters to avoid unwanted or wrong (very few in my case) corrections.
I don’t go into more details here - the official documentation is ok anyway. But I can say it’s definitely worth - I used it on a quite large code-base and it did a very well job (I’m thinking about writing a separate blogpost on this).
If you have any questions, recommendations or feedback, please let me know and leave a comment. Thx!