98、CMake Presets:使用CMakePresets.json标准化构建配置
说实话,我见过太多CMake项目了。有的项目里,构建脚本写得跟天书似的,每次换人维护都得折腾半天。我自己也踩过不少坑——最典型的就是,团队里每个人都在自己的命令行里敲不同的CMake参数,结果出来的构建产物千奇百怪,调试起来简直要命。
嗯,CMake Presets就是来解决这个问题的。它用一份CMakePresets.json文件,把所有的构建配置标准化。说白了,就是把你平时在命令行里敲的那些参数,全部写进一个JSON文件里,大家统一用。
为什么需要CMake Presets?
我刚开始用CMake的时候,项目还小,就我一个人。后来团队扩大到十几个人,问题就来了:
- 有人用
-DCMAKE_BUILD_TYPE=Debug,有人用-DCMAKE_BUILD_TYPE=Release - 有人开了
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON,有人没开 - 有人用Ninja,有人用Makefiles
每次排查问题,都得先问一句:「你用的什么参数?」你说烦不烦?
CMake Presets就是官方给出的标准化方案。从CMake 3.19开始支持,3.21之后基本稳定。我个人建议,新项目直接上Presets,老项目也尽快迁移。
CMakePresets.json的基本结构
先看一个最简单的例子:
{
"version": 3,
"configurePresets": [
{
"name": "default",
"displayName": "默认配置",
"description": "使用Ninja构建,Debug模式",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/default",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
}
}
]
}
这个文件放在项目的根目录下。然后你只需要敲:
cmake --preset default
CMake就会自动读取CMakePresets.json,按照default这个预设来配置项目。你看,是不是清爽多了?
配置预设(configurePresets)详解
配置预设是最核心的部分。它控制的是cmake -S . -B build这一步的行为。我一般会定义几个常用的:
| 预设名称 | 用途 | 典型参数 |
|---|---|---|
| debug | 日常开发调试 | Debug模式,开启编译命令导出 |
| release | 发布版本构建 | Release模式,优化等级O2 |
| ci | 持续集成环境 | Release模式,开启测试,静态分析 |
| coverage | 代码覆盖率检查 | Debug模式,开启覆盖率标志 |
举个例子,我项目里常用的debug预设:
{
"name": "debug",
"displayName": "Debug构建",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/debug",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_CXX_FLAGS_DEBUG": "-g -O0 -fsanitize=address",
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
},
"environment": {
"CC": "clang",
"CXX": "clang++"
}
}
这里有个小技巧——${sourceDir}和${sourceParentDir}这两个变量,CMake会自动替换成实际的源码目录路径。我刚开始用的时候还傻乎乎地写绝对路径,后来才发现有这好东西。
构建预设(buildPresets)
配置预设搞定之后,构建预设就简单多了。它对应的是cmake --build build这一步:
{
"buildPresets": [
{
"name": "debug",
"displayName": "Debug构建",
"configurePreset": "debug",
"jobs": 8,
"targets": ["my_app", "my_tests"]
},
{
"name": "release",
"displayName": "Release构建",
"configurePreset": "release",
"jobs": 8
}
]
}
使用的时候:
cmake --build --preset debug
它会自动找到对应的配置预设,然后执行构建。你想想看,以前你得先cmake -S . -B build/debug -G Ninja -DCMAKE_BUILD_TYPE=Debug,再cmake --build build/debug --target my_app -j8,现在一行搞定。
测试预设(testPresets)
测试预设是CMake 3.20之后加入的。它控制ctest的行为:
{
"testPresets": [
{
"name": "unit",
"displayName": "单元测试",
"configurePreset": "debug",
"output": {
"outputOnFailure": true
},
"execution": {
"noTestsAction": "error",
"stopOnFailure": false
}
}
]
}
运行测试:
ctest --preset unit
我曾经在一个项目里,测试脚本写得乱七八糟,每次跑测试都得翻文档看参数。后来统一用Presets管理,新同事来了直接ctest --preset all就完事,省心多了。
预设的继承与覆盖
这个功能很实用。你可以定义一个基础预设,然后其他预设继承它:
{
"configurePresets": [
{
"name": "base",
"hidden": true,
"generator": "Ninja",
"cacheVariables": {
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
}
},
{
"name": "debug",
"inherits": "base",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "release",
"inherits": "base",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"CMAKE_CXX_FLAGS": "-O2"
}
}
]
}
注意base预设设置了"hidden": true,这样它不会出现在cmake --list-presets的输出里,只作为模板使用。这个设计我觉得挺巧妙的,避免了用户误用。
条件预设与平台适配
不同平台可能需要不同的配置。CMake Presets支持条件判断:
{
"configurePresets": [
{
"name": "windows-msvc",
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Windows"
},
"generator": "Visual Studio 17 2022",
"architecture": "x64"
},
{
"name": "linux-gcc",
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Linux"
},
"generator": "Ninja",
"cacheVariables": {
"CMAKE_C_COMPILER": "gcc",
"CMAKE_CXX_COMPILER": "g++"
}
}
]
}
这样,在Windows上执行cmake --preset default时,会自动匹配到MSVC的预设;在Linux上则匹配到GCC的预设。跨平台项目用这个,简直不要太爽。
避坑指南
我曾经踩过的坑:
- 版本号写错:CMake Presets的version字段必须写对。version 2支持CMake 3.19-3.20,version 3支持3.21+。我一开始写了version 2,结果3.21的新特性用不了,排查了半天。
- 路径分隔符:在Windows上,路径分隔符要用正斜杠
/,不要用反斜杠\。JSON里反斜杠是转义字符,写错了会解析失败。 - 环境变量覆盖:Presets里设置的环境变量会覆盖系统环境变量。我有一次在Presets里设了
CC=gcc,结果系统里明明装了clang,构建时却一直用gcc,找了半天才发现是Presets的问题。
知识体系总览
下面这张图,是我自己总结的CMake Presets核心结构。你看一眼就能明白各个部分的关系:
实际项目中的最佳实践
根据我自己的经验,总结几条实用的建议:
我的个人习惯:
- 统一命名规范:所有预设名称都用小写字母,用连字符分隔。比如
debug-clang、release-gcc,一看就明白。 - 不要写死路径:尽量用
${sourceDir}、${sourceParentDir}这些变量,别写绝对路径。否则换台机器就废了。 - 版本控制:
CMakePresets.json一定要提交到Git里。它是项目的一部分,不是个人配置。 - 配合CI使用:在CI脚本里直接用
cmake --preset ci,这样本地和CI的构建配置完全一致,再也不用担心「在我机器上能跑」的问题了。
嗯,关于CMake Presets,核心内容就这些了。说白了,它就是一份标准化的构建配置文件,让团队协作更顺畅,让跨平台构建更省心。如果你还在手动敲CMake参数,我建议你赶紧试试Presets——相信我,用上了就回不去了。
公众号:蓝海资料掘金营,微信deep3321