6

I'm looking to trigger a CMake configure to occur when the output of a command changes; Specifically I am looking at trying to make CMake configure when the output of git describe --always --dirty has change from the previous time CMake was configured.

Most of this problem can be solved by watching the HEAD file and resolving the symref inside to refs/heads/[branch] and linking these with configure_file(...) however this doesn't pick up on when the tree is in a dirty state (I.E. when there are uncommitted modifications). In this instance git describe --always --dirty will append the -dirty suffix to the output.

When this occurs there are no changes to the git files, only that git has noticed differences from the stored state so I can't configure_file(...) on any files here to get cmake to notice the change and reconfigure.

So I'm looking to find if there is a way to get cmake to run the git command notice the difference in output and trigger a reconfigure, almost need something akin to a pre-reconfigure-check stage.

Not sure if this is possible of if anyone has any ideas how else this behaviour can be achieved?

rblk
  • 185
  • 1
  • 2
  • 9
  • Don't do this kind of stuff in CMake itself, because that would give you sort of a "chicken-and-egg" problem (check for changed files when no files have changed). Just move it to some shell script wrapper calling your git, configure and build steps. – Florian Jul 06 '16 at 10:48
  • So the reason I am looking to do this is so I can compile the `git describe` string into the version information to ensure that I know exactly which git commit it was built from and _critically_, if there are modifications from that commit. I don't want to have a meta-build system on top of it just to gain that one small piece of functionality if I can avoid it and I don't see why it should end in a chicken and egg situation? – rblk Jul 06 '16 at 10:53
  • 1
    I don't know the way to force CMake to rerun configure during build. But if you only want to change *version.h* and rebuild everything dependent from it, you may generate that file on **build** stage with something like `add_custom_target(BuildVersion COMMAND ${CMAKE_EXECUTABLE} -P )`. This runs CMake in script mode, where you may use usual `configure_file` command. Futher `add_dependencies( BuildVersion)` command will garantee, that executable will be rebuilt if script changes the *version.h* file. – Tsyvarev Jul 06 '16 at 14:02
  • [This related question](http://stackoverflow.com/q/33999666/1938798) may help (also read the comments on that answer). – Craig Scott Jul 06 '16 at 20:54
  • There is related discussion with some solutions at: http://stackoverflow.com/questions/1435953/how-can-i-pass-git-sha1-to-compiler-as-definition-using-cmake – Mark Dewing Jan 19 '17 at 19:26

1 Answers1

3

The Problem

The problem here is that the "pre-reconfigure-check stage" is handled by your build environment. CMake is only called when some of its input files are changed (because CMake has generated rules for your build environment to keep track if those files being changed).

For this discussion let's assume you have something like:

execute_process(
    COMMAND ${GIT_EXECUTABLE} describe --always --dirty
    OUTPUT_VARIABLE _git_file_list
    OUTPUT_STRIP_TRAILING_WHITESPACE
)

configure_file(version.h.in version.h)

No Build-In Reconfigure when External Command Output Changes

I don't think something like "checking the output of external command as pre-reconfigure step" is possible without an additional script that you make part of your build process.

You could force CMake to reconfigure every time e.g. by calling make rebuild_cache before your actual build or by adding e.g. add_custom_command(TARGET MyExe POST_BUILD ${CMAKE_COMMAND} -E remove ${CMAKE_CURRENT_BINARY_DIR}/version.h), but calling the configuration process every time is very time consuming thing to do.

Solution

In my projects I have moved something like the code above into it's own WriteVersionInfo.cmake script:

set(_version_cpp_full_path "${CMAKE_CURRENT_BINARY_DIR}/version.cpp")
set(_version_obj_full_path "${_version_cpp_full_path}${CMAKE_CXX_OUTPUT_EXTENSION}")

string(REPLACE " " ";" _compiler_flags_list ${CMAKE_CXX_FLAGS})

# Create a dummy output to satisfy dependency check
if (NOT EXISTS ${_version_obj_full_path})
    file(WRITE ${_version_obj_full_path} "")
endif()

add_custom_command(
    TARGET MyExe
    PRE_LINK 
    COMMAND ${CMAKE_COMMAND} -D GIT_EXECUTABLE=${GIT_EXECUTABLE} 
                             -D _version_cpp_name=${_version_cpp_full_path} 
                             -P ${CMAKE_CURRENT_SOURCE_DIR}/WriteVersionInfo.cmake
    COMMAND ${CMAKE_CXX_COMPILER} ${_compiler_flags_list} 
                                  -o ${_version_obj_full_path} 
                                  -c ${_version_cpp_full_path}
    VERBATIM
)

target_link_libraries(
    MyExe
    ${_version_obj_full_path}
)

Here I'm directly writing to a .cpp file and directly pass it to the compiler as a PRE_LINK step. Please note that the above approach is not working for library targets (because there is no pre-link step).

References

Florian
  • 39,996
  • 9
  • 133
  • 149
  • As far as I read [the manual](https://cmake.org/cmake/help/v3.13/command/add_custom_command.html), `PRE_LINK ` only doesn't exist for `add_custom_target()`; it exists for both libraries and executables. – Victor Sergienko Apr 25 '19 at 16:46