目录
[一. 缓存变量](#一. 缓存变量)
2.2.1.示例1------不带force的set是不能覆盖已经存在的缓存变量的
2.2.2.示例2------带force的set才能覆盖已经存在的缓存变量
[2.3.命令行 -D 创建/覆盖缓存变量](#2.3.命令行 -D 创建/覆盖缓存变量)
2.3.2.使用-d来替换掉CMakeLists.txt里面指定的缓存变量
[四. 缓存变量与普通变量的交互:优先级规则](#四. 缓存变量与普通变量的交互:优先级规则)
4.2.示例2------普通变量的遮盖效应会传递到子作用域
一. 缓存变量
我们去官网看看是怎么说的:set --- CMake 4.1.1 Documentation
翻译下来也大概就是下面这样子。
设置 CMake 缓存条目 (Set Cache Entry)
bash
set(<变量名> <值>... CACHE <类型> <说明文字> [FORCE])
这条命令用于在 CMake 中创建或修改一个缓存变量 。您可以把缓存变量想象成项目的配置设置 ,这些设置会被保存下来(在 CMakeCache.txt
文件中),以便下次运行 CMake 时记住用户的选择。正因为它们旨在让用户自定义,所以默认情况下,如果该缓存条目已经存在,set
命令不会覆盖它。如果您希望强制覆盖 现有的值,请使用 FORCE
选项。
参数详解:
-
<类型>
(必须指定): 定义了变量的类型,它决定了在cmake-gui
等图形化工具中如何与用户交互。必须是以下类型之一:-
BOOL
: 布尔值,只能是ON
或OFF
。例如,用来控制是否编译某个功能模块。在cmake-gui
中会显示为一个复选框,非常直观。 -
FILEPATH
: 指向磁盘上某个文件 的路径。例如,指定一个外部工具的路径。在cmake-gui
中会提供一个文件选择对话框,让用户方便地浏览和选择。 -
PATH
: 指向磁盘上某个目录 的路径。例如,指定第三方库的安装目录。在cmake-gui
中同样会提供一个目录选择对话框。 -
STRING
: 一行普通的文本。在cmake-gui
中显示为一个文本框 。如果您还通过set_property(CACHE <变量名> PROPERTY STRINGS ...)
设置了可选值列表,它则会变成一个下拉选择框,让用户从预定义的选项中选择。 -
INTERNAL
: 也是一行文本,但主要用于 CMake 内部使用。这种类型的变量不会显示在cmake-gui
中 ,用户无法看到或修改。它通常用于在多次运行 CMake 时在内部持久化存储一些状态或信息。注意:使用此类型隐含了FORCE
选项,即会自动覆盖旧值。
-
-
<说明文字>
(必须指定): 这是一段简单的描述文字,用于解释这个缓存变量的作用和目的。当用户在cmake-gui
中将鼠标悬停在变量上时,这段文字就会显示出来,帮助用户理解该如何配置。请务必写得清晰明了。 -
[FORCE]
(可选): 就像上面提到的,加上这个选项会强制用新值覆盖已经存在的缓存条目。如果你确信无论之前用户设置成什么,都需要被当前脚本中的值重置,那就使用它。
重要注意事项 (非常重要!):
-
变量覆盖规则 (优先级): CMake 中变量的查找规则是:普通变量 会覆盖未使用的缓存变量 。这意味着,如果你之前用
set(MY_VAR "value")
(没有CACHE
)设置了一个普通变量,那么直接读取MY_VAR
得到的是普通变量的值,而不是缓存变量的值。要访问缓存变量,需要使用$CACHE{MY_VAR}
语法(CMake 3.13及以上版本)。这是一个非常常见的困惑点! -
处理命令行创建的变量: 用户可能在运行 CMake 时通过
-D<变量>=<值>
的命令行选项创建了一个缓存变量,但没有指定类型。此时,set(... CACHE ...)
命令会为其补充上类型。 -
路径转换: 如果一个通过
-D
创建的、类型为PATH
或FILEPATH
的缓存变量,其值是相对路径 (如../mylib
),那么当set
命令为其显式设置类型时,CMake 会自动将这个相对路径转换为基于当前工作目录的绝对路径,从而保证路径的准确性。
二.创建缓存变量
2.1.使用set()来创建缓存变量
📂 目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
bash
cmake_minimum_required(VERSION 3.15)
project(CacheVarDemo)
# 1. 创建一些缓存变量(会写入 build/CMakeCache.txt)
set(MY_BOOL ON CACHE BOOL "是否启用某个功能模块")
set(MY_PATH "../mylib" CACHE PATH "第三方库路径")
set(MY_FILE "../README.md" CACHE FILEPATH "某个文件路径")
set(MY_STR "hello" CACHE STRING "一段字符串")
# 2. 显示结果
message("MY_BOOL='${MY_BOOL}'")
message("MY_PATH='${MY_PATH}'")
message("MY_FILE='${MY_FILE}'")
message("MY_STR='${MY_STR}'")
其实我们可以一键运行下面这个命令来进行搭建这个目录结构
bash
mkdir -p demo && cat > demo/CMakeLists.txt <<'EOF'
cmake_minimum_required(VERSION 3.15)
project(CacheVarDemo)
# 1. 创建一些缓存变量(会写入 build/CMakeCache.txt)
set(MY_BOOL ON CACHE BOOL "是否启用某个功能模块")
set(MY_PATH "../mylib" CACHE PATH "第三方库路径")
set(MY_FILE "../README.md" CACHE FILEPATH "某个文件路径")
set(MY_STR "hello" CACHE STRING "一段字符串")
# 2. 显示结果
message("MY_BOOL='${MY_BOOL}'")
message("MY_PATH='${MY_PATH}'")
message("MY_FILE='${MY_FILE}'")
message("MY_STR='${MY_STR}'")
EOF
在 demo
目录下执行:
rm -rf build && mkdir build && cd build && cmake ..
大家仔细看看
几乎所有的缓存变量都会被记录到 CMakeCache.txt
文件中。 这个文件本质上就是一个持久化的键值对存储,CMake 的主要目的就是通过这个文件来记住用户的配置选择,从而实现"一次配置,多次使用",避免每次运行时都重新询问所有配置。
我们现在就去查看CMakeCache.txt中的内容
嗯?怎么这么多别的变量啊?
CMakeCache.txt
文件不仅仅存储了通过 set(... CACHE ...)
或 -D
选项显式设置的变量,它更是一个庞大的数据库,存储了CMake在配置和生成过程中产生的、为了确保下次运行一致所必需的大量内部缓存变量。
那么我们怎么查询我们设置的缓存变量呢?其实我们可以借助grep
grep MY_ CMakeCache.txt
意思是:
-
grep
:Linux/Unix 下的文本搜索工具。 -
MY_
:要搜索的关键字(这里是匹配所有以MY_
开头的变量名)。 -
CMakeCache.txt
:要搜索的文件,就是 CMake 生成的缓存文件。
怎么样?还是很容易理解的吧。
2.2.使用FORCE参数来覆盖缓存变量
我们来好好了解一下
set(<variable> <value> CACHE <type> <docstring> [FORCE])
命令的执行遵循一套严格的规则:
场景一:缓存变量不存在(初次运行)
-
触发条件 :当你第一次在一个空的构建目录中运行 CMake 时,
CMakeCache.txt
文件不存在或其中没有名为<variable>
的条目。 -
CMake 的决策逻辑:
-
检查 :在缓存中查找
<variable>
,结果是没找到。 -
行动 :"用户显然还没有机会设置这个变量。我将把我(开发者)提供的
<value>
作为初始值,并将其持久化到缓存中。"
-
-
最终结果 :缓存变量 被创建 ,其值被设置为命令中提供的
<value>
。这个值会被写入CMakeCache.txt
文件,成为一个正式的、可供用户修改的配置选项。 -
比喻 :你提供了一份合同的初稿,因为还没有正式版本,所以初稿直接被采纳为正式合同。
场景二:缓存变量已存在(后续运行)
-
触发条件 :在后续的配置运行中,
CMakeCache.txt
文件已经存在,并且包含了名为<variable>
的条目。它的值可能是:-
之前设置的默认值。
-
用户通过
cmake-gui
/ccmake
修改后的值。 -
用户通过命令行
-D<variable>=<new_value>
设置的值。
-
-
CMake 的决策逻辑(无
FORCE
关键字):-
检查 :在缓存中查找
<variable>
,结果"命中"!其当前值为[cached_value]
。 -
决策 :"这个变量已经存在了。这意味着用户可能已经看到了它,并且有机会做出自己的选择。我的职责是提供一个默认值,而不是一个强制值。 既然用户没有表达修改的意图(这次运行没有用新的
-D
重新指定),那么我应该继续信任并保留缓存中现有的值。" -
行动 :忽略 本次
set
命令中提供的<value>
。
-
-
最终结果 :缓存变量的值 保持不变 。这次
set
命令实际上成了一个"空操作"。 -
比喻:用户已经在你给的合同初稿上做了修改并签了字。你不会拿一份新的空白的初稿覆盖掉已经签署的合同。你尊重用户的最终决定。
场景三:强制覆盖(使用 FORCE
关键字)
-
触发条件 :在命令中使用了
FORCE
选项:set(... CACHE ... FORCE)
。 -
CMake 的决策逻辑:
-
检查 :在缓存中查找
<variable>
,结果"命中"!其当前值为[old_value]
。 -
决策 :"虽然这个变量已经存在,但命令中包含了
FORCE
关键字!这说明开发者明确意图要覆盖当前的值,无论用户之前设置过什么。" -
行动 :使用本次
set
命令中提供的<value>
覆盖缓存中现有的值。
-
-
最终结果 :缓存变量的值 被强制更新 为命令中提供的新
<value>
。 -
比喻:这是一个"单方面修订条款"的行为。无论合同之前是什么状态,现在都用这份新的版本强制替换它。
2.2.1.示例1------不带force的set是不能覆盖已经存在的缓存变量的
话不多说,我们先看例子
📂 目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
bash
cmake_minimum_required(VERSION 3.15)
project(CacheNoForceDemo)
# 第一次设置缓存变量
set(MY_VAR "first" CACHE STRING "测试变量")
message("第一次设置后 MY_VAR='${MY_VAR}'")
# 第二次尝试覆盖(不带 FORCE)
set(MY_VAR "second" CACHE STRING "测试变量")
message("第二次尝试覆盖后 MY_VAR='${MY_VAR}'")
我们可以通过下面这一条连着的bash语句来搭建这个目录结构和文件
bash
mkdir -p demo && \
cat > demo/CMakeLists.txt <<'EOF'
cmake_minimum_required(VERSION 3.15)
project(CacheNoForceDemo)
# 第一次设置缓存变量
set(MY_VAR "first" CACHE STRING "测试变量")
message("第一次设置后 MY_VAR='${MY_VAR}'")
# 第二次尝试覆盖(不带 FORCE)
set(MY_VAR "second" CACHE STRING "测试变量")
message("第二次尝试覆盖后 MY_VAR='${MY_VAR}'")
EOF

接下来我们就来构建这个项目
bash
mkdir build && cd build && cmake ..

我们发现覆盖前后都是一样的。也就是说,我们的覆盖是失败的。
2.2.2.示例2------带force的set才能覆盖已经存在的缓存变量
好的 👍,我给你一个「对照实验」:同一个项目里,先演示 不带 FORCE 时无法覆盖 ,再演示 加 FORCE 成功覆盖。
📂 目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
bash
cmake_minimum_required(VERSION 3.15)
project(CacheForceCompareDemo)
# 第一次设置缓存变量
set(MY_VAR "first" CACHE STRING "测试变量")
message("第一次设置后 MY_VAR='${MY_VAR}'")
# 第二次尝试覆盖(不带 FORCE)
set(MY_VAR "second" CACHE STRING "测试变量")
message("第二次尝试覆盖后 MY_VAR='${MY_VAR}'")
# 第三次尝试覆盖(加 FORCE)
set(MY_VAR "third" CACHE STRING "测试变量" FORCE)
message("第三次使用 FORCE 覆盖后 MY_VAR='${MY_VAR}'")
我们可以通过下面这个命令来一键搭建出这个目录结构和文件
bash
mkdir -p demo && \
cat > demo/CMakeLists.txt <<'EOF'
cmake_minimum_required(VERSION 3.15)
project(CacheForceCompareDemo)
# 第一次设置缓存变量
set(MY_VAR "first" CACHE STRING "测试变量")
message("第一次设置后 MY_VAR='${MY_VAR}'")
# 第二次尝试覆盖(不带 FORCE)
set(MY_VAR "second" CACHE STRING "测试变量")
message("第二次尝试覆盖后 MY_VAR='${MY_VAR}'")
# 第三次尝试覆盖(加 FORCE)
set(MY_VAR "third" CACHE STRING "测试变量" FORCE)
message("第三次使用 FORCE 覆盖后 MY_VAR='${MY_VAR}'")
EOF

接下来我们就来构建我们的项目
bash
mkdir build && cd build && cmake ..

我们发现,每一次打印的结果都是不一样的,这就更加说明了我们的猜想
- 不带 FORCE → 已存在的缓存值不会被覆盖。
- 带 FORCE → 立刻覆盖缓存里的旧值。
2.2.3.对比示例
我们可以写一个例子,用 两个缓存变量 ,分别演示 不使用 FORCE 和 使用 FORCE 的效果。
📂 目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
bash
cmake_minimum_required(VERSION 3.15)
project(CacheTwoVarsDemo)
# 第一次创建两个缓存变量
set(VAR1 "first1" CACHE STRING "第一个缓存变量")
set(VAR2 "first2" CACHE STRING "第二个缓存变量")
# 尝试修改,不加 FORCE
set(VAR1 "second1" CACHE STRING "第一个缓存变量")
set(VAR2 "second2" CACHE STRING "第二个缓存变量" FORCE)
# 打印结果
message("VAR1='${VAR1}' (不使用 FORCE)")
message("VAR2='${VAR2}' (使用 FORCE)")
其实我们可以通过下面这个目录来一键构建出这个目录结构和文件
bash
mkdir -p demo && cat > demo/CMakeLists.txt <<'EOF'
cmake_minimum_required(VERSION 3.15)
project(CacheTwoVarsDemo)
# 第一次创建两个缓存变量
set(VAR1 "first1" CACHE STRING "第一个缓存变量")
set(VAR2 "first2" CACHE STRING "第二个缓存变量")
# 尝试修改,不加 FORCE
set(VAR1 "second1" CACHE STRING "第一个缓存变量")
set(VAR2 "second2" CACHE STRING "第二个缓存变量" FORCE)
# 打印结果
message("VAR1='${VAR1}' (不使用 FORCE)")
message("VAR2='${VAR2}' (使用 FORCE)")
EOF
接下来我们就来构建项目
bash
rm -rf build && mkdir build && cd build && cmake ..
🔹 解释
- VAR1 第一次创建是
"first1"
,之后修改"second1"
没有 FORCE ,所以缓存保持"first1"
。 - VAR2 第一次创建是
"first2"
,修改"second2"
使用 FORCE ,缓存被覆盖成"second2"
。
我们可以去CMakeChace.txt里面看看

和我们的运行结果可是一模一样的。
2.3.命令行 -D 创建/覆盖缓存变量
2.3.1.直接使用-D来创建/覆盖缓存变量
📂 目录结构
demo/
└── CMakeLists.txt
这里的 CMakeLists.txt
可以几乎空,但保留一个最小声明:
bash
cmake_minimum_required(VERSION 3.15)
project(DCacheDemo)
接下来我们就来看看
bash
rm -rf build && mkdir build && cd build

接下来我们就使用 -D 创建新缓存变量
bash
cmake .. -DMY_NEW_VAR="hello"

我们现在就可以去CMakeCache.txt里面查看这个缓存变量
bash
grep MY_NEW_VAR CMakeCache.txt

跟我们设置的一模一样的。
接下来我们将使用**-D 覆盖已有缓存变量**

然后运行:
bash
cmake .. -DMY_NEW_VAR="hello world"

我们现在就可以去CMakeCache.txt里面查看这个缓存变量
bash
grep MY_NEW_VAR CMakeCache.txt

怎么样?
2.3.2.使用-d来替换掉CMakeLists.txt里面指定的缓存变量
📂 目录结构
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
bash
cmake_minimum_required(VERSION 3.15)
project(DOverrideDemo)
# 定义一个缓存变量
set(MY_OPTION "from_cmakelists" CACHE STRING "演示缓存变量")
# 打印最终结果
message("MY_OPTION='${MY_OPTION}'")
我们可以一键搭建这个项目的目录结构和文件
bash
mkdir -p demo && cat > demo/CMakeLists.txt <<'EOF'
cmake_minimum_required(VERSION 3.15)
project(DOverrideDemo)
# 定义一个缓存变量
set(MY_OPTION "from_cmakelists" CACHE STRING "演示缓存变量")
# 打印结果
message("MY_OPTION='${MY_OPTION}'")
EOF
接下来我们来构建一下这个项目
bash
rm -rf build && mkdir build && cd build && cmake ..

现在我们可以去CMakeCache.txt
里看看对应条目:

接下来我们使用 -D
覆盖 CMakeLists.txt 中的缓存变量
bash
cmake .. -DMY_OPTION="from_commandline"

这个时候我们回去那个CMakeCache.txt
看看这个条目被更新为:

三.缓存变量的作用域
在 CMake 的变量体系中,缓存变量是一个特殊的存在,它完全超越了普通变量所遵循的"目录作用域"规则。
您可以将其理解为项目配置中的全局变量 或持久化设置。
它的核心特征在于其全局可见性 和持久化存储,这与普通变量的局部性和临时性形成了鲜明对比。
核心特性:全局唯一与持久化
-
全局唯一性(单一事实来源) :
整个 CMake 项目中,任何一个特定的缓存变量有且只有一个 。它被存储在一个独立的、全局的存储区中,通常被视为所有目录作用域之上的一个共享层。无论您在当前目录、子目录,还是父目录中读取一个名为
MY_CACHE_VAR
的缓存变量,您访问的都是同一个全局实体。它的值在任何地方、任何时候(在一次配置过程中)都是一致的。 -
无视目录作用域隔离 :
这是缓存变量与普通变量最根本的区别。普通变量严格遵守目录作用域的"向下继承,向上隔离"规则。而缓存变量则完全无视这堵"墙"。
-
读操作:在任何目录作用域中读取缓存变量,得到的都是其全局唯一的值。
-
写操作 :在任何目录作用域中修改缓存变量的值,都会立即更新这个全局唯一的值,并且这个更改立刻对所有其他目录作用域可见。在一个子目录中修改了缓存变量,父目录或其他兄弟目录在随后读取它时,会立刻得到这个新值。这彻底打破了普通变量那种"修改互不影响"的隔离性。
-
-
持久化存储(跨运行存在) :
缓存变量的值不会被保存在
CMakeLists.txt
文件里,而是会被写入到 CMake 构建目录下的CMakeCache.txt
文件中。这个文件是 CMake 的"记忆中心"。这意味着:-
一旦一个缓存变量被设置,它的值会在您多次运行
cmake
配置命令的过程中持续存在。 -
这正是图形化配置工具(如
cmake-gui
或ccmake
)能够展示并允许用户修改的变量列表。 -
要清除一个缓存变量,必须手动删除构建目录、在 GUI 中操作,或使用
unset(... CACHE)
命令。
-
3.1.示例1------全局可见行和全局唯一性
📂 目录结构
demo/
├── CMakeLists.txt
└── sub/
├── CMakeLists.txt
└── subsub/
└── CMakeLists.txt
🔹 顶层 demo/CMakeLists.txt
bash
cmake_minimum_required(VERSION 3.15)
project(GlobalCacheVarDemo)
set(MY_CACHE_VAR "from_top" CACHE STRING "演示全局缓存变量" FORCE)
message("顶层看到 MY_CACHE_VAR='${MY_CACHE_VAR}'")
add_subdirectory(sub)
message("顶层再次看到 MY_CACHE_VAR='${MY_CACHE_VAR}'")
🔹 子目录 demo/sub/CMakeLists.txt
bash
message("子目录进入时 MY_CACHE_VAR='${MY_CACHE_VAR}'")
set(MY_CACHE_VAR "from_sub" CACHE STRING "演示全局缓存变量" FORCE)
message("子目录修改后 MY_CACHE_VAR='${MY_CACHE_VAR}'")
add_subdirectory(subsub)
# 在孙子目录修改完之后,再次查看
message("子目录返回后 MY_CACHE_VAR='${MY_CACHE_VAR}'")
🔹 孙子目录 demo/sub/subsub/CMakeLists.txt
bash
message("孙子目录进入时 MY_CACHE_VAR='${MY_CACHE_VAR}'")
set(MY_CACHE_VAR "from_subsub" CACHE STRING "演示全局缓存变量" FORCE)
message("孙子目录修改后 MY_CACHE_VAR='${MY_CACHE_VAR}'")
其实我们可以通过一行 bash(一次性创建文件并运行)来快速搭建这个目录结构和文件。
bash
mkdir -p demo/sub/subsub && cat > demo/CMakeLists.txt <<'EOF'
cmake_minimum_required(VERSION 3.15)
project(GlobalCacheVarDemo)
set(MY_CACHE_VAR "from_top" CACHE STRING "演示全局缓存变量" FORCE)
message("顶层看到 MY_CACHE_VAR='${MY_CACHE_VAR}'")
add_subdirectory(sub)
message("顶层再次看到 MY_CACHE_VAR='${MY_CACHE_VAR}'")
EOF
cat > demo/sub/CMakeLists.txt <<'EOF'
message("子目录进入时 MY_CACHE_VAR='${MY_CACHE_VAR}'")
set(MY_CACHE_VAR "from_sub" CACHE STRING "演示全局缓存变量" FORCE)
message("子目录修改后 MY_CACHE_VAR='${MY_CACHE_VAR}'")
add_subdirectory(subsub)
message("子目录返回后 MY_CACHE_VAR='${MY_CACHE_VAR}'")
EOF
cat > demo/sub/subsub/CMakeLists.txt <<'EOF'
message("孙子目录进入时 MY_CACHE_VAR='${MY_CACHE_VAR}'")
set(MY_CACHE_VAR "from_subsub" CACHE STRING "演示全局缓存变量" FORCE)
message("孙子目录修改后 MY_CACHE_VAR='${MY_CACHE_VAR}'")
EOF

接下来我们就来构建我们的项目
bash
mkdir build && cd build && cmake ..

大家仔细观察,就会发现这3个CMakeLists.txt里面操作的都是同一个缓存变量!!!!这就验证了缓存变量的全局可见性和全局唯一性。
注意:如果我们不在set里面加force,运行结果就会是下面这样子。
3.2.示例2------全局可见性
接下来我们将
-
在 每一层目录 都各自
set
一个不同名字的缓存变量(例如TOP_CACHE_VAR
、SUB_CACHE_VAR
、SUBSUB_CACHE_VAR
)。 -
并且在 每一层 打印出 全部 3 个变量,直观演示「缓存变量是全局唯一的」特性。
📂 目录结构
demo/
├── CMakeLists.txt
└── sub/
├── CMakeLists.txt
└── subsub/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt (顶层)
bash
cmake_minimum_required(VERSION 3.15)
project(CacheThreeVarsDemo)
# 顶层设置一个缓存变量
set(TOP_CACHE_VAR "set_in_top" CACHE STRING "顶层缓存变量")
message("[顶层] 进入时: TOP='${TOP_CACHE_VAR}', SUB='${SUB_CACHE_VAR}', SUBSUB='${SUBSUB_CACHE_VAR}'")
# 进入子目录
add_subdirectory(sub)
message("[顶层] 返回时: TOP='${TOP_CACHE_VAR}', SUB='${SUB_CACHE_VAR}', SUBSUB='${SUBSUB_CACHE_VAR}'")
🔹 demo/sub/CMakeLists.txt (子目录)
bash
message("[子目录] 进入时: TOP='${TOP_CACHE_VAR}', SUB='${SUB_CACHE_VAR}', SUBSUB='${SUBSUB_CACHE_VAR}'")
# 设置子目录自己的缓存变量
set(SUB_CACHE_VAR "set_in_sub" CACHE STRING "子目录缓存变量")
message("[子目录] 设置后: TOP='${TOP_CACHE_VAR}', SUB='${SUB_CACHE_VAR}', SUBSUB='${SUBSUB_CACHE_VAR}'")
# 进入孙子目录
add_subdirectory(subsub)
message("[子目录] 从孙子返回时: TOP='${TOP_CACHE_VAR}', SUB='${SUB_CACHE_VAR}', SUBSUB='${SUBSUB_CACHE_VAR}'")
🔹 demo/sub/subsub/CMakeLists.txt (孙子目录)
bash
message("[孙子目录] 进入时: TOP='${TOP_CACHE_VAR}', SUB='${SUB_CACHE_VAR}', SUBSUB='${SUBSUB_CACHE_VAR}'")
# 设置孙子目录自己的缓存变量
set(SUBSUB_CACHE_VAR "set_in_subsub" CACHE STRING "孙子目录缓存变量")
message("[孙子目录] 设置后: TOP='${TOP_CACHE_VAR}', SUB='${SUB_CACHE_VAR}', SUBSUB='${SUBSUB_CACHE_VAR}'")
我们可以一键复制下面的 Bash 语句来创建我们的目录结构和文件
bash
mkdir -p demo/sub/subsub && cat > demo/CMakeLists.txt <<'EOF'
cmake_minimum_required(VERSION 3.15)
project(CacheThreeVarsDemo)
set(TOP_CACHE_VAR "set_in_top" CACHE STRING "顶层缓存变量")
message("[顶层] 进入时: TOP='${TOP_CACHE_VAR}', SUB='${SUB_CACHE_VAR}', SUBSUB='${SUBSUB_CACHE_VAR}'")
add_subdirectory(sub)
message("[顶层] 返回时: TOP='${TOP_CACHE_VAR}', SUB='${SUB_CACHE_VAR}', SUBSUB='${SUBSUB_CACHE_VAR}'")
EOF
cat > demo/sub/CMakeLists.txt <<'EOF'
message("[子目录] 进入时: TOP='${TOP_CACHE_VAR}', SUB='${SUB_CACHE_VAR}', SUBSUB='${SUBSUB_CACHE_VAR}'")
set(SUB_CACHE_VAR "set_in_sub" CACHE STRING "子目录缓存变量")
message("[子目录] 设置后: TOP='${TOP_CACHE_VAR}', SUB='${SUB_CACHE_VAR}', SUBSUB='${SUBSUB_CACHE_VAR}'")
add_subdirectory(subsub)
message("[子目录] 从孙子返回时: TOP='${TOP_CACHE_VAR}', SUB='${SUB_CACHE_VAR}', SUBSUB='${SUBSUB_CACHE_VAR}'")
EOF
cat > demo/sub/subsub/CMakeLists.txt <<'EOF'
message("[孙子目录] 进入时: TOP='${TOP_CACHE_VAR}', SUB='${SUB_CACHE_VAR}', SUBSUB='${SUBSUB_CACHE_VAR}'")
set(SUBSUB_CACHE_VAR "set_in_subsub" CACHE STRING "孙子目录缓存变量")
message("[孙子目录] 设置后: TOP='${TOP_CACHE_VAR}', SUB='${SUB_CACHE_VAR}', SUBSUB='${SUBSUB_CACHE_VAR}'")
EOF

接下来我们就来构建我们的项目
bash
mkdir build && cd build && cmake ..

这样你就能看到:
-
每层定义的缓存变量 对全局都可见。
-
即使是后设置的变量(
SUB_CACHE_VAR
、SUBSUB_CACHE_VAR
),顶层最后也能读到它们的值。
要不要我再帮你加一个 普通变量版本(不带 CACHE),并打印对比差异?
四. 缓存变量与普通变量的交互:优先级规则
当一个变量名既作为普通变量存在,又作为缓存变量存在时,CMake 遵循一条明确的优先级规则:
-
普通变量的设置会"遮盖"缓存变量。
-
一旦通过`set(MY_VAR "value")`这样的语句在当前作用域内定义了一个普通变量 ,该作用域及其所有由此向下延伸的子作用域(通过`add_subdirectory()`或`function()`调用进入的新作用域)中,任何对`${MY_VAR}`的求值操作都会直接返回这个新设置的普通变量的值。 缓存中存储的值依然完好无损地存在于全局缓存中 ,只是在当前的变量解析路径上被暂时地"绕过"了。
-
但是,这个"遮盖"效应是局部的,仅限于当前目录作用域及其子作用域。一旦跳出这个范围,仍然可以访问到底层缓存变量的值。
-
**为了提供一种显式且可靠的访问方式,不受当前作用域内普通变量的干扰,CMake引入了`CACHE{MY_VAR}\`语法。** 这是一种强制的、指向性的访问。如果您使用 `CACHE{MY_VAR}` 语法(CMake 3.13+),无论你在哪个作用域,仍然可以访问到底层缓存变量的值。它明确指示CMake解释器绕过所有当前作用域内的普通变量查找,直接访问全局缓存命名空间并获取其中存储的值。
4.1.示例1------普通变量的设置会"遮盖"缓存变量
好 👌 我给你写一个最简单的例子,演示 同名普通变量和缓存变量的优先级关系。
📂 目录结构
demo/
├── CMakeLists.txt
└── sub/
└── CMakeLists.txt
🔹 顶层 demo/CMakeLists.txt
bash
cmake_minimum_required(VERSION 3.15)
project(ShadowCacheDemo)
# 定义一个缓存变量
set(MY_VAR "cache_value" CACHE STRING "缓存变量 MY_VAR")
message("[顶层] 初始: MY_VAR='${MY_VAR}' (缓存)")
# 进入子目录
add_subdirectory(sub)
# 回到顶层后,再次读取
message("[顶层] 返回时: MY_VAR='${MY_VAR}' (缓存)")
🔹 子目录 demo/sub/CMakeLists.txt
bash
# 子目录开始
message("[子目录] 进入时: MY_VAR='${MY_VAR}' (继承自缓存)")
# 定义同名的普通变量(遮盖缓存变量)
set(MY_VAR "normal_value")
message("[子目录] 设置普通变量后: MY_VAR='${MY_VAR}' (普通变量覆盖缓存)")
# 如果想显式访问缓存值(CMake 3.13+ 支持)
message("[子目录] 显式访问缓存: $CACHE{MY_VAR}")
🔹 一行 Bash 运行
bash
mkdir -p demo/sub && cat > demo/CMakeLists.txt <<'EOF'
cmake_minimum_required(VERSION 3.15)
project(ShadowCacheDemo)
set(MY_VAR "cache_value" CACHE STRING "缓存变量 MY_VAR")
message("[顶层] 初始: MY_VAR='${MY_VAR}' (缓存)")
add_subdirectory(sub)
message("[顶层] 返回时: MY_VAR='${MY_VAR}' (缓存)")
EOF
cat > demo/sub/CMakeLists.txt <<'EOF'
message("[子目录] 进入时: MY_VAR='${MY_VAR}' (继承自缓存)")
set(MY_VAR "normal_value")
message("[子目录] 设置普通变量后: MY_VAR='${MY_VAR}' (普通变量覆盖缓存)")
message("[子目录] 显式访问缓存: $CACHE{MY_VAR}")
EOF

接下来我们就来构建我们的项目
bash
mkdir build && cd build && cmake ..
输出

这也就说明了
当一个普通变量 被设置成一个与缓存变量 同名的名字时,在其作用域内 ,普通变量 的值会"遮盖"(Shadow)掉缓存变量 的值。这意味着,直接使用 ${MY_VAR}
将会访问到普通变量的值。
但是,缓存变量本身的值并没有被改变 ,它依然安全地存储在 CMakeCache.txt
中。一旦离开了那个普通变量的作用域(例如,从子目录返回到父目录),${MY_VAR}
又会重新指向那个未被改变的缓存变量的值。
我们来仔细讲解一下
1.顶层目录,初始阶段
-
set(MY_VAR "cache_value" CACHE STRING "缓存变量 MY_VAR")
- 这行代码定义了一个名为
MY_VAR
的缓存变量 ,其值为"cache_value"
。
- 这行代码定义了一个名为
-
message("[顶层] 初始: MY_VAR='${MY_VAR}' (缓存)")
-
此时,
${MY_VAR}
读取到的就是这个缓存变量的值。 -
输出 :
[顶层] 初始: MY_VAR='cache_value' (缓存)
-
2.进入子目录 sub/
-
add_subdirectory(sub)
命令执行,CMake 开始处理sub/CMakeLists.txt
。 -
message("[子目录] 进入时: MY_VAR='${MY_VAR}' (继承自缓存)")
-
子目录继承 了父目录的作用域。此时还没有同名的普通变量,所以
${MY_VAR}
仍然解析为缓存变量的值。 -
输出 :
[子目录] 进入时: MY_VAR='cache_value' (继承自缓存)
-
3.在子目录中设置普通变量
-
set(MY_VAR "normal_value")
(注意:没有CACHE
关键字)- 这行代码定义了一个同名的普通变量 。根据"遮盖"规则,从现在开始,在当前目录(子目录)的作用域内 ,
${MY_VAR}
将指向这个新的普通变量。
- 这行代码定义了一个同名的普通变量 。根据"遮盖"规则,从现在开始,在当前目录(子目录)的作用域内 ,
-
message("[子目录] 设置普通变量后: MY_VAR='${MY_VAR}' (普通变量覆盖缓存)")
-
正如规则所述,它现在读取到的是普通变量的值
"normal_value"
。 -
输出 :
[子目录] 设置普通变量后: MY_VAR='normal_value' (普通变量覆盖缓存)
-
4.显式访问缓存变量
-
message("[子目录] 显式访问缓存: $CACHE{MY_VAR}")
-
CMake 3.13 引入了
$CACHE{VARNAME}
语法,允许你显式地、直接地 访问缓存变量的值,绕过任何可能存在的同名普通变量。 -
所以,这里它成功地读取到了被"遮盖"的缓存变量的原始值
"cache_value"
。 -
输出 :
[子目录] 显式访问缓存: cache_value
-
这个操作非常重要,它证明了缓存变量
MY_VAR
的值自始至终都没有被改变过。
-
5.返回顶层目录
-
子目录的
CMakeLists.txt
处理完毕,CMake 返回到顶层目录继续执行。 -
当离开子目录的作用域时,在那个作用域内定义的普通变量
MY_VAR
(值为"normal_value"
)就被销毁了。 -
message("[顶层] 返回时: MY_VAR='${MY_VAR}' (缓存)")
-
现在,
${MY_VAR}
前面已经没有同名的普通变量来遮盖它了,所以它再次清晰地指向了那个全局的、一直未变的缓存变量。 -
输出 :
[顶层] 返回时: MY_VAR='cache_value' (缓存)
-
4.2.示例2------普通变量的遮盖效应会传递到子作用域
那我在上一个例子的基础上再加一个 孙子目录,让你清楚看到:
-
普通变量的遮盖效应会传递到子作用域(子目录、孙子目录),
-
但是一旦跳出当前作用域,就恢复为缓存变量。
📂 目录结构
demo/
├── CMakeLists.txt
└── sub/
├── CMakeLists.txt
└── subsub/
└── CMakeLists.txt
🔹 顶层 demo/CMakeLists.txt
bash
cmake_minimum_required(VERSION 3.15)
project(ShadowCacheDemo)
# 定义一个缓存变量
set(MY_VAR "cache_value" CACHE STRING "缓存变量 MY_VAR")
message("[顶层] 初始: MY_VAR='${MY_VAR}' (缓存)")
# 进入子目录
add_subdirectory(sub)
# 回到顶层后,再次读取
message("[顶层] 返回时: MY_VAR='${MY_VAR}' (缓存)")
🔹 子目录 demo/sub/CMakeLists.txt
bash
# 子目录开始
message("[子目录] 进入时: MY_VAR='${MY_VAR}' (继承自缓存)")
# 定义同名普通变量 → 遮盖缓存变量
set(MY_VAR "normal_value")
message("[子目录] 设置普通变量后: MY_VAR='${MY_VAR}' (普通变量覆盖缓存)")
message("[子目录] 显式访问缓存: $CACHE{MY_VAR}")
# 进入孙子目录
add_subdirectory(subsub)
# 回到子目录后
message("[子目录] 返回时: MY_VAR='${MY_VAR}' (普通变量还在遮盖缓存)")
🔹 孙子目录 demo/sub/subsub/CMakeLists.txt
bash
# 孙子目录开始
message("[孙子目录] 进入时: MY_VAR='${MY_VAR}' (继承了子目录的普通变量覆盖)")
message("[孙子目录] 显式访问缓存: $CACHE{MY_VAR}")
我们可以直接复制下面这个代码去一键构建出目录结构和文件
bash
mkdir -p demo/sub/subsub && cat > demo/CMakeLists.txt <<'EOF'
cmake_minimum_required(VERSION 3.15)
project(ShadowCacheDemo)
set(MY_VAR "cache_value" CACHE STRING "缓存变量 MY_VAR")
message("[顶层] 初始: MY_VAR='${MY_VAR}' (缓存)")
add_subdirectory(sub)
message("[顶层] 返回时: MY_VAR='${MY_VAR}' (缓存)")
EOF
cat > demo/sub/CMakeLists.txt <<'EOF'
message("[子目录] 进入时: MY_VAR='${MY_VAR}' (继承自缓存)")
set(MY_VAR "normal_value")
message("[子目录] 设置普通变量后: MY_VAR='${MY_VAR}' (普通变量覆盖缓存)")
message("[子目录] 显式访问缓存: $CACHE{MY_VAR}")
add_subdirectory(subsub)
message("[子目录] 返回时: MY_VAR='${MY_VAR}' (普通变量还在遮盖缓存)")
EOF
cat > demo/sub/subsub/CMakeLists.txt <<'EOF'
message("[孙子目录] 进入时: MY_VAR='${MY_VAR}' (继承了子目录的普通变量覆盖)")
message("[孙子目录] 显式访问缓存: $CACHE{MY_VAR}")
EOF

接下来我们就来构建这个项目
bash
mkdir build && cd build && cmake ..

✅ 这样你就能清楚看到:
-
子目录定义的普通变量会传递到孙子目录,继续遮盖缓存变量。
-
但是回到顶层时,普通变量作用域消失,重新读取的是缓存值。