Search code examples
c++cmakebuildpre-build-event

Make CMake update header file before building


I have a header file version.h containing version information. Before every build, I want CMake to ensure that the version information is up to date. As a test, I've set up CMake to touch the file before every build. That should cause all source files that include the file to be recompiled on every build. Here is how it works:

# CMakeLists.txt
add_library(MY_APP STATIC main.cpp version.h)

add_custom_target(UPDATE_VERSION
  COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_SOURCE_DIR}/version.h
)

add_dependencies(MY_APP UPDATE_VERSION)
// main.cpp
#include "version.h"

On every build, the first output I get is /usr/local/bin/cmake -E touch version.h, and the timestamp of the file is changed. Great! However, main.cpp is only compiled on every other build! It seems that whether main.cpp should be compiled is determined before version.h is updated. Probably something like this happens:

  1. List main.cpp for compilation if version.h is newer than main.obj
  2. touch version.h
  3. Compile main.cpp if it was listed for compilation

Whenever main.cpp is compiled, main.obj will be newer than version.h in step 1 during the next build, so then main.cpp will not be compiled. In the next run again, version.h will be newer than main.obj in step 1, and main.cpp is compiled again.

At least this is my assumption to why it does not work. How can I update build version.h so that main.cpp is compiled every time?

I'm using Visual Studio 2022 and Ninja.


Solution

  • If you want to keep using your current modification timestamp approach, you might want to mark the generated file as a byproduct of the custom target using the BYPRODUCTS argument of add_custom_target, which will give the source file the GENERATED property. And/or add the ALL argument.

    Using file timestamps is depending on whether the generated buildsystem lacks the sophistication to look at whether the content of a file has changed. I think you'd do better to actually change the content of the file. Ex. putting ${CMAKE_COMMAND} -P /absolute/path/to/write_stamp_file.cmake, where write_stamp_file.cmake contains something like this:

    string(TIMESTAMP timestamp "%s" UTC)
    file(WRITE "${training_stamp_file}" "// UTC: ${timestamp}")
    

    Of course, that's assuming a buildsystem that lacks the sophistication to detect whether the changes made in a dirty header actually necessitate re-compilation of a source file that #includes it, but... that's another problem. There is the OBJECT_DEPENDS source file property, but at the time of this writing, only the Makefile and Ninja generators support it.