Home > front end >  How can I build two executables that share a main function in CMake?
How can I build two executables that share a main function in CMake?

Time:01-31

I have a project that's becoming large enough that I want to switch to from a plain Makefile to CMake. My project contains only one application, but supports two different boards (each board has a set of source files, and some defines specific to the board). The (simplified) project structure currently looks like this:

CMakeLists.txt
src/
├─ board/
│  ├─ board_a/
│  │  ├─ board.c
│  ├─ board_b/
│  │  ├─ board.c
├─ app/
│  ├─ main.c
CMakeLists.txt

I could (and currently have) just put everything in the root CMakeLists.txt file, but I don't want that file to become enormous as the project expands. Essentially, I am wondering how I can create a project which allows me to build an executable for either board where each executable can have a separate set of defines (that are used in both the board.c and main.c files), source files (board.c in this case, while sharing the main.c), and can share compile/linker options (I want to share some common compilation settings between both executables) without having one enormous CMakeLists.txt.

CodePudding user response:

You already have a src/CMakeLists.txt, so you are part of the way there. Put your overall build settings -- dependencies, C standard version, global compiler flags -- in the top-level CMakeLists.txt. In the subdirectories, put only your CMake commands for the executables, or whatever target makes sense locally.

(As an aside, define "enormous"? I've got top-level CMakeLists here that are anywhere between 200-950 lines, but I've seen 3000-line monstrosities as well)

Personally, from the minimal sketch of the source layout here, I'd do:

  • src/CMakeLists.txt does an add_subdirectory() command for each board, e.g. add_subdirectory(board/board_a) . If you like, set() a variable to a list of board names and you can iterate over it.
  • In each board's subdirectory, create a library -- shared, static, or OBJECT -- named after the board, with the sources for that board. For instance add_library(board_a OBJECT board.c)
  • Back in src/CMakeLists.txt again, for each board, add an executable with the source from app/ and link to the library defined for the board, like
    add_executable(exe_board_a app/source.c)
    target_link_library(exe_board_a PRIVATE board_a)
    
    If there are special considerations for that executable, set them there as well. Compile flags can be obtained from the library targets (use STATIC then, not OBJECT).

This moves most of the long-lists-of-sources and potentially compile flags to the per-board CMakeLists.txt and turns the intermediate level into a long list of "make this executable".

CodePudding user response:

how I can create a project which allows me to build an executable for either board where each executable can have a separate set of defines

Put CMakeLists.txt inside board_a that does add_library(board_a board.c).

Put CMakeLists.txt inside board_b that does add_library(board_b board.c).

If the boards are somewhat similar:

  • In the root CMakeLists.txt create a add_executable(exe_board_a main.c) that target_link_libraries(exe_board_a PUBLIC board_a).
  • Repeat above for exe_board_b

If the boards are unrelated, like need many different compiler flags or separate toolchain files or even different compilers:

  • In the root CMakeLists.txt add_executable(exe main.c) that does target_link_libraries(exe PUBLIC ${USE_BOARD}).

  • Compile your project twice with cmake -DUSE_BOARD=board_a and cmake -DUSE_BOARD=board_b. Use two separate build directories. The double compilation can be scripted with a custom script, a root custom Makefile, or you can research cmake-presets.

Remember to use target_* commands, not the directory specific commands.

CodePudding user response:

This isn't too bad with object libraries:

cmake_minimum_required(VERSION 3.22)
project(boards LANGUAGES C)

add_library(board_a OBJECT src/board_a/board.c)
target_compile_definitions(
  board_a PUBLIC "defines for board_a.c and main.c")

add_library(board_b OBJECT src/board_b/board.c)
target_compile_definitions(
  board_b PUBLIC "defines for board_b.c and main.c")

add_executable(main_a src/app/main.c)
target_link_libraries(main_a PRIVATE board_a)

add_executable(main_b src/app/main.c)
target_link_libraries(main_b PRIVATE board_b)

You could of course move the add_library calls down into subdirectories (via add_subdirectory), but the trick I'm using here is to put things like defines on as PUBLIC usage requirements for the object libraries, so that they will propagate to main.c when the associated application compiles.

You would have one add_executable call per board in the top level which you could of course place in a loop if you wind up with many boards.

  •  Tags:  
  • Related