24、预设(Presets):CMakePresets.json,配置预设、构建预设、测试预设,CI/CD集成
说实话,CMake 的命令行参数我背了好几年。每次开新项目,都得翻文档查 -DCMAKE_BUILD_TYPE 怎么写。直到 CMake 3.19 引入了 Presets 机制,我才算真正解脱。
Presets 是什么?说白了,就是把常用的 CMake 配置、构建、测试参数,写进一个 JSON 文件里。团队里所有人都用同一套参数,再也不用担心「我机器上能编译啊」这种鬼话了。
CMakePresets.json 的结构
这个文件必须放在项目的根目录。文件名是固定的,就叫 CMakePresets.json。我习惯把它和 CMakeLists.txt 放在一起。
一个典型的文件结构长这样:
{
"version": 3,
"configurePresets": [],
"buildPresets": [],
"testPresets": []
}
version 目前推荐用 3。version 2 也还能用,但少了些新特性。我个人建议直接上 version 3,省得以后升级麻烦。
配置预设(configurePresets)
配置预设是最核心的部分。它决定了 CMake 怎么生成构建系统。
来看一个实际例子:
{
"version": 3,
"configurePresets": [
{
"name": "default",
"displayName": "默认配置",
"description": "Debug 模式,开启测试",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/${presetName}",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_CXX_STANDARD": "17",
"BUILD_TESTING": "ON"
}
},
{
"name": "release",
"displayName": "发布配置",
"description": "Release 模式,优化全开",
"inherits": "default",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"BUILD_TESTING": "OFF"
}
}
]
}
这里有几个关键点:
- binaryDir:构建目录。我用
${sourceDir}/build/${presetName},这样不同预设的构建产物不会混在一起。 - inherits:继承机制。release 预设继承了 default,只覆盖了需要改的变量。避免重复写同样的东西。
- generator:生成器。我推荐 Ninja,跨平台且快。Windows 上也可以用 Visual Studio。
${presetName} 变量来命名构建目录。这样你执行 cmake --preset release 时,产物自动进 build/release 目录,清爽得很。
构建预设(buildPresets)
配置预设搞定了,构建预设就简单多了。它只是告诉 CMake 怎么编译:
{
"buildPresets": [
{
"name": "default",
"configurePreset": "default",
"jobs": 4
},
{
"name": "release",
"configurePreset": "release",
"jobs": 8
}
]
}
注意 configurePreset 字段。它必须指向一个已经定义好的配置预设。构建预设不能独立存在,它依赖于配置预设生成的构建系统。
jobs 是并行编译的线程数。我一般设成 CPU 核心数。但如果你在 CI 环境里,记得别设太高,免得把服务器搞崩了。我曾经在 4 核的 CI 机器上设了 16 个 jobs,结果编译到一半机器直接 OOM 了……嗯,从那以后我就老实了。
测试预设(testPresets)
测试预设控制 ctest 的行为:
{
"testPresets": [
{
"name": "default",
"configurePreset": "default",
"output": {
"outputOnFailure": true
},
"execution": {
"noTestsAction": "error",
"stopOnFailure": false
}
}
]
}
我个人最常用的是 outputOnFailure。测试失败时直接输出详细信息,省得再跑一遍。还有 noTestsAction 设成 error,如果没找到测试用例就直接报错——这能帮你发现测试没注册的问题。
configurePreset 必须和构建预设指向同一个配置预设。否则 ctest 找不到构建产物,会报错。我见过有人把测试预设指向了 release,但构建用的是 default,结果折腾了半天。
CI/CD 集成
Presets 最大的价值,其实在 CI/CD 里。你想想看,本地开发和 CI 用同一套配置,多省心。
在 GitHub Actions 里,可以这样用:
# .github/workflows/build.yml
- name: Configure
run: cmake --preset default
- name: Build
run: cmake --build --preset default
- name: Test
run: ctest --preset default
就这么三行。不需要在 CI 配置文件里重复写 -DCMAKE_BUILD_TYPE=Debug 之类的参数了。所有配置都在 CMakePresets.json 里统一管理。
GitLab CI 也类似:
build:
script:
- cmake --preset release
- cmake --build --preset release
Jenkins 的话,直接在 Pipeline 里调用同样的命令就行。
避坑指南
用 Presets 时,有几个坑我踩过:
- 路径问题:
binaryDir里的${sourceDir}是 CMake 自动识别的源码根目录。别自己拼路径,容易出错。 - 版本兼容:CMake 3.19 才开始支持 Presets。如果你的 CI 镜像里 CMake 版本太老,记得升级。我见过有人用 Ubuntu 18.04 默认的 CMake 3.10,结果 Presets 完全不认。
- JSON 语法:JSON 不允许注释。别想着在文件里写
//或/* */,CMake 会直接报错。如果非要写说明,用description字段。 - 继承覆盖:子预设的
cacheVariables会完全覆盖父预设的同名变量,不是合并。如果你只想改一个变量,记得把其他变量也带上,或者用cacheVariables的${inherits}机制。
知识体系总览
下面这张图,帮你理清 Presets 的整体结构:
Presets 这东西,说白了就是「一次配置,到处使用」。本地开发用 --preset default,CI 用 --preset release,测试用 --preset test。团队里所有人都用同一个 JSON 文件,参数统一,再也不用争论「你那个编译选项不对」了。
我个人觉得,Presets 是 CMake 近年来最实用的功能之一。它把配置从 CMakeLists.txt 和 CI 脚本里解放出来,让项目管理变得更清晰。如果你还没用上,我建议你从下个项目开始试试。嗯,相信我,用了就回不去了。
公众号:蓝海资料掘金营,微信 deep3321