Operating on Target after it's built to modify based on input file

After building my binary, I need to run a post processing script on the binary, which effectively copies an input into the ELF object.

Under QNX, this is the “usemsg” command. The input is the target and a text file, the output is also the target, which is now modified to include the contents of that text file in the ELF object itself.

I’m able to run via add_custom_command. However, if the input file is modified, I can’t figure out how to get the custom command to run again.

function(target_use_msg TARGET USEFILE)
    if(QNXNTO)
        set(QNX_USEMSG_EXECUTABLE usemsg)
        if(DEFINED QNX_USEMSG_EXECUTABLE)
            set(_singleargs DESCRIPTION)
            cmake_parse_arguments(PARSE_ARGV 2 arg "" "${_singleargs}" "")

            site_name(HOSTNAME)
            set(_USE_INFO_SOURCE "")
            string(APPEND _USE_INFO_SOURCE "USER=$ENV{USER}\nHOST=${HOSTNAME}\nMYVERSION=x\n")
            if(arg_DESCRIPTION)
                string(APPEND _USE_INFO_SOURCE "DESCRIPTION=${arg_DESCRIPTION}")
            endif()
            file(WRITE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${TARGET}_info.use" "${_USE_INFO_SOURCE}")
            UNSET(_USE_INFO_SOURCE)

            add_custom_target(
                ${TARGET}_use
                DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${USEFILE}"
            )
            add_custom_command(
                TARGET ${TARGET} POST_BUILD
                COMMAND
                    ${QNX_USEMSG_EXECUTABLE}
                ARGS "$<TARGET_FILE:${TARGET}>" "${CMAKE_CURRENT_SOURCE_DIR}/${USEFILE}"
                COMMAND
                    ${QNX_USEMSG_EXECUTABLE}
                ARGS -f "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${TARGET}_info.use" "$<TARGET_FILE:${TARGET}>"
                VERBATIM
            )
            add_dependencies(${TARGET} ${TARGET}_use)
        endif()
    endif()
endfunction()

This is just one variation. I hoped I could create some kind of dependencies. But that code is not helping. Just the same is using:

            add_custom_command(
                TARGET ${TARGET} POST_BUILD
                COMMAND
                    ${QNX_USEMSG_EXECUTABLE}
                ARGS "$<TARGET_FILE:${TARGET}>" "${CMAKE_CURRENT_SOURCE_DIR}/${USEFILE}"
                COMMAND
                    ${QNX_USEMSG_EXECUTABLE}
                ARGS -f "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${TARGET}_info.use" "$<TARGET_FILE:${TARGET}>"
            )

Bulding works. Modify the input file ${USEFILE} and it’s not recognised.

It doesn’t seem QNX specific, anything might want to postprocess a binary and modify it based on some input file (i.e. merge resources).

Any hints on how to get this work, or alternatives? I couldn’t find any general way either to specify a file (e.g. ending in “.use”) and then apply a general rule after everything is linked either. Honestly, not too sure where I should be looking.

Is ${USEFILE} added as a source to the target that contains the custom command?

You’re not using MAIN_DEPENDENCY or DEPENDS keywords to tell CMake that the custom command depends on the input file.

The QNX usemsg takes an input file and literally copies it into the target verbatim as an ELF section.

I had tried initially to add the ${USEFILE} to the sources via the add_executable without success.

I was able to do a real hack (that I’m certainly not proud of, but it works as intended with side effects of having to relink).

        if(DEFINED QNX_USEMSG_EXECUTABLE)
            set(_singleargs DESCRIPTION)
            cmake_parse_arguments(PARSE_ARGV 2 arg "" "${_singleargs}" "")

            site_name(HOSTNAME)
            set(_USE_INFO_SOURCE "")
            string(APPEND _USE_INFO_SOURCE "USER=$ENV{USER}\nHOST=${HOSTNAME}\nMYVERSION=x\n")
            if(arg_DESCRIPTION)
                string(APPEND _USE_INFO_SOURCE "DESCRIPTION=${arg_DESCRIPTION}")
            endif()
            file(WRITE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${TARGET}_info.use" "${_USE_INFO_SOURCE}")
            UNSET(_USE_INFO_SOURCE)

            # This is very hacky.
            #
            # I need a way to run `usemsg` after building the target. But
            # additionally to that, if the use message file is modified, I need
            # a way to re-run the command.
            #
            # Because `add_custom_command(TARGET ... POST_BUILD)` ignores the
            # DEPENDS command, and `add_executable(TARGET file.use)` is ignored
            # because it's not a source file, modifying the `file.use` won't
            # cause it to rebuild.
            #
            # So create an empty source file, add it as a dependency to the
            # executable target. The empty source file is dependent on the
            # `file.use` file. Thus when the use file is modified, the timestamp
            # of the empty source file is updated. This causes the binary to be
            # relinked (which unfortunately can add time to the build), but at
            # least the POST_BUILD now is executed resulting in the use message
            # being added.
            if(CMAKE_C_COMPILER_LOADED)
                set(_qnx_usemsg_file "${TARGET}.tmp.c")
            elseif(CMAKE_CXX_COMPILER_LOADED)
                set(_qnx_usemsg_file "${TARGET}.tmp.cpp")
            else()
                message(FATAL_ERROR "TARGET_USE_MSG needs either C or CXX language enabled")
            endif()
            add_custom_command(
                OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${_qnx_usemsg_file}"
                DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${USEFILE}"
                COMMAND ${CMAKE_COMMAND} -E touch "${CMAKE_CURRENT_BINARY_DIR}/${_qnx_usemsg_file}"
                COMMENT ""
            )
            add_custom_command(
                TARGET ${TARGET} POST_BUILD
                COMMAND ${QNX_USEMSG_EXECUTABLE}
                ARGS "$<TARGET_FILE:${TARGET}>" "${CMAKE_CURRENT_SOURCE_DIR}/${USEFILE}"
                COMMAND ${QNX_USEMSG_EXECUTABLE}
                ARGS -f "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${TARGET}_info.use" "$<TARGET_FILE:${TARGET}>"
                VERBATIM
            )
            target_sources(${BINARY} PRIVATE ${_qnx_usemsg_file})
        endif()

I’d appreciate if there are better ideas.

You may use a checksum check to trigger the build again if needed:


add_library(ObjLib OBJECT ${CMAKE_BINARY_DIR}/gtest-tidy-mwe.cpp)

# NOTE: OBJECT_DEPENDS does not help? CK
# TODO: set_property(TARGET ObjLib PROPERTY OBJECT_DEPENDS ${CMAKE_BINARY_DIR}/verify/gtest-tidy-mwe.md5)

add_executable(gtest-tidy-mwe $<TARGET_OBJECTS:ObjLib>)

target_link_libraries(gtest-tidy-mwe PRIVATE GTest::gtest_main)

# enable clang-tidy for my target only!
target_enable_clang_tidy(gtest-tidy-mwe)

enable_testing()
add_test(NAME gtest-tidy-mwe COMMAND gtest-tidy-mwe)
install(TARGETS gtest-tidy-mwe)

# Additional command which will run after the above from a different directory
add_custom_command(
    TARGET gtest-tidy-mwe
    POST_BUILD
    COMMAND md5sum $<TARGET_OBJECTS:ObjLib> $<TARGET_FILE:gtest-tidy-mwe> > gtest-tidy-mwe.md5
    BYPRODUCTS ${CMAKE_BINARY_DIR}/verify/gtest-tidy-mwe.md5
    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/verify
)

# Additional target the depends on BYPRODUCT above ...
add_custom_target(
    check
    COMMAND md5sum -c gtest-tidy-mwe.md5 || touch $<TARGET_OBJECTS:ObjLib>
    DEPENDS gtest-tidy-mwe
    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/verify
)