15、预设与缓存:CMakePresets.json配置、缓存变量、option()命令、高级缓存编辑
说实话,CMake 的缓存机制是我早期最头疼的部分。明明改了个变量,重新构建时它还是老样子。后来我才明白——缓存这东西,就像程序员的短期记忆,你不主动刷新,它就记着旧数据。
这一章,我们来彻底搞懂 CMake 的缓存体系。包括预设文件怎么用、缓存变量怎么玩、option() 命令的坑,以及一些高级编辑技巧。
15.1 CMakePresets.json:项目配置的“快捷方式”
你有没有遇到过这种情况?一个新同事加入项目,光配置 CMake 就花了半天。各种 -D 参数、构建类型、工具链路径……每次都要敲一长串命令。
CMakePresets.json 就是来解决这个问题的。它把常用的配置选项打包成“预设”,一键切换。
15.1.1 预设文件的结构
一个典型的 CMakePresets.json 长这样:
{
"version": 3,
"configurePresets": [
{
"name": "default",
"displayName": "默认配置",
"description": "Debug 模式,使用 Ninja",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/default",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_CXX_STANDARD": "17"
}
},
{
"name": "release",
"displayName": "发布配置",
"description": "Release 模式,优化全开",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/release",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"CMAKE_CXX_FLAGS": "-O3 -march=native"
}
}
],
"buildPresets": [
{
"name": "default",
"configurePreset": "default"
}
]
}
我个人习惯把预设文件放在项目根目录。这样团队成员 clone 下来就能直接用 cmake --preset=default 开始构建。
${sourceDir} 会自动替换成源码路径。你还可以用 ${userHome} 引用用户目录。
15.1.2 预设的继承与覆盖
预设可以互相继承。比如你有一个基础预设,然后在此基础上微调:
{
"configurePresets": [
{
"name": "base",
"hidden": true,
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/${presetName}"
},
{
"name": "debug",
"inherits": "base",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "asan",
"inherits": "debug",
"cacheVariables": {
"CMAKE_CXX_FLAGS": "-fsanitize=address"
}
}
]
}
这里 base 预设设置了 hidden: true,它不会出现在预设列表中,只作为模板使用。我经常用这种模式来组织不同平台的配置——Windows 一套基础,Linux 一套基础,然后各自派生。
15.2 缓存变量:CMake 的“记忆系统”
缓存变量是 CMake 在第一次运行 cmake 命令时生成的。它们保存在 CMakeCache.txt 文件中。下次再运行 cmake,它会先读这个文件,而不是重新计算。
为什么会这样?因为有些信息一旦确定就不需要变了。比如编译器路径、安装前缀、第三方库的位置。每次都重新检测太慢,缓存就是用来加速的。
15.2.1 缓存变量的定义方式
在 CMakeLists.txt 里,你可以用 set() 命令定义缓存变量:
set(MY_VARIABLE "hello" CACHE STRING "这是一个缓存变量")
注意 CACHE 关键字。没有它,变量就是普通变量,只在当前 CMakeLists.txt 作用域有效。加上 CACHE,它就会被写入 CMakeCache.txt。
缓存变量有类型:
- STRING:字符串,最常用
- BOOL:布尔值,ON/OFF
- FILEPATH:文件路径
- PATH:目录路径
- INTERNAL:内部变量,不显示在 cmake-gui 中
我个人建议:凡是需要用户配置的选项,都用缓存变量。比如第三方库的路径、安装目录等。
15.2.2 缓存变量的优先级
这里有个容易踩坑的地方。缓存变量、普通变量、命令行参数,它们之间有优先级:
- 命令行
-D参数优先级最高 - 缓存变量次之
- 普通变量优先级最低
举个例子:
# CMakeLists.txt
set(MY_VAR "默认值" CACHE STRING "")
message("MY_VAR = ${MY_VAR}")
如果你第一次运行 cmake -DMY_VAR=命令行值 ..,输出是 MY_VAR = 命令行值。第二次运行 cmake ..(不加 -D),输出还是 MY_VAR = 命令行值。因为缓存记住了。
嗯,这里要注意:如果你想重置缓存变量,要么删掉 CMakeCache.txt,要么用 -D 重新赋值。
15.3 option() 命令:开关的艺术
option() 是缓存变量的语法糖,专门用来定义布尔开关:
option(USE_OPENCV "是否启用 OpenCV 支持" ON)
它等价于:
set(USE_OPENCV ON CACHE BOOL "是否启用 OpenCV 支持")
但 option() 有个特殊行为:如果变量已经在缓存中,它不会覆盖。也就是说,用户第一次设置后,后续的 option() 调用不会改变它的值。
我曾经在项目中遇到过一个问题:某个库的 option() 默认是 ON,但用户想关闭它。结果用户改了缓存,但代码里又用 set() 强制覆盖了……嗯,那场面挺混乱的。
option() 定义用户可配置的开关,用 set(... CACHE BOOL ...) 定义内部开关。前者尊重用户选择,后者可以强制覆盖。
15.4 高级缓存编辑技巧
除了基本的 set() 和 option(),还有一些高级玩法。
15.4.1 强制更新缓存
有时候你需要强制更新缓存变量,即使它已经存在。可以用 FORCE 关键字:
set(MY_VAR "新值" CACHE STRING "" FORCE)
这会覆盖缓存中的旧值。我一般只在初始化脚本或工具链文件中用这个,普通 CMakeLists.txt 里尽量少用——容易让用户困惑。
15.4.2 缓存变量的高级类型
除了基本类型,CMake 还支持 STRINGS 属性,用来限制可选值:
set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "构建类型")
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug;Release;RelWithDebInfo;MinSizeRel")
这样在 cmake-gui 中,用户只能从下拉列表中选择,不能乱填。我特别喜欢这个特性,尤其是管理编译选项时。
15.4.3 缓存变量的依赖关系
有时候一个缓存变量的值依赖于另一个。比如:
option(ENABLE_SSL "启用 SSL" OFF)
if(ENABLE_SSL)
set(OPENSSL_ROOT_DIR "" CACHE PATH "OpenSSL 安装路径")
endif()
但这里有个问题:如果用户先关闭 SSL,再打开,OPENSSL_ROOT_DIR 可能还是空的。我建议用 cmake_dependent_option() 来处理这种依赖:
include(CMakeDependentOption)
cmake_dependent_option(ENABLE_SSL "启用 SSL" OFF
"NOT WIN32" ON)
这个命令来自 CMakeDependentOption 模块。它可以根据条件自动启用或禁用选项。我在跨平台项目中经常用它——Windows 上默认关闭某些功能,Linux 上默认打开。
15.5 本章知识体系
下面这张图总结了预设、缓存和选项之间的关系:
从图中可以看出:预设文件生成缓存,缓存被 option() 读取,而高级技巧则是对缓存进行精细控制。这三者构成了 CMake 配置管理的核心。
15.6 避坑指南
最后分享几个我踩过的坑:
- 缓存不更新:修改了 CMakeLists.txt 但缓存没变?试试
cmake --fresh强制重新生成缓存。 - 预设路径问题:预设中的路径尽量用
${sourceDir}而不是.,否则在不同目录执行 cmake 时可能出错。 - option() 的陷阱:如果你在子目录中用
option(),它只在那个子目录作用域有效。想全局生效?用set(... CACHE BOOL ...)代替。 - 缓存文件冲突:多人协作时,不要把
CMakeCache.txt提交到版本控制。每个开发者的环境不同,缓存应该本地生成。
嗯,缓存这东西,说简单也简单,说复杂也复杂。但只要你理解了它的“记忆”机制,就能驾驭它。记住:缓存是你的朋友,不是敌人。
公众号:蓝海资料掘金营,微信 deep3321