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:
gen
is built first and then target test
is builtStep 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}
)
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
, buttest
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.