配置预设(configurePresets):定义编译器、缓存变量、工具链等
配置预设,说白了就是告诉 CMake:「嘿,这次构建你想怎么配置?」。它管的是编译前的那些事——用什么编译器、开哪些缓存变量、要不要指定工具链文件。我个人觉得,这是整个 Presets 体系里最核心的一块。
为什么这么说?你想想看,一个项目从拉下来到能跑,最头疼的就是配置这一步。每个人环境不一样,编译器版本不同,依赖路径各异。配置预设就是把这些差异统一管理起来,让团队所有人都用同一套规则。
配置预设的核心字段
一个典型的 configurePreset 长这样:
{
"version": 6,
"configurePresets": [
{
"name": "default",
"displayName": "默认配置",
"description": "使用 Ninja 生成器,Debug 模式",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/${presetName}",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_CXX_STANDARD": "17"
}
}
]
}
嗯,这里要注意几个关键字段:
- name:预设的名字,后面引用时用这个
- generator:生成器,比如 Ninja、Unix Makefiles
- binaryDir:构建目录,我习惯用
${sourceDir}/build/${presetName},这样不同预设互不干扰 - cacheVariables:缓存变量,相当于你在 cmake 命令行里写的
-DXXX=YYY
编译器与工具链的指定
我在项目中遇到过最坑的事:同事用 GCC 编译,我用 Clang,结果链接阶段各种符号找不到。后来统一用配置预设指定编译器,问题就解决了。
指定编译器有两种方式:
方式一:通过 cacheVariables 指定
{
"name": "clang-debug",
"cacheVariables": {
"CMAKE_C_COMPILER": "/usr/bin/clang-14",
"CMAKE_CXX_COMPILER": "/usr/bin/clang++-14",
"CMAKE_BUILD_TYPE": "Debug"
}
}
方式二:通过 toolchainFile 指定
{
"name": "arm-cross",
"cacheVariables": {
"CMAKE_TOOLCHAIN_FILE": "${sourceDir}/cmake/toolchains/arm-gcc.cmake"
}
}
我个人更推荐第二种方式。为什么?因为工具链文件里可以封装更多东西——编译器路径、链接器标志、系统根目录等等。你想想看,如果每个变量都写在 Presets 里,那 Presets 文件会变得又臭又长。
缓存变量的高级用法
缓存变量不只是设个值那么简单。有些变量是 CMake 内部定义的,有些是你自己项目定义的。我给大家列个常用清单:
| 变量名 | 说明 | 常用值 |
|---|---|---|
| CMAKE_BUILD_TYPE | 构建类型 | Debug / Release / RelWithDebInfo |
| CMAKE_CXX_STANDARD | C++ 标准 | 17 / 20 / 23 |
| CMAKE_EXPORT_COMPILE_COMMANDS | 导出编译命令(给 clangd 用) | ON |
| BUILD_SHARED_LIBS | 是否构建动态库 | ON / OFF |
| CMAKE_INSTALL_PREFIX | 安装路径 | /usr/local 或自定义 |
环境变量与条件逻辑
配置预设还支持环境变量的设置。这个在 CI/CD 场景下特别有用:
{
"name": "ci-release",
"environment": {
"CC": "gcc-12",
"CXX": "g++-12",
"CFLAGS": "-O2 -march=native"
},
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
}
另外,你还可以用 condition 字段做条件判断。比如只在 Linux 上生效的预设:
{
"name": "linux-only",
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Linux"
},
"generator": "Ninja",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
}
我曾经在跨平台项目里用过这个特性。Windows 上用 Visual Studio 生成器,Linux 上用 Ninja,macOS 上用 Xcode。一个 Presets 文件搞定三个平台,团队里再也没人抱怨「我这边编译不过」了。
继承与复用
配置预设支持继承。你可以定义一个基础预设,然后其他预设继承它:
{
"name": "base",
"hidden": true,
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/${presetName}",
"cacheVariables": {
"CMAKE_CXX_STANDARD": "20",
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
}
},
{
"name": "debug",
"inherits": "base",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"ENABLE_DEBUG_LOG": "ON"
}
},
{
"name": "release",
"inherits": "base",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"ENABLE_DEBUG_LOG": "OFF"
}
}
注意那个 "hidden": true。它表示这个预设不会出现在可用列表里,只作为基类被继承。这个设计很巧妙,对吧?
知识体系结构图
下面这张图总结了配置预设的核心逻辑:
避坑指南
- 路径问题:binaryDir 里用了绝对路径,结果别人拉下来直接报错。记住用
${sourceDir}和${presetName}变量。 - 变量覆盖:继承时如果父预设和子预设定义了同一个变量,子预设会覆盖父预设。这个逻辑没问题,但容易忽略。
- 工具链文件路径:工具链文件里如果用了相对路径,它是相对于
CMAKE_SOURCE_DIR的,不是相对于 Presets 文件。我一开始就被这个坑过。
实战建议
最后,我给大家几个实战中的建议:
- 最少原则:配置预设里只放必须的变量。那些跟平台相关的、跟个人习惯相关的,尽量放到工具链文件里。
- 命名规范:预设名字要有意义。比如
linux-debug、win-release,一看就知道是干什么的。 - 版本控制:CMakePresets.json 一定要提交到 Git 里。这是团队共享的基础。
- 测试覆盖:至少准备 Debug 和 Release 两个预设。我见过有人只配了 Debug,上线前才发现 Release 构建有问题。
配置预设这东西,说白了就是「一次配置,到处使用」。花点时间把它配好,后面能省下大把时间。嗯,今天就聊到这里,大家去试试吧。