变量与缓存:普通变量、缓存变量、环境变量
说实话,CMake 的变量系统,是很多初学者第一个栽跟头的地方。我当年刚接触 CMake 时,就被普通变量和缓存变量的关系搞晕过——明明设了个变量,换个目录就找不到了?嗯,今天咱们就把这块彻底捋清楚。
1. 普通变量:最基础的存在
普通变量,说白了就是「临时变量」。它在当前 CMakeLists.txt 的作用域内有效,一旦出了这个作用域,就没了。
# 设置普通变量
set(MY_VAR "hello")
set(MY_LIST a b c d) # 列表也是变量
# 使用变量
message("MY_VAR = ${MY_VAR}")
message("MY_LIST = ${MY_LIST}")
我在项目中遇到过一个问题:一个子目录的 CMakeLists.txt 里设了个变量,父目录死活读不到。当时我还以为是 CMake 的 bug,后来才明白——这就是作用域在作怪。
核心要点:普通变量遵循「就近原则」。子目录的 set() 不会影响父目录,除非你用 PARENT_SCOPE。
# 子目录中想影响父目录
set(MY_VAR "value" PARENT_SCOPE)
2. 缓存变量:跨目录的「持久化」方案
缓存变量就不一样了。它会被写入 CMakeCache.txt,整个构建树都能访问。我习惯把缓存变量理解为「全局配置项」。
# 设置缓存变量
set(MY_CACHE_VAR "cache_value" CACHE STRING "这是缓存变量说明")
# 强制覆盖已有缓存
set(MY_CACHE_VAR "new_value" CACHE STRING "说明" FORCE)
你想想看,为什么要有缓存变量?因为有些配置你不想每次 cmake 都重新设一遍。比如安装路径、编译选项这些,设一次就够了。
注意:缓存变量一旦写入 CMakeCache.txt,下次 cmake 时不会自动覆盖。除非你加了 FORCE 关键字,或者手动删了缓存文件。
3. option 命令:开关变量的优雅写法
option 其实就是缓存变量的语法糖。专门用来处理 ON/OFF 这种开关选项。
option(USE_OPENCV "是否启用 OpenCV" ON)
option(ENABLE_TEST "启用单元测试" OFF)
if(USE_OPENCV)
find_package(OpenCV REQUIRED)
endif()
我曾经在一个大型项目中,用 option 管理了十几个编译开关。说实话,比手动 set 缓存变量清爽多了。每个 option 都会自动出现在 cmake-gui 里,用户勾选就行。
小技巧:option 的默认值只在第一次 cmake 时生效。如果你改了默认值,记得删掉 CMakeCache.txt 中对应的条目,或者用 -D 参数强制覆盖。
4. 环境变量:和系统打交道
环境变量是 CMake 和操作系统之间的桥梁。你可以读取系统环境变量,也可以设置新的环境变量(但只影响 CMake 自身)。
# 读取环境变量
message("PATH = $ENV{PATH}")
message("HOME = $ENV{HOME}")
# 设置环境变量(仅 CMake 内部有效)
set(ENV{MY_ENV_VAR} "test_value")
message("MY_ENV_VAR = $ENV{MY_ENV_VAR}")
嗯,这里要注意:set(ENV{...}) 设置的环境变量,不会影响父进程(你的 shell)。它只在 CMake 运行期间有效。我刚开始时以为能永久改环境变量,结果折腾了半天。
5. 变量作用域:理解「层级」关系
作用域是 CMake 变量系统最核心的概念。我画了张图,帮你理清关系:
从这张图能看出来:环境变量在最顶层,所有进程都能读。缓存变量次之,整个构建树共享。普通变量最底层,受目录作用域限制。
6. 变量优先级:到底听谁的?
当同一个变量名在不同层级出现时,CMake 有一套明确的查找规则:
| 优先级 | 变量类型 | 说明 |
|---|---|---|
| 1(最高) | 普通变量(当前作用域) | 当前目录的 set() 优先 |
| 2 | 缓存变量 | CMakeCache.txt 中的值 |
| 3(最低) | 环境变量 | $ENV{...} 读取 |
说白了,CMake 先找当前作用域的普通变量,找不到再找缓存变量,最后才看环境变量。这个顺序我建议你记牢,调试时特别有用。
实战经验:我曾经调试一个第三方库的 CMake 脚本,发现某个变量值总是不对。查了半天,原来是父目录的 set() 和缓存变量冲突了。最后用 message() 打印变量来源才定位到问题。
7. 实用技巧与避坑指南
最后分享几个我这些年积累的经验:
- 用 -D 传参最稳妥:cmake -DMY_VAR=value 这种方式设置的变量,会直接写入缓存,优先级高且可预测。
- message() 是你的好朋友:不确定变量值?直接 message("VAR=${VAR}") 打印出来看。
- 缓存变量要加说明:set(... CACHE STRING "说明") 里的说明字符串别偷懒,cmake-gui 里会显示。
- 避免变量名冲突:我习惯给项目变量加前缀,比如 MYPROJ_XXX,防止和第三方库的变量撞车。
曾经踩过的坑:我在一个项目中用了 add_subdirectory() 加载子模块,子模块里 set() 了一个变量,我以为父目录也能用。结果编译时各种报错。后来才意识到,子目录的普通变量默认不会传到父目录。要么用 PARENT_SCOPE,要么用缓存变量。
嗯,变量这块内容就这些。理解普通变量、缓存变量、环境变量的区别,再搞懂作用域规则,CMake 的变量系统你就掌握了八成。剩下的就是在项目中多练,遇到问题多打 message() 调试。
公众号:蓝海资料掘金营,微信 deep3321