Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically generated source file list for cmake

Tags:

c++

cmake

We have a code generator that generates communication protocol definitions into multiple c++ files and want to switch our build system to use cmake. I have made a sample project to demonstrate the problem we're facing.

The code generator takes an input file and may generate hundreds of output files so enumerating them in CMakeLists.txt is not an option especially since the protocol changes during development. We neither want to use wildcards for this.

We did not find a way to make cmake regenerate the makefiles if one of it's input file changes, according to the documentation it is our belief that configure_file should be able to do that but it apparently fails at it's job.

We want the makefile for the main project (in main folder in example) to be regenerated by cmake whenever either the generator (in gen folder in example) code changes or the protocol definition file (in our example this is in.txt) is updated.

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 using src/in.txt. input.txt contains a list of source files which 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 which does not require bootstrapping cmake or needing to rerun it every time the protocol changes during development. This is easily achievable but undesirable as it leads to problematic code which would end up using older protocol definitions if someone from the team does not realize the protocol file (or generator) has changed.

like image 783
robert Avatar asked Jun 08 '16 13:06

robert


1 Answers

We have found a way to do it. The generator gen is put in a separate project, without changing the file structure. gen is built and executed when parsing the top level CMakeLists.txt file. When top level make is called, it modifies CMakeLists.txt, therefore the next time make is called cmake runs automatically. Project gen has a custom command which regenerates the output if in.txt changes.

src/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})
like image 173
robert Avatar answered Oct 30 '22 21:10

robert