4. 变量与缓存:set()命令、变量作用域、缓存变量(CACHE)、环境变量读取

CMake 里的变量系统,说实话,刚接触时挺容易懵的。

我记得第一次用 CMake 做跨平台项目,就栽在变量作用域上。明明在顶层设了个变量,子目录里死活读不到。后来才发现,CMake 的变量传递机制跟 C++ 的命名空间有点像,但又不太一样。

这一节,咱们就把变量这块彻底捋清楚。

4.1 set() 命令:变量的基本操作

set() 是 CMake 里最常用的命令。说白了,就是给变量赋值。

set(MY_VAR "hello")
set(MY_LIST "a" "b" "c")  # 列表
set(MY_STRING "a;b;c")    # 分号分隔的字符串

嗯,这里要注意:CMake 里没有真正的数组类型。列表就是用分号分隔的字符串。你写 "a" "b" "c",CMake 内部存成 "a;b;c"。

我在项目中遇到过一个问题:用 set() 给变量赋了值,但后面用 if() 判断时总是不对。后来发现是变量名拼错了。CMake 不会报错,只会返回空字符串。所以,变量名一定要写对。

注意:CMake 变量名是大小写敏感的。MY_VAR 和 my_var 是两个不同的变量。

4.2 变量作用域:函数、目录与全局

CMake 的变量作用域分三层:

  • 函数作用域:在 function() 内部定义的变量,外部不可见
  • 目录作用域:每个 CMakeLists.txt 文件有自己的作用域
  • 全局作用域:在顶层 CMakeLists.txt 中定义的变量,默认是全局的

为什么会这样?因为 CMake 的设计哲学是「隔离」。每个子目录应该独立,互不干扰。

举个例子:

# 顶层 CMakeLists.txt
set(TOP_VAR "我是顶层")

add_subdirectory(sub)

message("TOP_VAR = ${TOP_VAR}")  # 可以访问
message("SUB_VAR = ${SUB_VAR}")  # 空,子目录的变量不可见
# sub/CMakeLists.txt
set(SUB_VAR "我是子目录")
message("TOP_VAR = ${TOP_VAR}")  # 可以访问,父目录变量默认向下传递

我个人习惯在顶层定义所有「公共」变量,比如项目版本号、安装路径等。子目录里只定义局部变量。

小技巧:如果你想让子目录的变量传递回父目录,可以用 set(PARENT_SCOPE)。但我不建议频繁使用,容易让变量流变得混乱。

4.3 缓存变量(CACHE):持久化存储

缓存变量是 CMake 里一个很特别的存在。它会被写入 CMakeCache.txt 文件,下次运行 CMake 时自动加载。

我刚开始用 CMake 时,不理解为什么有些变量设了值,重新 cmake 后又变回去了。后来才明白,那是普通变量。缓存变量才是「记住」的。

set(MY_CACHE_VAR "默认值" CACHE STRING "这是一个缓存变量")

缓存变量的典型用途:

  • 用户可配置的选项(如 BUILD_SHARED_LIBS)
  • 编译器的路径
  • 第三方库的安装位置

我曾经踩过一个坑:在子目录里用 set(... CACHE) 定义了一个缓存变量,结果顶层 CMakeLists.txt 里也定义了同名变量。两个值互相覆盖,调试了半天才发现。

避坑指南:缓存变量一旦定义,cmake-gui 或 ccmake 里就能看到。如果你不想让用户修改,可以用 INTERNAL 类型:set(... CACHE INTERNAL "")。

4.4 环境变量读取:与系统交互

CMake 可以读取系统的环境变量。语法很简单:

set(ENV_VAR $ENV{HOME})
message("HOME = ${ENV_VAR}")

也可以设置环境变量(只在 CMake 运行期间有效):

set(ENV{MY_ENV} "临时值")

我在项目中常用环境变量来做两件事:

  1. 读取 PATH 来查找工具链
  2. 读取 CMAKE_PREFIX_PATH 来定位第三方库

但要注意,环境变量是「只读」的。你可以在 CMake 里改,但不会影响父进程的环境。

核心要点:
  • 普通变量:当前作用域有效,不会持久化
  • 缓存变量:跨 CMake 运行持久化,用户可配置
  • 环境变量:从系统读取,CMake 内可修改但不影响外部

4.5 知识体系图:变量与缓存的核心逻辑

下面这张图,帮你理清 CMake 变量系统的全貌:

CMake 变量系统核心逻辑 普通变量 • 当前作用域有效 • 不持久化 • 父→子目录传递 • 子→父需 PARENT_SCOPE set(MY_VAR "value") 缓存变量 • 写入 CMakeCache.txt • 跨运行持久化 • 用户可配置 • 全局可见 set(... CACHE STRING "") 环境变量 • 从系统读取 • CMake 内可修改 • 不影响外部进程 • 用于查找工具链 $ENV{PATH} 可提升为 读取到 作用域:函数级 → 目录级 → 全局级(缓存变量跨所有作用域)

4.6 实战建议:变量命名与组织

最后,分享几个我多年积累的变量管理习惯:

  • 命名规范:项目级变量用 PROJECT_ 前缀,模块级用 MODULE_ 前缀
  • 缓存变量:只在顶层 CMakeLists.txt 中定义,子目录不要碰
  • 环境变量:读取时加默认值,避免空指针
  • 调试技巧:用 message(STATUS "VAR = ${VAR}") 打印变量值

嗯,变量这块就讲到这里。记住一句话:普通变量是临时的,缓存变量是永久的,环境变量是外来的。搞清这三者的关系,CMake 变量系统就没什么难的了。

我的经验:如果你发现 CMake 行为「诡异」,十有八九是变量作用域搞错了。先检查变量是在哪里定义的,再检查哪里读取的。这个排查思路,帮我省了无数时间。

可可精品资料库