Search code examples
c++cmakecode-generation

Generate a source file that may or may not be updated


I have a CMakeLists.txt in which I want to generate several source files (namely, versiondata.cpp and version.rc.inc, included by res.rc) that depends on the general environment (current git HEAD, gcc -v output, CMakeCache.txt itself, and so on).

If it depended just on some files, I would generate it using an add_custom_command directive with the relevant DEPENDS and OUTPUT clauses; however, it's a bit tricky to pinpoint exactly its file dependencies; ideally, I'd want to run my script every time I call make, updating the files only if needed; if the generated files have actually been touched, then the targets depending from them should be rebuilt (the script is careful not to overwrite the files if they would have the same content as before).

My first attempt was using an add_custom_command with a fake main output, like this:

add_custom_command(OUTPUT versiondata.cpp.fake versiondata.cpp version.rc.inc
    COMMAND my_command my_options
    COMMENT "Generating versiondata.cpp"
)
# ...
# explicitly set the dependencies of res.rc, as they are not auto-deduced
set_source_files_properties(res.rc PROPERTIES OBJECT_DEPENDS "${PROJECT_BINARY_DIR}/version.rc.inc;${PROJECT_SOURCE_DIR}/other_stuff.ico")
# ...
add_executable(my_executable WIN32 ALL main.cpp versiondata.cpp res.rc)

versiondata.cpp.fake is never really generated, so the custom command is always run. This worked correctly, but always rebuilt my_executable, as CMake for some reasons automatically touches the output files (if generated) even though my script left them alone.

Then I thought I might make it work using an add_custom_target, that is automatically "never already satisfied":

add_custom_target(versiondata BYPRODUCTS versiondata.cpp version.rc.inc
    COMMAND my_command my_options
    COMMENT "Generating versiondata.cpp"
)
# ...
# explicitly set the dependencies of res.rc, as they are not auto-deduced
set_source_files_properties(res.rc PROPERTIES OBJECT_DEPENDS "${PROJECT_BINARY_DIR}/version.rc.inc;${PROJECT_SOURCE_DIR}/other_stuff.ico")
# ...
add_executable(my_executable WIN32 ALL main.cpp versiondata.cpp res.rc)

The idea here is that the versiondata target should be "pulled in" from the targets that depend on its BYPRODUCTS, and should be always executed. This seems to work on CMake 3.20, and the BYPRODUCTS seem to have some effect because if I remove the dependencies from my_executable my script doesn't get called.

However, on CMake 3.5 I get

make[2]: *** No rule to make target 'version.rc.inc', needed by 'CMakeFiles/my_executable.dir/res.rc.res'.  Stop.

and if I remove the explicit dependency from version.rc.inc it doesn't get generated at all

[ 45%] Building RC object CMakeFiles/my_executable.dir/res.rc.res
/co/my_executable/res.rc:386:26: fatal error: version.rc.inc: No such file or directory
 #include "version.rc.inc"
                          ^
compilation terminated.
/opt/mingw32-dw2/bin/i686-w64-mingw32-windres: preprocessing failed.
CMakeFiles/my_executable.dir/build.make:5080: recipe for target 'CMakeFiles/my_executable.dir/res.rc.res' failed
make[2]: *** [CMakeFiles/my_executable.dir/res.rc.res] Error 1

so I suspect that the fact that this works in 3.20 is just by chance.

Long story short: is there some way to make this work as I wish?


Solution

  • In CMake there are two types of dependencies:

    1. Target-level dependency, between targets.

      A target can be build only after unconditional building of all targets it depends on.

    2. File-level dependency, between files.

      If some file is older than one of its dependencies, the file will be regenerated using corresponded COMMAND.

    The key factor is that checking for timestamp of dependent files is performed strictly after building of dependent targets.

    For correct regeneration of versiondata.cpp file and executable based on it, one need both dependencies:

    1. Target-level, which would ensure that versiondata custom target will be built before the executable.

      add_dependencies(my_executable versiondata)
      
    2. File-level, which will ensure that the executable will be rebuilt whenever file versiondata.cpp will be updated.

      This dependency is created automatically by listing versiondata.cpp among the sources for the executable.


    Now about BYPRODUCTS.

    Even without explicit add_dependencies, your code works on CMake 3.20 because BYPRODUCTS generates needed target-level dependency automatically.

    This could be deduced from the description of DEPENDS option in add_custom_target/add_custom_command:

    Changed in version 3.16: A target-level dependency is added if any dependency is a byproduct of a target or any of its build events in the same directory to ensure the byproducts will be available before this target is built.

    and noting, that add_executable effectively depends on every of its source files.

    Because given comment for DEPENDS is applicable only for CMake 3.16 and later, in older CMake versions BYPRODUCTS does not create target-level dependency automatically, and one need to resort to explicit add_dependencies.