Search code examples
c++ccmakebuildcompilation

How do C++ build systems keep track of changes in the source files?


When building my C++ projects I would use a bash file where there is all the instructions needed. It didn't present a problem because all the projects that I did were relatively small.

In my last project the build time would take around 4-6 mins so I switched to CMake. The first build was slow but the all the next ones were relatively fast.

My question is:Is compiling only the changed files the only optimization made by CMake to improve build time? Also: How does CMake keep track of changes within files? How does it decide whether to recompile a file or not?

I thought that maybe it saves the time of the last build and so checks last modification time of a file and compares it with the saved time. However I want to know the exact answer of it.


Solution

  • I've touched on this here under "Does CMake track header dependencies?".

    CMake is a buildsystem generator. Tracking changes that require rebuild is usually the (generated) buildsystem's job. From what I've read, CMake used to do some of that job, but started to rely more on buildsystems and compilers to do that in later versions. See for example the CMAKE_DEPENDS_USE_COMPILER variable, which was added in version 3.20. There are some related undocumented variables in the platform/compiler builtin parts of CMake with "DEPFILE" in their names if you want to go exploring through the Modules code.

    If you want to know about a specific buildsystem which CMake supports, consult the documentation for that buildsystem, which may or may not include such info. For example, the Ninja docs have a section on header dependencies, which state:

    To get C/C++ header dependencies (or any other build dependency that works in a similar way) correct Ninja has some extra functionality.

    The problem with headers is that the full list of files that a given source file depends on can only be discovered by the compiler: different preprocessor defines and include paths cause different files to be used. Some compilers can emit this information while building, and Ninja can use that to get its dependencies perfect.

    As for those compiler mechanisms, for example, see the GCC flags that start with -M. Ex.

    -M: Instead of outputting the result of preprocessing, output a rule suitable for make describing the dependencies of the main source file. The preprocessor outputs one make rule containing the object file name for that source file, a colon, and the names of all the included files, including those coming from -include or -imacros command-line options.

    As for how the buildsystem knows when a file has changed, usually the buildsystem will look at filesystem timestamps. Ex. from the Ninja docs:

    Ninja is closest in spirit and functionality to Make, relying on simple dependencies between file timestamps.

    But that's not to say they could technically use other mechanisms, such as content hashes.

    CMake does track other types of dependencies that the compiler is not suitable or able to track, such as those in add_custom_command and add_custom_target. Craig Scott (one of the CMake maintainers) has a related blogpost here if you want some extra, related readings. CMake also takes care of things like knowing when to rebuild if project configuration or the CMake cache has changed. I pretty sure that it does those things by generating custom rules in the generated buildsystem to handle those things.