Wiki
顶层CMake与公共编译选项
本节先讲两个文件:
- 顶层
CMakeLists.txt 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)
作用:
- 指定项目需要的最低 CMake 版本。
- 告诉 CMake 使用对应版本的策略行为。
- 太老的 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)
作用:
- 设置项目名。
- 设置项目版本。
- 声明项目使用哪些语言。
- 初始化一批 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
)
含义:
lib1_src_lib自己需要project_options。- 链接
lib1_src_lib的目标也会得到project_options。 project_warnings只用于编译lib1_src_lib自己,不强迫下游也开启相同 warning。
20.cmake/WindowsRuntime.cmake
这个文件负责安装 MinGW 工具链中找到的常用 GCC 运行库。开发时先保证工具链的 bin/ 位于 PATH 中;验证安装结果时,按模板的安装步骤执行,再整体移动安装目录检查程序能否运行。
日常练习保留这个文件即可。新增第三方库后,还要按依赖自身的安装要求检查运行环境。Linux 继续使用系统运行库和本模板的 RPATH 配置。