Dynamically generated list of source files for cmake

We have a code generator that generates communication protocol definitions into several C ++ files and wants to switch our build system to using cmake. I made a sample project to demonstrate the problem we are facing.

The code generator accepts an input file and can generate hundreds of output files, so listing them in CMakeLists.txt not an option, especially because the protocol changes during development. We do not want to use wildcards for this.

We did not find a way to make cmake regenerate makefiles, if one of them makes changes to the file, according to the documentation, we believe configure_file should do this, but apparently it does not work on this task.

We want the make file for the main project (in the main folder in the example) to be regenerated by cmake whenever the generator code (in the gen in the example) or the protocol definition file (in our example, in.txt ) in.txt .

I have the following file structure:

 src ├── CMakeLists.txt ├── gen │  ├── CMakeLists.txt │  └── gen.cpp ├── in.txt └── main ├── CMakeLists.txt └── main.cpp 

with the following contents: SRC / CMakeLists.txt:

 cmake_minimum_required(VERSION 3.5.1) project(cmake-config) add_subdirectory(main) add_subdirectory(gen) add_dependencies(main gen run_gen) 

GEN / CMakeLists.txt:

 cmake_minimum_required(VERSION 3.5.1) project(gen) add_executable(gen gen.cpp) add_custom_target( run_gen COMMAND ./gen ${CMAKE_SOURCE_DIR}/in.txt COMMENT running gen ) add_dependencies(run_gen gen) 

GEN / gen.cpp:

 #include <fstream> #include <string> using namespace std; int main(int argc, char *argv[]){ ifstream inf(argv[1]); ofstream outf("input.txt"); string word; while(inf >> word) outf << "set(SOURCES ${SOURCES} " << word << ")\n"; } 

Main / CMakeLists.txt:

 cmake_minimum_required(VERSION 3.5.1) project(main) if(NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/../gen/input.txt) file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/../gen/input.txt "") endif() configure_file(${CMAKE_CURRENT_BINARY_DIR}/../gen/input.txt ${CMAKE_CURRENT_BINARY_DIR}/input.txt COPYONLY) set(SOURCES main.cpp) include(${CMAKE_CURRENT_BINARY_DIR}/input.txt) message(${SOURCES}) add_executable(main ${SOURCES}) 

Main / main.cpp:

 int main(){} 

gen generates input.txt with src/in.txt . input.txt contains a list of source files that I want to use to build main :

 set(SOURCES ${SOURCES} a.cpp) set(SOURCES ${SOURCES} b.cpp) set(SOURCES ${SOURCES} c.cpp) 

We are looking for a solution that does not require a reboot of cmake or requires a restart at each protocol change during development. This is easily achievable, but undesirable, as it leads to problematic code that ultimately uses older protocol definitions if one of the team does not understand that the protocol file (or generator) has been modified.

+5
source share
2 answers

We found a way to do this. The gen generator is placed in a separate project without changing the file structure. gen is created and executed when parsing the top-level CMakeLists.txt file. When the top level of make is called, it modifies CMakeLists.txt, so the next time make is called cmake starts automatically. The gen project has a custom command that regenerates output if in.txt changes.

CSI / CMakeLists.txt:

 cmake_minimum_required(VERSION 3.5.1) project(cmake-config) if(NOT EXISTS ${CMAKE_BINARY_DIR}/gen/Makefile) execute_process(COMMAND cmake -B${CMAKE_BINARY_DIR}/gen -H${CMAKE_SOURCE_DIR}/gen) endif() execute_process(COMMAND make -C ${CMAKE_BINARY_DIR}/gen) add_custom_target(touch_cmakelists.txt ALL COMMAND touch ${CMAKE_SOURCE_DIR}/CMakeLists.txt ) add_subdirectory(main) 

GEN / CMakeLists.txt:

 cmake_minimum_required(VERSION 3.5) project(gen) add_executable(gen gen.cpp) add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/input.txt COMMAND ${CMAKE_CURRENT_BINARY_DIR}/gen ${CMAKE_SOURCE_DIR}/../in.txt DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/gen ${CMAKE_SOURCE_DIR}/../in.txt COMMENT "Running gen" ) add_custom_target(run_generator ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/input.txt ) 

Main / CMakeLists.txt:

 project(main) set(SOURCES main.cpp) include(${CMAKE_CURRENT_BINARY_DIR}/../gen/input.txt) add_executable(main ${SOURCES}) 
0
source

I think you can fully automate the process. You need a macro that can handle a list of file arguments. Here is an example that I use to process interface files for the Zeroc Ice middleware, which become the source files for the rest of the project. What you also lacked was the argument "MAIN_DEPENDENCY" in "ADD_CUSTOM_COMMAND".

 macro(sliceCpp outfiles) foreach(i ${ARGN}) GET_FILENAME_COMPONENT(name ${i} NAME) GET_FILENAME_COMPONENT(name2 ${i} NAME_WE) SET(infile ${CMAKE_CURRENT_SOURCE_DIR}/${name}) SET(out1 ${CMAKE_CURRENT_BINARY_DIR}/${name2}.cpp) SET(out2 ${CMAKE_CURRENT_BINARY_DIR}/${name2}.h) ADD_CUSTOM_COMMAND( OUTPUT ${out1} ${out2} COMMAND ${ICE_SLICE2CPP} ARGS -I${ICE_SLICE_DIR} -I${CMAKE_CURRENT_SOURCE_DIR} --output-dir ${CMAKE_CURRENT_BINARY_DIR} ${infile} MAIN_DEPENDENCY ${infile} ) SET(${outfiles} ${${outfiles}} ${out1} ${out2}) endforeach(i) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}) endmacro(sliceCpp) 

As a result, CMake will understand the dependencies and restore only what is needed. Other structures that generate cpp files for interfaces can behave similarly, you can take a look at ROS generate_messages() https://github.com/ros/ros_comm/blob/kinetic-devel/clients/roscpp/rosbuild/roscpp. cmake

0
source

All Articles