3、CMake变量与缓存:set() 与 unset()、缓存变量(CACHE)、环境变量引用、常用内置变量

聊到CMake,变量系统是绕不开的核心。说白了,CMake就是靠变量来传递信息的。我刚开始接触CMake时,总觉得变量不就是赋值取值嘛,有啥好学的?结果被缓存变量坑了好几次才老实。今天咱们就把这块彻底捋清楚。

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

set() 是CMake里最常用的命令,用来给变量赋值。它的基本语法很简单:

set(变量名 值)

举个例子:

set(MY_NAME "Cross-Platform")
set(MY_VERSION 1.2.3)
set(MY_SOURCES main.cpp utils.cpp)

这里要注意,CMake的变量都是字符串类型的。你写 1.2.3,它也是字符串。你写 main.cpp utils.cpp,它会被当成一个字符串,而不是列表。那列表怎么表示?用分号:

set(MY_SOURCES main.cpp;utils.cpp;helper.cpp)

嗯,其实你写空格,CMake内部也会转成分号。但为了清晰,我建议你显式用分号。

unset() 就更好理解了,删除变量:

unset(MY_NAME)

我在项目中遇到过一个问题:某个变量在父目录的CMakeLists.txt里设了,子目录里想重新定义,结果发现值还是父目录的。为什么?因为CMake的变量作用域是栈式的。子目录会继承父目录的变量,但你在子目录里 set(),只会修改当前作用域的副本。想改父目录的?得用 PARENT_SCOPE:

set(MY_NAME "NewName" PARENT_SCOPE)
注意: PARENT_SCOPE 只能往上一层作用域传,不能跨多层。如果你有三层嵌套,想从最里层改最外层,那就得一层层传上去。

3.2 缓存变量(CACHE):让配置持久化

普通变量只在CMake运行期间有效,下次运行就没了。但有些配置,比如安装路径、编译选项,我们希望一次设置,下次还能记住。这时候就需要缓存变量。

语法是这样的:

set(变量名 值 CACHE 类型 "描述信息")

类型可以是 BOOL、STRING、FILEPATH、PATH 等。举个例子:

set(MY_INSTALL_PREFIX "/usr/local" CACHE PATH "安装路径")
set(ENABLE_FEATURE_X ON CACHE BOOL "是否启用特性X")

缓存变量会写入 CMakeCache.txt 文件。下次运行CMake时,它会自动读取这个文件。你想想看,这多方便——用户只需要在第一次配置时设置一次,后面就不用管了。

但这里有个坑:如果缓存变量已经存在,set() 默认不会覆盖它。也就是说,你第一次运行CMake时设了 ENABLE_FEATURE_X=ON,然后你改了CMakeLists.txt里的默认值,想改成 OFF,结果发现没用。为什么?因为缓存里已经有值了,CMake会优先用缓存里的。

解决办法有两个:

  • 删掉 CMakeCache.txt 重新配置
  • 用 FORCE 强制覆盖:set(ENABLE_FEATURE_X OFF CACHE BOOL "描述" FORCE)
我的习惯: 对于用户应该能修改的选项,我用缓存变量。对于内部实现细节,我用普通变量。这样CMake的GUI(cmake-gui)里只会显示用户关心的选项,不会乱糟糟的。

3.3 环境变量引用:跟系统打交道

有时候我们需要读取系统的环境变量,比如 PATH、HOME、或者你自己设的 MY_SDK_ROOT。CMake里用 $ENV{} 来引用:

set(MY_HOME $ENV{HOME})
message("Home directory: ${MY_HOME}")

也可以设置环境变量:

set(ENV{MY_VAR} "some_value")

不过要注意,set(ENV{}) 只影响当前CMake进程及其子进程,不会修改系统的环境变量。我刚开始学的时候以为能改系统变量,折腾了半天才发现不行。

我在项目中遇到过一个问题:某个第三方库的路径是通过环境变量指定的,但用户忘了设这个变量,CMake就会报错。我的做法是:

if(NOT DEFINED ENV{MY_SDK_ROOT})
    message(FATAL_ERROR "请设置 MY_SDK_ROOT 环境变量")
endif()
set(MY_SDK_PATH $ENV{MY_SDK_ROOT})

