跳到正文

Wiki

顶层CMake与公共编译选项

约 12 分钟阅读

本节先讲两个文件:

  1. 顶层 CMakeLists.txt
  2. cmake/ProjectOptions.cmake

这两个文件决定了整个工程的基本身份和公共编译规则。最后再介绍 Windows 安装时使用的 cmake/WindowsRuntime.cmake。

1.顶层 CMakeLists.txt

本模板的顶层文件如下:

cmake_minimum_required(VERSION 3.22)

project(cmake_template VERSION 1.0.0 LANGUAGES C CXX)

include(GNUInstallDirs)
include(CTest)

option(BUILD_SHARED_LIBS "Build shared project libraries" ON)

include("${CMAKE_CURRENT_LIST_DIR}/cmake/ProjectOptions.cmake")

add_subdirectory(src)

include("${CMAKE_CURRENT_LIST_DIR}/cmake/WindowsRuntime.cmake")

if(BUILD_TESTING)
  add_test(NAME eigen_example COMMAND ${PROJECT_NAME})
  set_tests_properties(eigen_example PROPERTIES
    PASS_REGULAR_EXPRESSION "Determinant = -2"
  )
endif()

顶层 CMakeLists.txt 的原则是:只做总控,不堆具体业务和第三方依赖。

2.cmake_minimum_required

cmake_minimum_required(VERSION 3.22)

作用:

  1. 指定项目需要的最低 CMake 版本。
  2. 告诉 CMake 使用对应版本的策略行为。
  3. 太老的 CMake 会直接报错,不会继续配置。

可以填什么:

cmake_minimum_required(VERSION 3.20)
cmake_minimum_required(VERSION 3.22)
cmake_minimum_required(VERSION 3.25)
cmake_minimum_required(VERSION 3.28)

选择建议:

写法 说明
3.10 太老,很多现代 CMake 写法不方便
3.16 Ubuntu 20.04 常见版本,兼容性好
3.22 本模板使用,兼容 Ubuntu 22.04 的 CMake 3.22.1,搭配预设格式版本 3
3.25 使用预设格式版本 6 时需要,本模板当前没有使用其新增功能
3.28 Ubuntu 24.04 常见版本,系统软件源提供 CMake 3.28.3
4.2 Ubuntu 26.04 常见版本,系统软件源提供 CMake 4.2.3

在 Ubuntu 24.04、26.04 上使用较新的 CMake 时,工程仍可保留 VERSION 3.22 的最低要求。只有实际使用了更高版本才支持的功能时,才需要提高最低版本。

最低版本应根据实际使用的功能和验证环境确定。本模板在 CMake 3.22.1 下已验证配置、构建、安装与运行,无需提高到 3.25。调整完整工程的最低要求时,还应同步检查 CMakePresets.json 的格式版本和 cmakeMinimumRequired。

3.project

project(cmake_template VERSION 1.0.0 LANGUAGES C CXX)

作用:

  1. 设置项目名。
  2. 设置项目版本。
  3. 声明项目使用哪些语言。
  4. 初始化一批 CMake 变量。

3.1.项目名

project(cmake_template)

项目名会影响这些变量:

变量 值
PROJECT_NAME cmake_template
CMAKE_PROJECT_NAME 顶层项目名,通常也是 cmake_template

本模板在 src/CMakeLists.txt 中使用:

add_executable(${PROJECT_NAME}
  ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp
)

所以最终可执行文件名就是:

cmake_template

如果把项目名改成:

project(robot_app VERSION 1.0.0 LANGUAGES C CXX)

最终可执行文件就会变成:

robot_app

3.2.VERSION

VERSION 1.0.0

项目版本号。

可以填什么:

VERSION 0.1.0
VERSION 1.0.0
VERSION 2.3.4

CMake 会生成这些变量:

变量 示例值
PROJECT_VERSION 1.0.0
PROJECT_VERSION_MAJOR 1
PROJECT_VERSION_MINOR 0
PROJECT_VERSION_PATCH 0

