目錄
一. 緩存變量
二.創建緩存變量
2.1.使用set()來創建緩存變量
2.2.使用FORCE參數來覆蓋緩存變量
2.2.1.示例1——不帶force的set是不能覆蓋已經存在的緩存變量的
2.2.2.示例2——帶force的set才能覆蓋已經存在的緩存變量
2.2.3.對比示例
2.3.命令行 -D 創建/覆蓋緩存變量
2.3.1.直接使用-D來創建/覆蓋緩存變量
2.3.2.使用-d來替換掉CMakeLists.txt里面指定的緩存變量
三.緩存變量的作用域
3.1.示例1——全局可見行和全局唯一性
3.2.示例2——全局可見性
四. 緩存變量與普通變量的交互:優先級規則
4.1.示例1——普通變量的設置會“遮蓋”緩存變量
4.2.示例2——普通變量的遮蓋效應會傳遞到子作用域
一. 緩存變量
我們去官網看看是怎么說的:set — CMake 4.1.1 Documentation
翻譯下來也大概就是下面這樣子。
設置 CMake 緩存條目 (Set Cache Entry)
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
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}'")
其實我們可以一鍵運行下面這個命令來進行搭建這個目錄結構
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
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語句來搭建這個目錄結構和文件
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
接下來我們就來構建這個項目
mkdir build && cd build && cmake ..
我們發現覆蓋前后都是一樣的。也就是說,我們的覆蓋是失敗的。
2.2.2.示例2——帶force的set才能覆蓋已經存在的緩存變量
好的 👍,我給你一個「對照實驗」:同一個項目里,先演示 不帶 FORCE 時無法覆蓋,再演示 加 FORCE 成功覆蓋。
📂 目錄結構
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
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}'")
我們可以通過下面這個命令來一鍵搭建出這個目錄結構和文件
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
接下來我們就來構建我們的項目
mkdir build && cd build && cmake ..
我們發現,每一次打印的結果都是不一樣的,這就更加說明了我們的猜想
- 不帶 FORCE → 已存在的緩存值不會被覆蓋。
-
帶 FORCE → 立刻覆蓋緩存里的舊值。
2.2.3.對比示例
我們可以寫一個例子,用 兩個緩存變量,分別演示 不使用 FORCE 和 使用 FORCE 的效果。
📂 目錄結構
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
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)")
其實我們可以通過下面這個目錄來一鍵構建出這個目錄結構和文件
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
?
接下來我們就來構建項目
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
可以幾乎空,但保留一個最小聲明:
cmake_minimum_required(VERSION 3.15)
project(DCacheDemo)
接下來我們就來看看
rm -rf build && mkdir build && cd build
??
接下來我們就使用 -D 創建新緩存變量
cmake .. -DMY_NEW_VAR="hello"
??
我們現在就可以去CMakeCache.txt里面查看這個緩存變量
grep MY_NEW_VAR CMakeCache.txt
??
跟我們設置的一模一樣的。
接下來我們將使用 -D 覆蓋已有緩存變量
然后運行:
cmake .. -DMY_NEW_VAR="hello world"
??
我們現在就可以去CMakeCache.txt里面查看這個緩存變量
grep MY_NEW_VAR CMakeCache.txt
??
怎么樣?
2.3.2.使用-d來替換掉CMakeLists.txt里面指定的緩存變量
📂 目錄結構
demo/
└── CMakeLists.txt
🔹 demo/CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(DOverrideDemo)# 定義一個緩存變量
set(MY_OPTION "from_cmakelists" CACHE STRING "演示緩存變量")# 打印最終結果
message("MY_OPTION='${MY_OPTION}'")
我們可以一鍵搭建這個項目的目錄結構和文件
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
接下來我們來構建一下這個項目
rm -rf build && mkdir build && cd build && cmake ..
現在我們可以去CMakeCache.txt
里看看對應條目:
接下來我們使用 -D
覆蓋 CMakeLists.txt 中的緩存變量
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
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
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
message("孫子目錄進入時 MY_CACHE_VAR='${MY_CACHE_VAR}'")set(MY_CACHE_VAR "from_subsub" CACHE STRING "演示全局緩存變量" FORCE)
message("孫子目錄修改后 MY_CACHE_VAR='${MY_CACHE_VAR}'")
其實我們可以通過一行 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}'")
EOFcat > 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}'")
EOFcat > 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
接下來我們就來構建我們的項目
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 (頂層)
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 (子目錄)
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 (孫子目錄)
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 語句來創建我們的目錄結構和文件
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}'")
EOFcat > 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}'")
EOFcat > 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
接下來我們就來構建我們的項目
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
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
# 子目錄開始
message("[子目錄] 進入時: MY_VAR='${MY_VAR}' (繼承自緩存)")# 定義同名的普通變量(遮蓋緩存變量)
set(MY_VAR "normal_value")message("[子目錄] 設置普通變量后: MY_VAR='${MY_VAR}' (普通變量覆蓋緩存)")# 如果想顯式訪問緩存值(CMake 3.13+ 支持)
message("[子目錄] 顯式訪問緩存: $CACHE{MY_VAR}")
🔹 一行 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}' (緩存)")
EOFcat > 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
接下來我們就來構建我們的項目
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
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
# 子目錄開始
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
# 孫子目錄開始
message("[孫子目錄] 進入時: MY_VAR='${MY_VAR}' (繼承了子目錄的普通變量覆蓋)")
message("[孫子目錄] 顯式訪問緩存: $CACHE{MY_VAR}")
我們可以直接復制下面這個代碼去一鍵構建出目錄結構和文件
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}' (緩存)")
EOFcat > 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}' (普通變量還在遮蓋緩存)")
EOFcat > demo/sub/subsub/CMakeLists.txt <<'EOF'
message("[孫子目錄] 進入時: MY_VAR='${MY_VAR}' (繼承了子目錄的普通變量覆蓋)")
message("[孫子目錄] 顯式訪問緩存: $CACHE{MY_VAR}")
EOF
接下來我們就來構建這個項目
mkdir build && cd build && cmake ..
? 這樣你就能清楚看到:
-
子目錄定義的普通變量會傳遞到孫子目錄,繼續遮蓋緩存變量。
-
但是回到頂層時,普通變量作用域消失,重新讀取的是緩存值。