CMake: how to handle add_custom_command sub-builds when building with Ninja

306 views Asked by At

I'm using CMake to build a C++ project (on Linux). The project consists of numerous sub-projects, each of which may invoke a function to create a useful link_date.c source file, which contains the timestamp when the link took place. I use this as a way to reliably embed the build time into the binary regardless of what source changes have occurred since the last build.

This function is declared thusly:

function(add_link_date TARGET)
    file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/link_date.c.in "
    const char *link_date() { return(\"@LINK_DATE@\"); }
    ")
    file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/link_date.cmake "
    EXECUTE_PROCESS(
        COMMAND date
        OUTPUT_VARIABLE LINK_DATE
        OUTPUT_STRIP_TRAILING_WHITESPACE
    )
    CONFIGURE_FILE(\${SRC} \${DST} @ONLY)
    ")
    add_custom_command(
        OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/link_date.c
        COMMAND ${CMAKE_COMMAND} -DSRC=link_date.c.in -DDST=link_date.c
        -P link_date.cmake
        DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/link_date.c.in)
    add_library(link_date-${TARGET} STATIC EXCLUDE_FROM_ALL link_date.c)
    target_link_libraries(${TARGET} link_date-${TARGET})
    add_custom_command(
        TARGET ${TARGET}
        PRE_LINK
        COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/link_date.c.in
        COMMAND ${CMAKE_COMMAND} --build ${CMAKE_CURRENT_BINARY_DIR} --target link_date-${TARGET})
endfunction()

This function, for each target that it is invoked for, creates a link_date.c.in template, then uses this template to create link_date.c, just for that target. Then this is added to a target-specific library which is then linked with the target executable. The final add_custom_command is used to ensure that the link_date library is rebuilt every time CMake is about to link the target executable. I got the original code for this from here.

The key thing is that it always injects the build timestamp into the executable no matter who builds it - developer, CI pipeline, etc. It works very well with the Makefiles generator and I've been using it for over 12 months, without any issues.

Recently I've been looking at updating our build to use Ninja, as it is demonstrably faster in most of my pipelines, however with this project it runs into a problem, specifically with the last COMMAND of the last add_custom_command (PRE_LINK) statement, which runs a sub-build. This has the effect of cding into the build sub-directory of the sub-project that invoked add_link_date, but because CMake only generates a Ninja build.ninja file in the top of the build directory, the child cmake --build fails because there is no build.ninja in this sub-project directory.

Is there a way to modify this process to work with Ninja?

I understand that not every CMake project can be migrated from Makefiles to Ninja, however this is the only real hurdle I have with this particular project so it would be nice to find a workaround.

0

There are 0 answers