Reputation: 728
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}
)
Upvotes: 2
Views: 763
Reputation: 65936
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.
Upvotes: 2
Reputation: 19926
Here's a minimal working example that I think is much less complicated and follows best practices. Here, there's no need to configure_file
your generation script. There's also no need to write to the source directory, which you should never do.
The link between the custom command and your target is clearly established, too. Plus, the include paths are set up correctly.
CMakeLists.txt
:
cmake_minimum_required(VERSION 3.25)
project(example)
set(gendir "${CMAKE_CURRENT_BINARY_DIR}/extern/gen")
set(genfiles generated.h generated_1.cpp generated_2.cpp)
list(TRANSFORM genfiles PREPEND "${gendir}/")
add_custom_command(
OUTPUT ${genfiles}
COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/generate_files.sh"
)
add_executable(app main.cpp ${genfiles})
target_include_directories(app PRIVATE "${gendir}")
generate_files.sh
:
#!/bin/bash
set -e -o pipefail
mkdir -p extern/gen
echo "const int get_trouble_code();
const int get_higher_trouble_code();" > extern/gen/generated.h
echo "#include \"generated.h\"
const int get_trouble_code(){return 1;}" > extern/gen/generated_1.cpp
echo "#include \"generated.h\"
const int get_higher_trouble_code(){return 1+1;}" > extern/gen/generated_2.cpp
main.cpp
:
(same as above, but reproduced here for completeness)
#include "generated.h"
int main()
{
get_trouble_code();
get_higher_trouble_code();
return 0;
}
Here's what I see when it builds (successfully!)
$ cmake -G Ninja -S . -B build -DCMAKE_BUILD_TYPE=Debug
-- The C compiler identification is GNU 11.3.0
-- The CXX compiler identification is GNU 11.3.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/alex/test/build
$ cmake --build build --verbose
[1/5] cd /home/alex/test/build && /home/alex/test/generate_files.sh
[2/5] /usr/bin/c++ -I/home/alex/test/build/extern/gen -g -MD -MT CMakeFiles/app.dir/extern/gen/generated_1.cpp.o -MF CMakeFiles/app.dir/extern/gen/generated_1.cpp.o.d -o CMakeFiles/app.dir/extern/gen/generated_1.cpp.o -c /home/alex/test/build/extern/gen/generated_1.cpp
[3/5] /usr/bin/c++ -I/home/alex/test/build/extern/gen -g -MD -MT CMakeFiles/app.dir/extern/gen/generated_2.cpp.o -MF CMakeFiles/app.dir/extern/gen/generated_2.cpp.o.d -o CMakeFiles/app.dir/extern/gen/generated_2.cpp.o -c /home/alex/test/build/extern/gen/generated_2.cpp
[4/5] /usr/bin/c++ -I/home/alex/test/build/extern/gen -g -MD -MT CMakeFiles/app.dir/main.cpp.o -MF CMakeFiles/app.dir/main.cpp.o.d -o CMakeFiles/app.dir/main.cpp.o -c /home/alex/test/main.cpp
[5/5] : && /usr/bin/c++ -g CMakeFiles/app.dir/main.cpp.o CMakeFiles/app.dir/extern/gen/generated_1.cpp.o CMakeFiles/app.dir/extern/gen/generated_2.cpp.o -o app && :
$ ./build/app
$ # doesn't crash
Upvotes: -1