4. 变量与缓存:普通变量、缓存变量、环境变量、set与unset命令、变量作用域、缓存编辑工具ccmake

CMake 里的变量系统,说实话,刚接触的时候容易懵。普通变量、缓存变量、环境变量,三个东西长得像,用法却完全不同。我最早做跨平台项目时,就因为搞混了缓存变量和普通变量,折腾了一整个下午才找到问题。今天咱们就把这三兄弟彻底捋清楚。

4.1 普通变量:最常用的那个

普通变量就是你在 CMakeLists.txt 里随手定义的变量。它的生命周期从定义开始,到当前作用域结束为止。

set(MY_VAR "hello")
message("${MY_VAR}")  # 输出 hello

嗯,就这么简单。但要注意,普通变量只在当前 CMakeLists.txt 文件以及它调用的子目录里有效。出了这个范围,变量就没了。

我的习惯:普通变量用来存临时值、中间计算结果、或者只在单个模块内使用的配置项。别用它来跨模块传数据,那是缓存变量的事。

4.2 缓存变量:跨文件、跨构建的持久化存储

缓存变量存在 CMakeCache.txt 文件里。你运行一次 cmake,它就写进去了。下次再运行,值还在。

set(MY_CACHE_VAR "hello" CACHE STRING "这是一个缓存变量")

关键区别在于 CACHE 关键字。加上它,变量就进了缓存。不加,就是普通变量。

我曾经在做一个大型项目时,把编译选项写成了普通变量。结果每次重新 cmake,选项就丢了,得手动再设一遍。后来改成缓存变量,世界清净了。

注意:缓存变量一旦设置,就不会被后续的 set 命令覆盖,除非你加 FORCE。这是很多人踩过的坑。
# 第一次设置
set(MY_OPTION ON CACHE BOOL "我的选项")

# 第二次想改?没用!除非加 FORCE
set(MY_OPTION OFF CACHE BOOL "我的选项")  # 还是 ON
set(MY_OPTION OFF CACHE BOOL "我的选项" FORCE)  # 这才改成 OFF

4.3 环境变量:和操作系统打交道

环境变量用 $ENV{} 读取,用 set(ENV{}) 设置。它读的是操作系统的环境变量,不是 CMake 自己的。

# 读取环境变量
message("PATH: $ENV{PATH}")

# 设置环境变量(只在当前 cmake 进程有效)
set(ENV{MY_ENV_VAR} "test")

环境变量有个特点:你改了它,不会影响 CMake 缓存,也不会影响其他模块。它就是个临时通道。

实用场景:用环境变量传递编译器路径、SDK 路径、或者 CI/CD 系统传入的参数。比如 $ENV{CMAKE_TOOLCHAIN_FILE} 指定交叉编译工具链。

4.4 set 与 unset 命令

set 我们已经见过了。它有三种形态:

命令 作用 示例
set(var value) 设置普通变量 set(NAME "Alice")
set(var value CACHE TYPE DOC) 设置缓存变量 set(NAME "Alice" CACHE STRING "名字")
set(ENV{var} value) 设置环境变量 set(ENV{PATH} "/usr/bin")

unset 就是删除变量:

unset(MY_VAR)           # 删除普通变量
unset(MY_CACHE_VAR CACHE)  # 删除缓存变量
unset(ENV{MY_ENV_VAR})  # 删除环境变量

你想想看,unset 如果不加 CACHE,它只删除普通变量。缓存变量还在。很多人以为 unset 能删缓存,结果删了个寂寞。

4.5 变量作用域:父与子的关系

CMake 的作用域规则其实很简单:

  • 父目录的普通变量,子目录能读到
  • 子目录的普通变量,父目录读不到
  • 缓存变量全局可见
  • 环境变量全局可见(但只在当前进程)

举个例子:

# CMakeLists.txt(根目录)
set(ROOT_VAR "我是根目录")
add_subdirectory(sub)

# sub/CMakeLists.txt
message("${ROOT_VAR}")  # 能读到:我是根目录
set(SUB_VAR "我是子目录")

# 回到根目录,SUB_VAR 不存在

我在项目中遇到过一个问题:子目录里改了某个变量,以为父目录也能用。结果父目录拿到的还是旧值。后来我用了 set(PARENT_SCOPE) 才解决。

# 子目录里想修改父目录的变量
set(MY_VAR "新值" PARENT_SCOPE)

说白了,PARENT_SCOPE 就是向上传递变量。但注意,它只能传一层。想传多层?用缓存变量吧。

4.6 缓存编辑工具 ccmake

ccmake 是 CMake 的终端图形界面工具。你运行 ccmake . 就能打开它。

它的界面长这样:

 CMake 3.28.0 - /home/user/myproject
 Press [enter] to edit option
 Press [c] to configure
 Press [g] to generate
 Press [t] to toggle advanced mode

 CMAKE_BUILD_TYPE              Release
 CMAKE_INSTALL_PREFIX          /usr/local
 MY_OPTION                     ON
 MY_STRING_VAR                 hello

上下键选择变量,回车编辑,c 键配置,g 键生成。就这么简单。

我个人的习惯是:第一次配置项目用 ccmake,因为能看到所有缓存变量,还能调高级选项。后续改参数就用命令行,因为快。

小技巧:t 键可以切换显示高级变量。很多隐藏的 CMake 变量都在里面,比如 CMAKE_CXX_FLAGSCMAKE_EXE_LINKER_FLAGS 等。

ccmake 还有个好处:它能显示变量的文档。你选中一个变量,底部会显示它的描述。这对新手特别友好。

4.7 知识体系图

下面这张图帮你理清三种变量的关系:

CMake 变量体系 普通变量 set(MY_VAR "val") 作用域:当前目录 子目录可读 父目录不可读 生命周期: cmake 运行期间 适合:临时值、 模块内部配置 缓存变量 set(... CACHE STRING "") 作用域:全局 所有目录可见 跨构建持久化 生命周期: 存储在 CMakeCache.txt 适合:编译选项、 路径配置、开关 环境变量 $ENV{PATH} 作用域:全局 操作系统级别 当前进程有效 生命周期: cmake 进程结束即消失 适合:系统路径、 CI/CD 参数 普通变量 → 临时存储 | 缓存变量 → 持久配置 | 环境变量 → 系统接口

4.8 避坑指南

最后,分享几个我踩过的坑:

  • 缓存变量不更新: 记得加 FORCE,或者手动删 CMakeCache.txt
  • 环境变量改了没生效: 环境变量只在当前 cmake 进程有效。想持久化?写到缓存里
  • 子目录变量传不回去:PARENT_SCOPE 或者缓存变量
  • ccmake 里改了值,但代码里又 set 了一次: 代码里的 set 不会覆盖缓存,除非加 FORCE

嗯,变量这部分内容就这些。记住三个原则:普通变量管局部,缓存变量管全局,环境变量管系统。搞清楚了,CMake 的变量系统你就掌握了八成。

公众号:蓝海资料掘金营,微信 deep3321