以后如果做安装包、导出 CMake package、生成版本头文件,这些变量会很有用。

3.3.LANGUAGES

LANGUAGES C CXX

声明项目启用哪些语言。

常见值:

值 说明
C C 语言
CXX C++
CUDA CUDA
ASM 汇编
Fortran Fortran

如果项目只有 C++,也可以写:

project(cmake_template VERSION 1.0.0 LANGUAGES CXX)

本模板保留 C CXX,是为了允许 src/*.c 和 src/*.cpp 都能参与构建。

4.include(GNUInstallDirs)

include(GNUInstallDirs)

作用:加载 GNU 风格的安装目录变量。

常用变量:

变量 常见值 用途
CMAKE_INSTALL_BINDIR bin 可执行文件
CMAKE_INSTALL_LIBDIR lib 或 lib64 动态库、静态库
CMAKE_INSTALL_INCLUDEDIR include 头文件
CMAKE_INSTALL_DATADIR share 数据文件
CMAKE_INSTALL_DOCDIR share/doc/项目名 文档

为什么不用手写:

install(TARGETS app DESTINATION bin)
install(TARGETS lib DESTINATION lib)

因为不同 Linux 发行版可能使用 lib 或 lib64。用 GNUInstallDirs 后,CMake 会根据系统习惯设置。

模板中的使用示例:

install(TARGETS ${PROJECT_NAME}
  RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

库安装:

install(TARGETS ${PREFIX}_src_lib
  LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
  ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
  RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

头文件安装:

install(DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/inc/"
  DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)

5.选择库类型和启用测试

顶层先提供库类型选项:

option(BUILD_SHARED_LIBS "Build shared project libraries" ON)

option 创建一个可以在配置时选择的开关。这里默认开启,Linux 预设也明确设置为 ON;Windows 预设设置为 OFF。后面的库模块不写死库类型,就可以使用这项设置。

顶层还加载:

include(CTest)

CTest 使工程可以用 ctest 执行测试,并提供 BUILD_TESTING 选项。顶层的 eigen_example 测试会运行当前程序,再检查原始 Eigen 示例的输出。它不增加业务源码。

add_subdirectory(src) 后加载的 WindowsRuntime.cmake 用于安装常用 GCC 运行库,后面单独解释。

6.include("${CMAKE_CURRENT_LIST_DIR}/cmake/ProjectOptions.cmake")

include("${CMAKE_CURRENT_LIST_DIR}/cmake/ProjectOptions.cmake")

作用:读取另一个 CMake 文件。

6.1.include(...)

include 会在当前作用域执行指定的 CMake 脚本。

可以写:

include(GNUInstallDirs)
include(cmake/ProjectOptions.cmake)
include("${CMAKE_CURRENT_LIST_DIR}/cmake/ProjectOptions.cmake")

区别:

写法 说明
include(GNUInstallDirs) 加载 CMake 内置模块
include(cmake/ProjectOptions.cmake) 相对路径写法
include("${CMAKE_CURRENT_LIST_DIR}/cmake/ProjectOptions.cmake") 更稳,不受当前工作目录影响

6.2.CMAKE_CURRENT_LIST_DIR

表示当前正在处理的 CMake 文件所在目录。

在顶层 CMakeLists.txt 中,它就是项目根目录。

所以:

"${CMAKE_CURRENT_LIST_DIR}/cmake/ProjectOptions.cmake"

表示:

项目根目录/cmake/ProjectOptions.cmake

7.add_subdirectory(src)

add_subdirectory(src)

作用:进入 src 子目录,继续读取 src/CMakeLists.txt。

可以填什么:

add_subdirectory(src)
add_subdirectory(test)
add_subdirectory(third_party/some_lib)

常见写法:

add_subdirectory(<源码目录>)

也可以指定构建目录:

add_subdirectory(src src_build)

但一般不需要。

顶层只写:

add_subdirectory(src)

表示具体的可执行文件、库、依赖,都交给 src 下面的 CMake 文件管理。

8.cmake/ProjectOptions.cmake

本模板内容如下:

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# Keep the Windows executable in a predictable location.
if(WIN32)
  set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
endif()

add_library(project_options INTERFACE)
target_compile_features(project_options INTERFACE cxx_std_20)

add_library(project_warnings INTERFACE)
target_compile_options(project_warnings INTERFACE
  "$<$<COMPILE_LANG_AND_ID:C,GNU,Clang>:-Wall;-Wextra;-Wpedantic>"
  "$<$<COMPILE_LANG_AND_ID:CXX,GNU,Clang>:-Wall;-Wextra;-Wpedantic>"
)

这个文件只放项目公共选项,不放 Eigen、OpenCV 等第三方依赖。

9.CMAKE_CXX_STANDARD

set(CMAKE_CXX_STANDARD 20)

设置默认 C++ 标准。

常见值:

值 标准
11 C++11
14 C++14
17 C++17
20 C++20
23 C++23

本模板默认使用 C++20:

set(CMAKE_CXX_STANDARD 20)

如果想用 C++23:

set(CMAKE_CXX_STANDARD 23)
target_compile_features(project_options INTERFACE cxx_std_23)

注意两个地方最好一起改。

CMake 版本和 C++ 标准版本需要分别判断。CMake 3.22 已能识别 cxx_std_20,但这表示选择相应的编译器标准模式,不代表编译器和标准库已经实现全部 C++20 功能;具体说明见 CMake 编译特性文档。模板当前的 Eigen 示例已使用 GCC 11.4 验证,后续程序用到的新语法和标准库功能仍需对应的编译器支持。

10.CMAKE_CXX_STANDARD_REQUIRED

set(CMAKE_CXX_STANDARD_REQUIRED ON)

作用:要求编译器必须支持指定的 C++ 标准。

可以填什么:

值 说明
ON 必须使用指定标准,不支持就报错
OFF 不支持时可能退回较低标准

模板建议写 ON,使不支持所选标准模式的编译器明确报错,避免静默退回旧标准。

11.CMAKE_CXX_EXTENSIONS

set(CMAKE_CXX_EXTENSIONS OFF)

作用:控制是否使用编译器扩展标准。

可以填什么:

值 编译选项倾向 说明
ON gnu++20 允许 GNU 扩展
OFF c++20 使用所选标准的非 GNU 扩展模式

表中以模板默认的 C++20 为例,具体参数拼写由 CMake 根据编译器选择。Linux-only 项目也建议先写 OFF,这样代码更接近标准 C++。

12.C 标准相关选项

set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)

含义和 C++ 对应选项类似。

常见 C 标准:

值 标准
99 C99
11 C11
17 C17
23 C23

如果项目没有 .c 文件,也可以只启用 C++:

project(cmake_template VERSION 1.0.0 LANGUAGES CXX)

然后删除 C 标准相关设置。

13.CMAKE_EXPORT_COMPILE_COMMANDS

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

作用:生成 compile_commands.json。

可以填:

值 说明
ON 生成
OFF 不生成

这个选项在 CMakePresets.json 中也写了:

"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"

两个地方都写不冲突。preset 的好处是 IDE 和命令行都能明确看到这个配置;ProjectOptions.cmake 的好处是即使不用 preset,也默认开启。

14.Windows 的运行文件输出目录

if(WIN32)
  set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
endif()

WIN32 表示当前构建目标是 Windows,也包含 64 位 Windows。这里在创建各 target 之前,统一指定运行文件的输出位置。

Windows 主程序会进入 build/windows-debug/bin/。两个项目库按预设选择静态链接;Linux 不进入这个输出目录分支,保留原有构建目录布局。

15.add_library(project_options INTERFACE)

add_library(project_options INTERFACE)

创建一个 interface target。

INTERFACE target 没有源码,不会生成 .a 或 .so。它只用来携带编译特性、宏定义、include 路径、链接依赖等"使用要求"。

add_library 常见类型:

类型 是否有源码 是否生成产物 用途
STATIC 有 .a 静态库
SHARED 有 .so 动态库
MODULE 有 插件式动态模块
OBJECT 有 .o 集合 复用对象文件
INTERFACE 无 无 传递配置

本模板用:

project_options

保存 C++ 标准要求。

16.target_compile_features

target_compile_features(project_options INTERFACE cxx_std_20)

作用:声明使用这个 target 的目标至少需要 C++20 标准模式。

可以填什么:

写法 说明
cxx_std_11 至少 C++11
cxx_std_14 至少 C++14
cxx_std_17 至少 C++17
cxx_std_20 至少 C++20
cxx_std_23 至少 C++23

为什么这里又写了一遍 C++20?

set(CMAKE_CXX_STANDARD 20)

是当前目录及其子目录中后续创建目标的默认值。

target_compile_features(project_options INTERFACE cxx_std_20)

是目标级要求。现代 CMake 更推荐用目标级要求,因为它能随着 target 链接关系传播。

17.add_library(project_warnings INTERFACE)

add_library(project_warnings INTERFACE)

创建一个专门保存 warning 选项的 interface target。

这样每个 target 可以选择是否链接:

target_link_libraries(my_target
  PRIVATE
    project_warnings
)

18.target_compile_options

target_compile_options(project_warnings INTERFACE
  "$<$<COMPILE_LANG_AND_ID:C,GNU,Clang>:-Wall;-Wextra;-Wpedantic>"
  "$<$<COMPILE_LANG_AND_ID:CXX,GNU,Clang>:-Wall;-Wextra;-Wpedantic>"
)

作用:给目标添加编译选项。

本模板选项:

选项 作用
-Wall 开启一组常见警告
-Wextra 开启更多警告
-Wpedantic 提醒不符合标准的写法

外面的 $<...> 是生成器表达式。第一行只在用 GNU 或 Clang 编译 C 源文件时添加这些参数,第二行对应 C++ 源文件。Windows 的 MinGW-w64 GCC 同样属于 GNU,因此两种平台都能使用这一组警告选项。表达式用引号括起来,让其中用分号分隔的选项作为一个整体交给 CMake 处理。参见 CMake 生成器表达式说明。

常见可追加选项:

选项 说明
-Wconversion 类型转换可能丢失信息时警告
-Wshadow 变量名遮蔽时警告
-Werror 把 warning 当 error

不建议模板默认加 -Werror。因为不同编译器、不同第三方头文件可能产生不同 warning,初学阶段容易被卡住。

19.PUBLIC、PRIVATE、INTERFACE 的直觉

后面的 CMake 文件会大量出现:

PUBLIC
PRIVATE
INTERFACE

它们控制"属性是否传递给使用者"。

关键字 自己用 传给别人
PRIVATE 是 否
PUBLIC 是 是
INTERFACE 否 是

例如:

target_link_libraries(lib1_src_lib
  PUBLIC
    project_options
  PRIVATE
    project_warnings
)

含义:

  1. lib1_src_lib 自己需要 project_options。
  2. 链接 lib1_src_lib 的目标也会得到 project_options。
  3. project_warnings 只用于编译 lib1_src_lib 自己,不强迫下游也开启相同 warning。

20.cmake/WindowsRuntime.cmake

这个文件负责安装 MinGW 工具链中找到的常用 GCC 运行库。开发时先保证工具链的 bin/ 位于 PATH 中;验证安装结果时,按模板的安装步骤执行,再整体移动安装目录检查程序能否运行。

日常练习保留这个文件即可。新增第三方库后,还要按依赖自身的安装要求检查运行环境。Linux 继续使用系统运行库和本模板的 RPATH 配置。