rtclark
rtclark

Reputation: 595

How to manage multiple build configurations with cmake

I am trying to think through how to design a new project build system.
I want to use CMake to compile for Windows and Linux. I want to have Release and Debug builds, which are pretty straight forward.

The problem is that I will want to deploy this software for multiple hardware projects over the course of the next few years. This means that I will have pre-processor definitions to turn on/off and or alter functionality in my C++ program. I will also want to link configuration specific libraries. A current legacy project uses a header with a list of defines and then links all possible project dependencies.

An example header:

// projects.h

//#define project1
//#define project2
#define project3

Which means that to change hardware/project configurations you have to edit this file to make sure the correct hardware configuration is selected.
What I want to do is to have a configuration for each project which can also be configured for Release or Debug build.

Is there any advice on how to more optimally deal with this in CMake?

Upvotes: 1

Views: 4433

Answers (1)

KamilCuk
KamilCuk

Reputation: 141768

for multiple hardware projects

For me it got messy and got messy real quick.

My goals were:

  • I don't like a single big config.h file. I don't want to have to recompile 100% of files because I added a space in a central config.h file
  • All boards expose an API that user application can use. Boards don't mix together.
  • I can easy add new boards and have a board to test the application.

What I discovered is that:

  • cmake is used to configure your project for different configurations. So cmake does not store a configuration, it is used to choose a configuration.
  • So you need "external" place to store the configurations for current project
  • I have multiple applications and multiple boards, the chosen application configuration options are stored inside a makefile. This makefile then configures cmake, which in turn configures the build system, that can then be used to build the application. This is so, because cmake supports one configuration per toolchain. So for architectures with different compilers/compiler options I have to re-run cmake.

I have basically such a directory structure:

- app
  - CMakeLists.txt
  - main.c
  - lib.c
- boards               # this is actually git submodule shared between multiple projects
  - boardapi           # generic board api library
    - CMakeLists.txt
    - uart_api.h       # exposes an api to access uart
    - spi_api.h
    - timer_api.h
    - board_api.h
    - some_defines.h    # includes some_defines_impl.h
  - BOARD1
    - toolchain.cmake
    - CMakeLists.txt
    - implementation1.c
    - implementation2.c
    - some_defines_impl.h
  - BOARD2
    - toolchain.cmake
    - CMakeLists.txt
    - implementation1.c
    - implementation2.c
    - some_defines_impl.h
  - linux
    - ... as above ...
  - armv5te
    - ... as above ...
  - CMakeLists.txt
- CMakeLists.txt
- Makefile
  • I have a boards directory with multiple folders per one board.
  • Each folder has it's own toolchain.cmake file and CMakeLists.txt file
  • In each CMakeLists.txt in the board folder a library with the name of the folder is added.
  • I have a makefile that iterates for all the boards I want to compile this application for and runs cmake + make for each of the boards. Basically BOARDS=BOARD1 BOARD2 and then all: $(foreach board,$(BOARDS),cmake;make;)
  • cmake is configured with cmake -DCMAKE_TOOLCHAIN_FILE=board/$(BOARD)/toolchain.cmake -DBOARD=$(BOARD)
  • The board/CMakeLists.txt file all does is add_subdirectory(${BOARD}) and add_library(board INTERFACE ${BOARD}).
  • The main CMakeLists.txt does add_subdirectory(boards) and after that link_libraries(boards). That should link all libraries with the board library.
  • Each board directory in turn controls the specific board configuration.
  • If you have multiple project configurations, just act as if they were normal projects and don't care about board. Example: iterate over them inside app/CMakeLists.txt, like so:

foreach(i IN LISTS project1 project2 project3) 
    add_executable(main+${i} main.c app.c)
    target_add_definitions(${i}) 
endforeach()

Alternatively, if ex. there are many macros, you could create another structure projects/{project1,project2,project3} and recreate the same structure as for boards, but without the toolchain.cmake file. Then just iterate foreach project and do target_link_libraries(main+${i} ${i}) that will effectively bring all the macro definitions to main application.

Upvotes: 3

Related Questions