Search code examples
c++cmake

Check if header has been generated using cmake


Let's say I have two files main.cpp and generate_header.cpp. generate_header.cpp generates a header containing data that main.cpp needs to run. based on this answer, I can use the following code to run generate_header.cpp before building main.cpp:

add_custom_target(run_generate_header 
    COMMAND generate_header
    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
    COMMENT "generating header"
)

add_dependencies(main_target run_generate_header)

However, as generating the header's content is slow, I would like to generate the header content only if the header is empty.

To achieve this, I thought about using a preprocessor macro HEADER_GENERATED, and check_cxx_symbol_exists to generate the header's content only if the macro is not defined.

check_cxx_symbol_exists(HEADER_GENERATED ${HEADER_PATH} HEADER_GENERATED)

if (NOT HEADER_GENERATED)
   add_dependencies(main_target run_generate_header)
endif()

The problem with this approach is that check_cxx_symbol_exists runs during the configuration stage, not the build stage, so if the header is erased after configuration, it will not be regenerated and the build will fail. Is there a way to check if the header contains data during the build stage? Thanks in advance!

dependency graph


Solution

  • Do not use add_custom_target for something that is generated. If something is generated, it's add_custom_command(OUTPUT this_is_generated.h - tell CMake what is generated and where and from what. Usually, you want pairs add_custom_command & add_custom_target. Sole add_custom_target is for executing something, like running a linter or printing file size.

    I would like to generate it only if it does not contain data.

    So do that. Generate files in cmake current binary dir.

    add_custom_command(
        COMMENT "generating header"
        OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/gen/the_generated_header.h
        COMMAND
            sh -xc
            [=[
            mkdir -vp "$(dirname "$2")"
            if it does contains data; then
                "$1" -o "$2"
            else
                echo > "$2"
            fi
            ]=]
            sh
            $<TARGET_FILE:generate_header>
            ${CMAKE_CURRENT_BINARY_DIR}/gen/the_generated_header.h
        DEPENDS generate_header 
        WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
    )
    add_custom_target(run_generate_header DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/gen/the_generated_header.h)
    
    add_dependencies(main_target run_generate_header)
    target_include_directries(main_target PUBLIC ${CMAKE_CURRENT_BINARY_DIR}/gen)
    

    however, it is not cross platform

    To be crossplatform, you would write the sh script in cmake. So create a file like:

    # script.cmake
    # check if the file exists in cmake
    execute_process(${ARGV1} -o ${ARGV2})
    # etc
    

    Then you would call this cmake script from cmake:

    add_custom_command(
       COMMAND cmake -P ./script.cmake 
        $<TARGET_FILE:generate_header>
        ${CMAKE_CURRENT_BINARY_DIR}/gen/the_generated_header.h
      ....