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,选项就丢了,得手动再设一遍。后来改成缓存变量,世界清净了。
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 缓存,也不会影响其他模块。它就是个临时通道。
$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_FLAGS、CMAKE_EXE_LINKER_FLAGS 等。
ccmake 还有个好处:它能显示变量的文档。你选中一个变量,底部会显示它的描述。这对新手特别友好。
4.7 知识体系图
下面这张图帮你理清三种变量的关系:
4.8 避坑指南
最后,分享几个我踩过的坑:
- 缓存变量不更新: 记得加
FORCE,或者手动删CMakeCache.txt - 环境变量改了没生效: 环境变量只在当前 cmake 进程有效。想持久化?写到缓存里
- 子目录变量传不回去: 用
PARENT_SCOPE或者缓存变量 - ccmake 里改了值,但代码里又 set 了一次: 代码里的 set 不会覆盖缓存,除非加 FORCE
嗯,变量这部分内容就这些。记住三个原则:普通变量管局部,缓存变量管全局,环境变量管系统。搞清楚了,CMake 的变量系统你就掌握了八成。