如何在 CMake 中构建共享主函数的两个可执行文件?

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

我有一个项目变得足够大,我想从普通的 Makefile 切换到 CMake。我的项目只包含一个应用程序,但支持两个不同的板(每个板都有一组源文件,一些定义特定于板)。 (简化的)项目结构目前如下所示:

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

可以(目前已经)将所有内容都放在根 CMakeLists.txt 文件中,但我不希望该文件随着项目的扩展而变得庞大.本质上,我想知道如何创建一个项目,允许我为任一板构建可执行文件,其中每个可执行文件可以有一组单独的定义(用于 board.cmain.c 文件)、源文件(在本例中为 board.c,同时共享 main.c),并且可以共享 compile/linker 选项(我想在两个可执行文件之间共享一些通用的编译设置)而无需一个巨大的CMakeLists.txt.

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

CMakeLists.txt 放入 board_a 中,这样 add_library(board_a board.c).

CMakeLists.txt 放入 board_b 中,这样 add_library(board_b board.c).

如果板子有些相似:

  • 在根 CMakeLists.txt 中创建一个 add_executable(exe_board_a main.c) target_link_libraries(exe_board_a PUBLIC board_a)
  • exe_board_b
  • 重复上面的步骤

如果板子不相关,例如需要许多不同的编译器标志或单独的工具链文件甚至不同的编译器:

  • 在执行 target_link_libraries(exe PUBLIC ${USE_BOARD}).

    的根 CMakeLists.txt add_executable(exe main.c)
  • 使用 cmake -DUSE_BOARD=board_acmake -DUSE_BOARD=board_b 编译您的项目两次。使用两个单独的构建目录。双重编译可以使用自定义脚本、根自定义 Makefile 编写脚本,或者您可以研究 cmake-presets.

记住使用 target_* 命令,而不是目录特定命令。

这对于对象库来说还算不错:

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)

你当然可以将 add_library 调用向下移动到子目录中(通过 add_subdirectory),但我在这里使用的技巧是将诸如 defines on as PUBLIC对象库的使用要求,以便它们将在相关应用程序编译时传播到 main.c。

你会在顶层的每个板有一个 add_executable 调用,如果你最终有很多板,你当然可以把它放在一个循环中。

您已经有了一个 src/CMakeLists.txt,所以您已经参与其中了。将您的整体构建设置——依赖项、C 标准版本、全局编译器标志——放在 top-level CMakeLists.txt 中。在子目录中,只放置可执行文件的 CMake 命令,或任何在本地有意义的目标。

(顺便说一句,定义“庞大”?我这里有 top-level CMakeLists,它们在 200-950 行之间,但我也看到了 3000 行的怪物)

就我个人而言,根据此处源布局的最小草图,我会这样做:

  • src/CMakeLists.txt 每个板 执行 add_subdirectory() 命令,例如add_subdirectory(board/board_a)。如果你愿意,set() 一个变量到板名称列表,你可以迭代它。
  • 在每个板子的子目录中,创建一个库——共享的、静态的或OBJECT——以板命名,并包含该板的源代码。例如add_library(board_a OBJECT board.c)
  • 再次回到 src/CMakeLists.txt,对于每个板,将源代码来自 app/ 和 link 的可执行文件添加到为板定义的库中,例如
    add_executable(exe_board_a app/source.c)
    target_link_library(exe_board_a PRIVATE board_a)
    
    如果该可执行文件有特殊注意事项,请将它们也设置在那里。可以从库目标中获取编译标志(然后使用 STATIC,而不是 OBJECT)。

这会将大部分 long-lists-of-sources 和可能的编译标志移动到 per-board CMakeLists.txt 并将中间级别变成一长串“使此可执行文件”。