这样用户一运行就能看到清晰的错误提示,而不是莫名其妙的链接错误。

3.4 常用内置变量:CMake自带的宝贝

CMake提供了很多内置变量,能帮我们省不少事。我挑几个最常用的说说。

CMAKE_CXX_STANDARD

这个变量控制C++标准版本。比如你想用C++17:

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)  # 如果编译器不支持,直接报错
set(CMAKE_CXX_EXTENSIONS OFF)        # 禁用编译器扩展,保持标准

嗯,这里要注意:CMAKE_CXX_STANDARD 只是一个变量,你设了之后,CMake会在编译时加上 -std=c++17 之类的标志。但如果你自己手动设置了 CMAKE_CXX_FLAGS,可能会覆盖它。我建议你只用 CMAKE_CXX_STANDARD,别自己加 -std= 标志,否则容易冲突。

CMAKE_BUILD_TYPE

这个变量指定构建类型,常见的有 Debug、Release、RelWithDebInfo、MinSizeRel。用法:

set(CMAKE_BUILD_TYPE Release)

但要注意,这个变量只在单配置生成器(比如Makefile、Ninja)上有效。对于多配置生成器(比如Visual Studio、Xcode),构建类型是在编译时选的,不是配置时选的。你想想看,如果你用Visual Studio,在CMake里设了 CMAKE_BUILD_TYPE=Release,结果发现没用——因为VS的解决方案里同时包含Debug和Release配置。

我的做法是:

if(NOT CMAKE_CONFIGURATION_TYPES)
    # 单配置生成器,设置默认构建类型
    if(NOT CMAKE_BUILD_TYPE)
        set(CMAKE_BUILD_TYPE Release CACHE STRING "构建类型" FORCE)
    endif()
endif()

这样既兼容了单配置生成器,又不影响多配置生成器。

其他常用内置变量

变量名 说明 示例值
CMAKE_SOURCE_DIR 顶层CMakeLists.txt所在目录 /home/user/myproject
CMAKE_BINARY_DIR 构建目录 /home/user/myproject/build
CMAKE_CURRENT_SOURCE_DIR 当前处理的CMakeLists.txt所在目录 /home/user/myproject/src
CMAKE_CURRENT_BINARY_DIR 当前处理目录对应的构建目录 /home/user/myproject/build/src
PROJECT_NAME 当前project()命令指定的项目名 MyProject
PROJECT_SOURCE_DIR 当前project()命令所在目录 /home/user/myproject
PROJECT_BINARY_DIR 当前project()命令对应的构建目录 /home/user/myproject/build

这些变量在写跨平台项目时特别有用。比如你想把生成的库文件放到构建目录下的 lib 文件夹里:

set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)

这样不管你在哪个平台,库文件都会乖乖跑到 build/lib 里。

核心总结: 普通变量用 set() 和 unset(),作用域是栈式的。缓存变量用 set(... CACHE ...),会持久化到 CMakeCache.txt。环境变量用 $ENV{} 引用。内置变量是CMate提供的便利工具,能帮你写出更健壮的构建脚本。

CMake变量体系 普通变量 缓存变量 环境变量 set() / unset() • 作用域:栈式(子目录继承) • 生命周期:仅本次CMake运行 • 传递:PARENT_SCOPE • 典型用途:内部临时变量 例:set(MY_SRCS a.cpp b.cpp) set(... CACHE ...) • 持久化:写入CMakeCache.txt • 类型:BOOL/STRING/FILEPATH/PATH • 覆盖:需用FORCE • 典型用途:用户可配置选项 例:set(OPT ON CACHE BOOL "...") $ENV{} / set(ENV{}) • 读取:$ENV{变量名} • 设置:set(ENV{变量名} 值) • 范围:仅当前进程及子进程 • 典型用途:读取系统配置 例:$ENV{HOME} 内置变量:CMAKE_CXX_STANDARD / CMAKE_BUILD_TYPE / CMAKE_SOURCE_DIR ...

这张图把CMake变量体系分成了三大块:普通变量、缓存变量、环境变量。底部是内置变量,它们本质上也是普通变量或缓存变量,只是CMake预定义好了名字和用途。你写CMakeLists.txt时,脑子里要有这张图——知道什么时候该用哪种变量,就能避免很多坑。

可可精品资料库