Search code examples
c++ccmakeorganization

How can I build two executables that share a main function in CMake?


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.


Solution

  • 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".