3、变量与缓存:set()与unset()、普通变量与缓存变量、CMake缓存机制、CMake GUI与ccmake的使用

CMake 里的变量系统,说实话,刚接触的时候容易让人懵。普通变量、缓存变量、环境变量……它们到底有什么区别?什么时候该用哪个?

我个人习惯把 CMake 的变量系统比作「两层楼」:一楼是普通变量,二楼是缓存变量。普通变量只在当前 CMakeLists.txt 和它调用的子目录里有效;缓存变量则会被写入 CMakeCache.txt,跨构建目录持久保存。

好,咱们一层一层拆开看。

3.1 set() 与 unset():变量的基本操作

先看最常用的 set() 命令。它的基本语法是:

set(<variable> <value>... [CACHE <type> <docstring> [FORCE]])

如果不加任何选项,那就是设置一个普通变量:

set(MY_VAR "hello")
message(STATUS "MY_VAR = ${MY_VAR}")

输出:MY_VAR = hello

如果要删除一个变量,用 unset()

unset(MY_VAR)
message(STATUS "MY_VAR = ${MY_VAR}")  # 输出空字符串

嗯,这里要注意:unset() 之后,变量就变成未定义了。如果你在 if() 里判断它,会得到 FALSE

小技巧: 我经常用 set(VAR "" CACHE STRING "") 来初始化一个空缓存变量,而不是用 unset()。因为 unset() 在某些旧版 CMake 里行为不太一致。

3.2 普通变量 vs 缓存变量

普通变量是「一次性」的。每次运行 cmake 配置阶段,它们都会被重新计算。缓存变量则会被写入 CMakeCache.txt,下次运行 cmake 时仍然存在。

看个对比:

# 普通变量
set(MY_VAR "world")
message("普通变量: ${MY_VAR}")

# 缓存变量
set(MY_CACHE_VAR "hello" CACHE STRING "这是一个缓存变量")
message("缓存变量: ${MY_CACHE_VAR}")

第一次运行 cmake,两者都能输出。第二次运行 cmake,普通变量会被重新设置,缓存变量则保留上次的值——除非你用 FORCE 强制覆盖。

注意: 缓存变量一旦被写入,就不会被后续的 set() 覆盖(除非加 FORCE)。我曾经踩过这个坑:在项目根目录设置了一个缓存变量,然后在子目录里想改它,结果死活改不掉。后来才发现,子目录的 set() 默认不会覆盖父目录的缓存变量。

那什么时候用缓存变量?我总结了几种场景:

  • 用户可配置的选项:比如 BUILD_SHARED_LIBSCMAKE_BUILD_TYPE
  • 跨目录共享的变量:比如第三方库的路径
  • 需要持久化的变量:比如编译器的路径、系统特性检测结果

3.3 CMake 缓存机制:CMakeCache.txt 的秘密

每次你运行 cmake -B build,CMake 都会在 build 目录下生成一个 CMakeCache.txt。这个文件是纯文本的,你可以直接打开看。

它的格式很简单:

// 变量的文档字符串
变量名:类型=值

举个例子:

// 构建类型
CMAKE_BUILD_TYPE:STRING=Release
// 是否构建共享库
BUILD_SHARED_LIBS:BOOL=ON

缓存变量的类型决定了 CMake GUI 里显示什么控件:

类型 说明 GUI 控件
BOOL 布尔值(ON/OFF) 复选框
STRING 字符串 文本框
PATH 路径 文件选择器
FILEPATH 文件路径 文件选择器
INTERNAL 内部变量(不显示在 GUI 中)
核心要点: 缓存机制是 CMake 实现「增量配置」的基础。第一次配置时,CMake 会检测系统环境、查找依赖库,然后把结果缓存起来。后续配置时,只要缓存没变,CMake 就跳过这些检测步骤,大大加快配置速度。

但缓存也有副作用。比如你升级了某个库,但 CMakeCache.txt 里还记录着旧路径,CMake 就不会重新查找。这时候你需要手动删除缓存文件,或者用 cmake --fresh 强制重新配置。

3.4 CMake GUI 与 ccmake:可视化配置工具

说实话,我平时很少用 GUI,但不得不承认,对于不熟悉 CMake 的开发者来说,GUI 确实更友好。

cmake-gui 是图形界面工具。你打开它,设置源码目录和构建目录,点击 Configure,然后就能看到所有缓存变量。你可以修改它们,再点击 Generate 生成构建系统。

它的界面大致分三块:

  • 左侧:变量列表,按分组显示(如 CMAKE、项目自定义变量)
  • 右侧:变量的值,可以直接编辑
  • 底部:Configure、Generate 按钮,以及日志输出

ccmake 是终端里的交互式工具。运行 ccmake -B build 就能进入一个类似 ncurses 的界面。用上下键选择变量,回车编辑,按 c 配置,按 g 生成。

我个人更喜欢 ccmake,因为不用离开终端。而且它有个好处:修改变量后,它会高亮显示哪些变量被改了,方便你追踪。

我的习惯: 在开发阶段,我经常用 ccmake 来快速调整编译选项。比如切换 Debug/Release 模式、开关某些功能。等确定下来后,再把这些选项固化到 CMakePresets.json 里。

最后,咱们用一张图来总结本章的知识体系:

CMake 变量与缓存机制 普通变量 set() / unset() 缓存变量 set(... CACHE ...) 环境变量 $ENV{...} CMakeCache.txt 持久化存储 | 增量配置 | 跨目录共享 类型:BOOL / STRING / PATH / FILEPATH / INTERNAL cmake-gui 图形界面 | 可视化编辑 ccmake 终端交互 | 快速调整 cmake -B build | cmake --fresh | ccmake -B build

这张图把本章的核心内容串起来了:变量分三类,缓存变量是核心,GUI 和 ccmake 是操作工具。你想想看,理解了这张图,CMake 的变量系统基本就掌握了八成。

避坑指南: 我曾经在项目里用 set(VAR "value" CACHE STRING "") 设置了一个缓存变量,然后在另一个 CMakeLists.txt 里用 set(VAR "new_value") 想覆盖它。结果发现根本没用——因为缓存变量优先级更高。正确的做法是加 FORCEset(VAR "new_value" CACHE STRING "" FORCE)。或者干脆用 unset() 先删掉再重新设置。

好了,关于变量与缓存,咱们就聊到这儿。记住:普通变量是「一次性」的,缓存变量是「持久化」的,环境变量是「系统级」的。搞清楚了这三者的区别,你在 CMake 里处理配置问题就会游刃有余。