24、预设文件:CMakePresets.json。如何统一团队的构建配置,简化命令行操作。
说实话,CMake 的命令行参数我记了好几年都没记全。什么 -DCMAKE_BUILD_TYPE=Release、-G Ninja、-DCMAKE_INSTALL_PREFIX=...,每次新开一个项目都要翻历史记录。更头疼的是,团队里五个人,五个人的构建方式都不一样——有人用 Debug,有人用 Release,有人忘了开测试开关。
后来 CMake 3.19 引入了 CMakePresets.json,我一看就拍大腿:这不就是我一直想要的东西吗?说白了,它就是一份构建配置的「模板文件」,把所有的 -D 参数、生成器、构建类型全部写进 JSON 里。团队成员只要 cmake --preset debug 就完事了,不用再背那一长串参数。
Presets 文件长什么样?
先看一个最简单的例子。我在项目根目录下放一个 CMakePresets.json:
{
"version": 3,
"configurePresets": [
{
"name": "debug",
"displayName": "Debug 构建",
"description": "带调试信息的构建,开启测试",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/debug",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"BUILD_TESTING": "ON"
}
},
{
"name": "release",
"displayName": "Release 构建",
"description": "优化后的发布版本",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/release",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"BUILD_TESTING": "OFF"
}
}
]
}
有了这个文件,团队成员只需要执行:
cmake --preset debug
cmake --build --preset debug
嗯,就这么简单。不用再纠结 -DCMAKE_BUILD_TYPE 拼没拼对,也不用担心有人忘了加 -G Ninja 导致生成器不一致。
为什么我强烈推荐用 Presets?
我在一个十几人的 C++ 团队里待过,那时候没有 Presets,每个人都在自己的 build.sh 里写一堆参数。结果呢?有人用 Visual Studio 生成器,有人用 Ninja,有人把安装路径写死了 /usr/local,CI 上又用另一套参数。每次排查构建问题,都要先问一句「你是怎么配置的?」
Presets 解决了三个核心痛点:
- 统一性:一份 JSON 文件,所有人都用同一套配置,不会出现「我本地能编译,你那里不行」的尴尬。
- 可读性:
displayName和description字段让每个预设的用途一目了然。新同事看一眼就知道该用哪个。 - 可继承:你可以定义一个基础预设,然后其他预设继承它,只覆盖需要改的字段。后面我会讲这个。
核心思想:Presets 把「构建配置」从人的脑子里、从 shell 脚本里,搬到了一个结构化的 JSON 文件中。机器可读,人也看得懂。
Presets 的完整结构
Presets 文件支持四种预设类型,不过日常最常用的是前两种:
| 预设类型 | 作用 | 对应命令 |
|---|---|---|
configurePresets |
配置阶段的参数(生成器、缓存变量、二进制目录) | cmake --preset <name> |
buildPresets |
构建阶段的参数(并行数、目标、配置类型) | cmake --build --preset <name> |
testPresets |
测试阶段的参数(CTest 选项、过滤条件) | ctest --preset <name> |
packagePresets |
打包阶段的参数(CPack 选项) | cpack --preset <name> |
我个人习惯把 configurePresets 和 buildPresets 配对使用。比如上面那个 debug 预设,我通常会再配一个对应的 build 预设:
{
"version": 3,
"configurePresets": [
{
"name": "debug",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/debug",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"BUILD_TESTING": "ON"
}
}
],
"buildPresets": [
{
"name": "debug",
"configurePreset": "debug",
"jobs": 8
}
]
}
这样 cmake --build --preset debug 就会自动使用 debug 配置阶段的参数,并且并行 8 个任务。你想想看,是不是比手动敲 -j8 省心多了?
继承与条件:让 Presets 更灵活
团队里不同平台、不同编译器怎么办?Presets 支持 inherits 和 condition 字段。我举个例子:
{
"version": 3,
"configurePresets": [
{
"name": "base",
"hidden": true,
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/${presetName}",
"cacheVariables": {
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
}
},
{
"name": "linux-debug",
"inherits": "base",
"displayName": "Linux Debug",
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Linux"
},
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "windows-debug",
"inherits": "base",
"displayName": "Windows Debug",
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Windows"
},
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
}
]
}
这里我定义了一个 base 预设,设置了 "hidden": true,意思是它不会出现在 --list-presets 的列表里,只作为基类被继承。然后 linux-debug 和 windows-debug 分别继承它,并添加了平台条件。
为什么要这么做?因为不同平台的生成器可能不一样。Windows 上你可能想用 Visual Studio,Linux 上用 Ninja。通过 condition,Presets 会自动匹配当前平台,用户只需要执行 cmake --preset debug,CMake 会自动选择正确的预设。
小技巧:${sourceDir}、${sourceParentDir}、${presetName} 这些变量可以在 Presets 中使用。我经常用 ${sourceDir}/build/${presetName} 来组织构建目录,这样每个预设都有自己的独立目录,互不干扰。
避坑指南:我曾经踩过的坑
Presets 虽然好用,但有几个地方容易翻车:
- 版本号别写错:
"version": 3是目前最常用的版本。如果你写了version: 2,有些新特性(比如condition)就用不了。我曾经因为这个排查了半天,最后发现是版本号写低了。 - 路径用正斜杠:即使在 Windows 上,
binaryDir里的路径也要用/,不要用\。CMake 内部会帮你转换。 - hidden 预设别忘了加 displayName:虽然 hidden 预设不会显示在列表中,但如果你在继承时没覆盖
displayName,用户执行--list-presets时可能会看到奇怪的空白名字。 - 条件表达式别嵌套太深:
condition支持and、or、not组合,但嵌套超过三层就很难读了。我建议最多两层,复杂条件拆成多个预设。
用 SVG 理清 Presets 的核心逻辑
下面这张图展示了 Presets 的工作流程,从 JSON 文件到最终构建:
从这张图可以看得很清楚:Presets 文件定义了多种预设类型,每种类型内部支持继承、条件、变量展开等特性,最终用户只需要一个 --preset 参数就能完成配置或构建。
团队协作中的最佳实践
我在实际项目中总结了几条经验,分享给你:
- 把 CMakePresets.json 纳入版本控制。这是团队的「构建契约」,所有人都应该使用同一份。如果有平台差异,用
condition处理,而不是每个人改自己的 JSON。 - 提供一份 CMakeUserPresets.json。这个文件不会被版本控制(建议加到
.gitignore里),允许开发者覆盖某些参数。比如有人想用Unix Makefiles而不是 Ninja,可以在用户预设里改。 - 命名要有规律。我习惯用
<平台>-<配置>的格式,比如linux-debug、windows-release。不要用mybuild、test1这种让人摸不着头脑的名字。 - CI 也用它。在 GitHub Actions 或 GitLab CI 里,直接
cmake --preset ci-linux,这样本地和 CI 的构建配置完全一致,再也不会出现「CI 上编译不过,本地却好好的」这种问题了。
注意:CMakePresets.json 的 version 字段决定了支持的语法。如果你用了 version: 6 的新特性,但团队里有人用的 CMake 版本太低,就会报错。我建议统一 CMake 版本,或者至少保证最低版本 3.23 以上,这样大部分特性都能用。
好了,关于 Presets 的核心内容就这些。说白了,它就是 CMake 官方给的一个「配置标准化」方案。你想想看,以前我们要写一堆 shell 脚本、Makefile 封装、甚至 Python 脚本来管理构建参数,现在一个 JSON 文件全搞定。而且它是 CMake 原生支持的,不需要任何外部工具。
我个人觉得,Presets 是 CMake 3.19 之后最值得升级的特性之一。如果你团队还在用老版本的 CMake,我建议尽快升级。省下来的时间,足够你喝好几杯咖啡了。