2. 第2例
为什么需要 CMake Presets
同一个 C++ 工程,在开发机上要 Debug + GCC,在 CI 上要 Release + Clang,在 Windows 上要 MSVC。传统做法是给每种组合写一条 cmake -S . -B build-xxx -DCMAKE_BUILD_TYPE=... -DCMAKE_CXX_COMPILER=... 命令,参数散落在 README、脚本和 CI 配置里,容易写错、难以继承、无法被 IDE 直接识别。CMake Presets(CMake 3.19+,CMakePresets.json)把这些组合固化成可继承的 JSON 预设,一条 cmake --preset=xxx 即可完成配置。
预设的三层结构
Presets 文件由三个顶层数组组成,它们通过 inherits 形成继承链,子预设只写差异字段:
configurePresets:定义生成器、二进制目录、工具链、缓存变量(cacheVariables)。buildPresets:通过configurePreset绑定某个配置预设,指定构建目标与并行度。testPresets:通过configurePreset绑定配置预设,指定 CTest 的输出与过滤条件。
关键字段:hidden: true 的预设只作为基类,不出现在 cmake --list-presets 中;binaryDir 支持 ${presetName} 等宏,让每个预设拥有独立构建目录,互不污染。
完整 CMakePresets.json
下面是一个可直接使用的示例,工程根目录下有一个 CMakeLists.txt 定义目标 app。预设文件放在工程根目录,与 CMakeLists.txt 同级。
{
"version": 6,
"cmakeMinimumRequired": {
"major": 3,
"minor": 23,
"patch": 0
},
"configurePresets": [
{
"name": "base",
"hidden": true,
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/${presetName}",
"cacheVariables": {
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON",
"CMAKE_CXX_STANDARD": "17",
"CMAKE_CXX_STANDARD_REQUIRED": "ON"
}
},
{
"name": "debug-gcc",
"inherits": "base",
"displayName": "Debug (GCC)",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_CXX_COMPILER": "g++",
"CMAKE_C_COMPILER": "gcc"
}
},
{
"name": "release-gcc",
"inherits": "base",
"displayName": "Release (GCC)",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"CMAKE_CXX_COMPILER": "g++",
"CMAKE_C_COMPILER": "gcc"
}
},
{
"name": "release-clang",
"inherits": "base",
"displayName": "Release (Clang)",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"CMAKE_CXX_COMPILER": "clang++",
"CMAKE_C_COMPILER": "clang"
}
}
],
"buildPresets": [
{
"name": "build-debug",
"configurePreset": "debug-gcc",
"jobs": 8
},
{
"name": "build-release",
"configurePreset": "release-gcc",
"jobs": 8
},
{
"name": "build-clang",
"configurePreset": "release-clang",
"jobs": 8
}
],
"testPresets": [
{
"name": "test-debug",
"configurePreset": "debug-gcc",
"output": { "outputOnFailure": true }
},
{
"name": "test-release",
"configurePreset": "release-gcc",
"output": { "outputOnFailure": true }
}
]
}
验证预设继承与缓存变量生效
配置完成后,用以下方式确认继承链和缓存变量确实写入了 CMakeCache.txt:
- 列出所有可见预设:
cmake --list-presets,应只看到debug-gcc、release-gcc、release-clang,base因hidden: true不出现。 - 执行配置:
cmake --preset=debug-gcc,观察输出中生成器为 Ninja,构建目录为build/debug-gcc。 - 检查缓存变量:
grep -E "CMAKE_BUILD_TYPE|CMAKE_CXX_COMPILER|CMAKE_CXX_STANDARD" build/debug-gcc/CMakeCache.txt,应分别得到Debug、g++路径、17。 - 验证继承:
CMAKE_EXPORT_COMPILE_COMMANDS来自base,在debug-gcc中未重复声明,但缓存中应为ON。 - 构建并测试:
cmake --build --preset=build-debug,再ctest --preset=test-debug。
如果使用 CMake 3.21+,可以在
configurePresets 中加 "toolchainFile" 指向交叉编译工具链文件,实现嵌入式平台的预设切换。CMake 3.25+ 还支持 packagePresets 和 workflowPresets,可把配置、构建、测试、打包串成一条工作流。常见问题:
binaryDir 中若使用 ${presetName},不同预设会生成不同目录,切换预设时不会互相覆盖缓存;但如果两个预设共用同一 binaryDir 且编译器不同,CMake 会报缓存冲突,必须删除旧目录或改用独立目录。另外,CMAKE_CXX_COMPILER 只在首次配置时生效,修改预设后需清空对应构建目录。快速执行卡片
| 操作 | 命令 | 预期结果 |
|---|---|---|
| 查看可用预设 | cmake --list-presets | 列出 3 个配置预设,不含 base |
| Debug 配置 | cmake --preset=debug-gcc | 生成 build/debug-gcc,缓存含 Debug/g++/17 |
| Release 配置 | cmake --preset=release-gcc | 生成 build/release-gcc,缓存含 Release/g++ |
| Clang 配置 | cmake --preset=release-clang | 生成 build/release-clang,缓存含 clang++ |
| 构建 | cmake --build --preset=build-debug | 在对应目录完成编译 |
| 测试 | ctest --preset=test-debug | 运行 CTest,失败时输出详情 |