Search code examples
c++cmakedependenciescmake-custom-commandadd-custom-target

cmake add_dependencies, add_custom_command and add_custom_target not working together


I have been trying to get the simple use case of getting cmake to to generate some files with a script and then build an executable out of those generated files. After spending hours reading the documentation and various stack overflow answers, it seems like this is how everything should be arranged, yet cmake refuses to configure the project.

The idea is that things are done in following steps:

  1. During configure time, the generate_files.sh is generated with correct paths
  2. During build time, the target gen is built first and then target test is built

Step 1 is being performed properly, however step 2 isn't. Cmake complains that the source files for target test aren't found, even though it should build gen first and then it would find the sources.

What am I doing wrong?

Directory structure:

.
├── build
├── CMakeLists.txt
├── config
│   └── generate_files.sh.in
├── extern
│   ├── CMakeLists.txt
│   └── gen
├── scripts
└── src
    ├── CMakeLists.txt
    └── main.cpp

Top level CMakeLists:

cmake_minimum_required(VERSION 3.13)
project(test VERSION 0.1.0.0)

add_subdirectory(src)
add_subdirectory(extern)

configure_file("${CMAKE_SOURCE_DIR}/config/generate_files.sh.in" "${CMAKE_SOURCE_DIR}/scripts/generate_files.sh")

src/CMakeLists:

add_executable(test main.cpp)

extern/CMakeLists:

set(generated_sources
    ${CMAKE_CURRENT_SOURCE_DIR}/gen/generated.h
    ${CMAKE_CURRENT_SOURCE_DIR}/gen/generated_1.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/gen/generated_2.cpp
    )

set(generated_directories
    ${CMAKE_CURRENT_SOURCE_DIR}/gen)

add_custom_command(
    OUTPUT ${generated_sources}
    COMMAND "${CMAKE_SOURCE_DIR}/scripts/generate_files.sh"
    )

add_custom_target(gen
    DEPENDS ${generated_sources}
    )

add_dependencies(test gen)

target_sources(test
    PRIVATE
        ${generated_sources}
)

target_include_directories(test
    PRIVATE
        ${generated_directories}
)

config/generate_files.sh.in:

#! /bin/bash

echo " const int get_trouble_code();
const int get_higher_trouble_code();" > ${CMAKE_SOURCE_DIR}/extern/gen/generated.h
echo " #include \"generated.h\"
const int get_trouble_code(){return 1;}" > ${CMAKE_SOURCE_DIR}/extern/gen/generated_1.cpp
echo " #include \"generated.h\"
const int get_higher_trouble_code(){return 1+1;}" > ${CMAKE_SOURCE_DIR}/extern/gen/generated_2.cpp

src/main.cpp:

#include "generated.h"

int main()
{
  get_trouble_code();
  get_higher_trouble_code();

  return 0;
}

Edit: I found a way to make it work, but now I am even more confused. Adding the generated files as a library instead of custom target seems to do the trick. The following changes to extern/CMakeLists work, but could someone explain why? :

set(generated_sources
    ${CMAKE_CURRENT_SOURCE_DIR}/gen/generated.h
    ${CMAKE_CURRENT_SOURCE_DIR}/gen/generated_1.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/gen/generated_2.cpp
    )

set(generated_directories
    ${CMAKE_CURRENT_SOURCE_DIR}/gen)

add_custom_command(
    OUTPUT ${generated_sources}
    COMMAND "${CMAKE_SOURCE_DIR}/scripts/generate_files.sh"
    )

#comment out this stuff
#add_custom_target(gen
#   DEPENDS ${generated_sources}
#    )
#
#add_dependencies(test gen)
#
#target_sources(test
#    PRIVATE
#        ${generated_sources}
#)

#add as library instead
add_library(gen ${generated_sources})
target_link_libraries(test PRIVATE gen)

target_include_directories(test
    PRIVATE
        ${generated_directories}
)

Solution

  • CMake complains that the source files for target test aren't found ...

    This is because the source file is absent, and its GENERATED property is not set.

    Normally, add_custom_command sets GENERATED property for all files listed in its OUTPUT. But before CMake 3.20 that property was local to the directory:

    • add_custom_command is called from extern/CMakeLists.txt, but
    • target test evaluates its sources in top-level CMakeLists.txt, where the target is created. (Irrespective where target_sources is called).

    In CMake 3.20 the GENERATED property becomes global (for use this feature your project should have corresponding cmake_minimum_required).

    If updating CMake is not an option, then you could set GENERATED property manually in top-level CMakeLists.txt. (This will hurts locality of your CMakeLists.txt .. so it is better to update CMake.)

    Technically, you trigger the problem issued in that bugreport.