4、CMake变量与缓存:set()命令、缓存变量、CMake预设变量
好,咱们今天聊点实在的。CMake里的变量系统,说白了就是整个构建流程的"血液"。你写的每个set()、每个option(),最终都会影响编译器怎么干活。我刚开始用CMake那会儿,总觉得变量嘛,不就是赋值取值?结果被缓存变量坑了好几次,编译出来的东西跟预期完全不一样……嗯,今天咱们就把这块彻底捋清楚。
4.1 set()命令:最基础的变量赋值
set()是CMake里最常用的命令,没有之一。它的基本语法很简单:
set(<variable> <value> [CACHE <type> <docstring> [FORCE]])
但这里有个坑——不加CACHE关键字,变量只在当前CMakeLists.txt作用域生效。什么意思呢?
我举个例子。你在根目录的CMakeLists.txt里写:
set(MY_VAR "hello")
然后在子目录的CMakeLists.txt里想用${MY_VAR},你会发现——拿不到!除非你用PARENT_SCOPE或者CACHE把它传上去。
关键点:普通set()创建的变量是"局部变量",只在当前CMakeLists.txt及其调用的子模块中可见。想跨文件共享?得用缓存变量或者全局属性。
我个人习惯是:临时变量用普通set(),需要用户配置的用缓存变量。这样代码清晰,别人一看就知道哪些是"可调参数"。
4.2 缓存变量:让配置"记住"你的选择
缓存变量是CMake里最容易被忽视、但又最重要的概念。你想想看,每次运行cmake ..,如果所有变量都要重新设一遍,那不得累死?缓存变量就是用来解决这个问题的——它会把值存到CMakeCache.txt里,下次构建自动读取。
创建缓存变量的标准写法:
set(MY_CACHE_VAR "default_value" CACHE STRING "This is a cache variable")
这里STRING是变量类型,CMake支持的类型有:
| 类型 | 说明 | 典型用途 |
|---|---|---|
| BOOL | 布尔值,ON/OFF | 开关选项,如BUILD_SHARED_LIBS |
| STRING | 任意字符串 | 路径、名称等 |
| FILEPATH | 文件路径 | 指向某个文件 |
| PATH | 目录路径 | 指向某个目录 |
| INTERNAL | 内部变量,不显示在cmake-gui中 | 自动推导的值 |
注意:缓存变量一旦设置,不会被后续的普通set()覆盖!除非你加上FORCE关键字。我曾经因为这个特性,调试了整整一个下午——明明在CMakeLists.txt里改了值,编译出来还是旧的……后来才发现是缓存里还留着老值。
所以我的建议是:如果你要修改缓存变量,要么删掉CMakeCache.txt重新配置,要么用set(... CACHE ... FORCE)强制覆盖。别指望普通set()能改缓存,它真的不行。
4.3 CMake预设变量:框架给你的"免费午餐"
CMake内置了很多预设变量,这些变量不需要你定义,直接用就行。它们反映了当前系统的环境、编译器信息、构建配置等。咱们做交叉编译,最常用的几个是:
| 变量名 | 含义 | 典型值(交叉编译时) |
|---|---|---|
| CMAKE_C_COMPILER | C编译器路径 | /usr/bin/arm-linux-gnueabihf-gcc |
| CMAKE_CXX_COMPILER | C++编译器路径 | /usr/bin/arm-linux-gnueabihf-g++ |
| CMAKE_SYSTEM_NAME | 目标系统名称 | Linux, Android, Generic |
| CMAKE_SYSTEM_PROCESSOR | 目标处理器架构 | arm, aarch64, x86_64 |
| CMAKE_FIND_ROOT_PATH | 查找依赖的根路径 | /path/to/sysroot |
| CMAKE_SYSROOT | 系统根目录 | /path/to/sysroot |
这些变量通常在工具链文件里设置。你想想看,如果没有这些预设变量,CMake怎么知道你要用arm编译器而不是本机的gcc?它只能靠猜——而猜的结果往往是错的。
小技巧:在CMakeLists.txt里加一行message(STATUS "CMAKE_C_COMPILER = ${CMAKE_C_COMPILER}"),可以帮你确认当前用的是哪个编译器。我在排查交叉编译问题时,第一步永远是打印这个变量。
4.4 变量优先级:谁说了算?
变量多了,冲突就来了。CMake里变量的优先级是这样的(从高到低):
- 命令行定义:
-DMY_VAR=value,最高优先级 - 缓存变量:存在CMakeCache.txt里的值
- 普通变量:CMakeLists.txt里用set()定义的
- 环境变量:通过
$ENV{...}读取的 - 默认值:CMake内置的默认值
这个优先级顺序,说白了就是:用户显式指定的 > 缓存记住的 > 脚本里写的 > 系统环境给的 > 框架默认的。理解了这个,你就知道为什么有时候改了CMakeLists.txt却没效果——八成是缓存里有个更高优先级的变量在作怪。
4.5 实战:用SVG梳理变量体系
说了这么多,咱们用一张图把整个变量体系串起来。这样你以后遇到变量相关的问题,脑子里能快速定位到"这是哪一类变量"。
4.6 避坑指南:我踩过的那些坑
最后,分享几个我实际项目中遇到的变量相关的问题,希望能帮你少走弯路。
坑1:缓存变量不更新
我曾经在工具链文件里改了CMAKE_SYSROOT的路径,但重新运行cmake后,它还是用旧路径。为什么?因为缓存里已经存了旧值,而我没有加FORCE。解决方案:要么删缓存,要么在工具链文件里用set(CMAKE_SYSROOT /new/path CACHE PATH "" FORCE)。
坑2:变量名拼写错误
CMake对变量名大小写敏感。我见过有人写CMAKE_c_COMPILER(小写c),结果CMake完全忽略了这个变量,还是用默认的gcc。这种错误很难排查,因为CMake不会报错——它只是"没找到这个变量"而已。
我的习惯:每次新建一个交叉编译项目,我都会在工具链文件开头加一段"变量自检"代码:
message(STATUS "=== 工具链变量自检 ===")
message(STATUS "CMAKE_C_COMPILER: ${CMAKE_C_COMPILER}")
message(STATUS "CMAKE_CXX_COMPILER: ${CMAKE_CXX_COMPILER}")
message(STATUS "CMAKE_SYSTEM_NAME: ${CMAKE_SYSTEM_NAME}")
message(STATUS "CMAKE_SYSROOT: ${CMAKE_SYSROOT}")
message(STATUS "========================")
这样一运行cmake,所有关键变量一目了然。哪里不对,一眼就能看出来。
好了,关于CMake变量与缓存,咱们就聊到这儿。记住一句话:变量是CMake的"内存",缓存是CMake的"硬盘"。理解了这两者的区别和交互方式,你就能驾驭大部分CMake配置场景了。
公众号:蓝海资料掘金营,微信deep3321