cmake

add_custom_command OUTPUT in config dependent dir


I want to generate files in the per-configuration build directory:

add_executable(test_generator_gen test_generator.cpp)
target_compile_definitions(test_generator_gen PRIVATE GENERATE)
target_link_libraries(test_generator_gen YOMM2::yomm2)

add_custom_command(
    OUTPUT ${GENERATED_FILES}
    DEPENDS test_generator_gen
    COMMAND test_generator_gen
    WORKING_DIRECTORY $<TARGET_FILE_DIR:test_generator_gen>
)

The files are generated where I want (e.g. ./builds/headers-only/tests/Debug/test_generator_slots.hpp and another file). My problem is, what do I put in ${GENERATED_FILES}? If I put "${CMAKE_CURRENT_BINARY_DIR}/test_generator_slots.hpp", I am lying to cmake, and the files get generated again and again. I tried what seemed natural to me:

add_custom_command(
    OUTPUT $<TARGET_FILE_DIR:test_generator_gen>/test_generator_slots.hpp
    DEPENDS test_generator_gen
    COMMAND test_generator_gen
    WORKING_DIRECTORY $<TARGET_FILE_DIR:test_generator_gen>
)
# ...

...but I get an error:

CMake Error at tests/CMakeLists.txt:95 (add_custom_command):
[cmake]   Error evaluating generator expression:
[cmake] 
[cmake]     $<TARGET_FILE_DIR:test_generator_gen>
[cmake] 
[cmake]   No target "test_generator_gen"

I am puzzled. Why is the same generator expression in the same command accepted for some arguments and not others? And how do I specify the OUTPUT correctly?

cmake 3.22.1


Solution

  • While the error message looks confusing, the documentation is clear:

    Arguments to OUTPUT may use a restricted set of generator expressions. Target-dependent expressions are not permitted.

    It is known that for multi-configuration generators the name of default output directory for executables and libraries is the same as configuration name. So you could use $<CONFIG> expression for it:

      OUTPUT ${CMAKE_BINARY_DIR}/$<CONFIG>/test_generator_slots.hpp
    

    For make the project to work both with multi- and single-configuration generators you need to use if:

    if(CMAKE_CONFIGURATION_TYPES)
      # Multi-configuration generator
      set(output_dir ${CMAKE_BINARY_DIR}/$<CONFIG>)
    else()
      # Single-configuration generator
      set(output_dir ${CMAKE_BINARY_DIR})
    endif()
    add_custom_command(
      OUTPUT ${output_dir}/test_generator_slots.hpp
      ...
    )
    

    Alternatively, you could explicitly set CMAKE_RUNTIME_OUTPUT_DIRECTORY variable for control output directory of the executables. And use that variable when need to refer to that directory:

    if(CMAKE_CONFIGURATION_TYPES)
      # Multi-configuration generator
      #
      # Explicitly use generator expression,
      # so CMake won't automatically append the subdirectory with a config name.
      set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/$<CONFIG>/bin)
    else()
      # Single-configuration generator
      #
      # While it is allowable to create config-specific directory,
      # we don't do that: every configuration already has its own build tree.
      set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
    endif()
    
    # The executable will be created in the directory specified by
    # CMAKE_RUNTIME_OUTPUT_DIRECTORY variable.
    add_executable(test_generator_gen test_generator.cpp)
    
    
    add_custom_command(
      OUTPUT ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/test_generator_slots.hpp
      ...
    )