Search code examples
cmakecmake-custom-command

Force custom command to always run without first deleting the output


I have a setup where I use a custom command to check the current hash of a git repository so that other commands can clone it if it has updated

add_custom_command(
    OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/module_VERSION.txt
    COMMAND ${GIT_EXECUTABLE} ls-remote ${MODULE_URL} master > ${CMAKE_CURRENT_BINARY_DIR}/module_VERSION.txt.tmp
    COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_BINARY_DIR}/module_VERSION.txt.tmp ${CMAKE_CURRENT_BINARY_DIR}/module_VERSION.txt
    COMMAND ${CMAKE_COMMAND} -E rm ${CMAKE_CURRENT_BINARY_DIR}/module_VERSION.txt.tmp
)

Of course this will only run once as CMake sees no reason to rerun it. I can force it to run by adding a second (dummy) output - CMake then recognises that this output doesn't exist and then reruns the rule. However the Makefile that this generates actually deletes module_VERSION.txt before running the command rendering the whole pursuit pointless (Ninja does not have this problem).

I am able to get this to work but in an extremely hacky way: creating another target that always runs and then generating a dependency on this.

# Use echo_append as a no-op
add_custom_command(
    OUTPUT module_FORCERUN
    COMMAND ${CMAKE_COMMAND} -E echo_append
)

add_custom_command(
    OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/module_VERSION.txt
    COMMAND ${GIT_EXECUTABLE} ls-remote ${MODULE_URL} master > ${CMAKE_CURRENT_BINARY_DIR}/module_VERSION.txt.tmp
    COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_BINARY_DIR}/module_VERSION.txt.tmp ${CMAKE_CURRENT_BINARY_DIR}/module_VERSION.txt
    COMMAND ${CMAKE_COMMAND} -E rm ${CMAKE_CURRENT_BINARY_DIR}/module_VERSION.txt.tmp
    DEPENDS module_FORCERUN
)

This seems just really hacky and like it could be relying on some corner cases in cmake which aren't guaranteed to be stable. Is there a better way to get this working?

I am using cmake 3.21.3


Solution

  • Use add_custom_target for implement "always run" functionality and via BYPRODUCTS keyword specify the file which it could produce/update:

    add_custom_target(update_module_version
        BYPRODUCTS ${CMAKE_CURRENT_BINARY_DIR}/module_VERSION.txt
        COMMAND ${GIT_EXECUTABLE} ls-remote ${MODULE_URL} master > ${CMAKE_CURRENT_BINARY_DIR}/module_VERSION.txt.tmp
        COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_BINARY_DIR}/module_VERSION.txt.tmp ${CMAKE_CURRENT_BINARY_DIR}/module_VERSION.txt
        COMMAND ${CMAKE_COMMAND} -E rm ${CMAKE_CURRENT_BINARY_DIR}/module_VERSION.txt.tmp
    )
    

    That way, if any other target will depend on update_module_version one, the module_VERSION.txt file will be created/updated before evaluation of the target.

    Such target-level dependency will be created automatically by CMake, if given file will be listed as dependency for target/command in the same directory, where target update_module_version is created:

    add_custom_command(OUTPUT <...>
      DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/module_VERSION.txt
      COMMAND <...>
    )
    

    From other directories the target-level dependency should be specified explicitly:

    # If used in other directories
    add_custom_command(OUTPUT <...>
      DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/module_VERSION.txt
              update_module_version
      COMMAND <...>
    )