原文地址:https://cslam.cn/archives/c9f565b5.htmlhtml
本文記錄一下 CMake 變量的定義、原理及其使用。CMake 變量包含 Normal Variables、Cache Variables。經過 set 指令能夠設置兩種不一樣的變量。也能夠在 CMake 腳本中使用和設置環境變量。set(ENV{<variable>} <value>...),本文重點講述 CMake 腳本語言特有的兩種變量。緩存
Normal Variables函數
經過 set(<variable> <value>... [PARENT_SCOPE])這個命令來設置的變量就是 Normal Variables。例如 set(MY_VAL 「666」) ,此時 MY_VAL 變量的值就是 666。測試
Cache Variablesui
經過 set(<variable> <value>... CACHE <type> <docstring> [FORCE])這個命令來設置的變量就是 Cache Variables。例如 set(MY_CACHE_VAL "666" CACHE STRING INTERNAL),此時 MY_CACHE_VAL 就是一個 CACHE 變量。spa
做用域屬於整個 CMakeLists.txt 文件,當該文件包含了 add_subdirectory()、include()、macro()、function()語句時,會出現兩種不一樣的效果。指針
假設,咱們在工程根目錄 CMakeLists.txt 文件中使用 add_subdirectory(src) 包含另外一個 src 目錄,在 src 目錄中有另外一個 CMakeLists.txt 文件。在終端運行的目錄結構以下:code
$ tree . ├── CMakeLists.txt └── src └── CMakeLists.txt 1 directory, 2 files
以根目錄 CMake 文件爲父目錄,src 目錄爲子目錄,此時子目錄 CMake 文件會拷貝一份父目錄文件的 Normal 變量。須要說明的是,咱們在子目錄中若是想要修改父目錄 CMake 文件包含的 Normal 變量。必須經過 set(… PARENT_SCOPE) 的方式。下面經過例子來講明。orm
在父 / 根目錄的 CMakeLists.txt 文件內容以下:htm
cmake_minimum_required(VERSION 3.10) message("父目錄 CMakeLists.txt 文件") set(MY_VAL "666") message("第一次在父目錄 MY_VAL=${MY_VAL}") add_subdirectory(src) message("第二次在父目錄,MY_VAL=${MY_VAL}")
在子目錄 src/CMakeLists.txt 文件內容以下:
cmake_minimum_required(VERSION 3.10) message("進入子目錄 src/CMakeLists.txt 文件") message("在子目錄,MY_VAL=${MY_VAL}") message("退出子目錄")
運行結果:
$ cmake . 父目錄 CMakeLists.txt 文件 第一次在父目錄 MY_VAL=666 進入子目錄 src/CMakeLists.txt 文件 在子目錄,MY_VAL=666 退出子目錄 第二次在父目錄,MY_VAL=666
從結果能夠看出,在子目錄 CMake 文件中能夠直接使用父目錄定義的 MY_VAL 變量的值 666。當在子目錄 CMake 文件中修改 MY_VAL 變量值,看看在父目錄中 MY_VAL 的值如何變化。下面僅僅在子目錄 CMake 文件中加入一行代碼 set(MY_VAL "777"), 最後的子目錄 CMake 文件代碼以下:
cmake_minimum_required(VERSION 3.10) message("進入子目錄 src/CMakeLists.txt 文件") set(MY_VAL "777") # 剛剛加入的 message("在子目錄,MY_VAL=${MY_VAL}") message("退出子目錄")
運行結果:
$ cmake . 父目錄 CMakeLists.txt 文件 第一次在父目錄 MY_VAL=666 進入子目錄 src/CMakeLists.txt 文件 在子目錄,MY_VAL=777 退出子目錄 第二次在父目錄,MY_VAL=666
咱們發如今 src/CMakeLists.txt 中打印的 MY_VAL 的值是 777,而後退出子目錄回到根目錄後,打印 MY_VAL 的值仍然是 666。這就說明了:子目錄的 CMakeLists.txt 文件僅僅是拷貝了一份父目錄的 Normal 變量,即便在子目錄 CMake 文件中修改了 MY_VAL 變量,那也只是子目錄本身的變量,不是父目錄的變量。由於 Normal 變量的做用域就是以 CMakeLists.txt 文件爲基本單元。那麼咱們如何在子目錄 CMake 文件中修改父目錄 CMake 文件的 Normal 變量呢? 咱們須要在子目錄 CMakeLists.txt 文件中設置 MY_VAL 時,加上 PARENT_SCOPE 屬性。即用以下代碼: set(MY_VAL "777" PARENT_SCOPE),子目錄 CMakeLists.txt 文件以下:
cmake_minimum_required(VERSION 3.10) message("進入子目錄 src/CMakeLists.txt 文件") set(MY_VAL "777" PARENT_SCOPE) # 修改處 message("在子目錄,MY_VAL=${MY_VAL}") message("退出子目錄")
運行結果:
$ cmake . 父目錄 CMakeLists.txt 文件 第一次在父目錄 MY_VAL=666 進入子目錄 src/CMakeLists.txt 文件 在子目錄,MY_VAL=666 退出子目錄 第二次在父目錄,MY_VAL=777
能夠看出在第二次回到父目錄時,MY_VAL 的值已經變成了 777。同理,對於 function() 最開始的結論也適用。代碼以下:
cmake_minimum_required(VERSION 3.10) message("父目錄 CMakeLists.txt 文件") set(MY_VAL "666")
message("第一次在父目錄 MY_VAL=${MY_VAL}") # 函數定義 function(xyz test_VAL) # 函數定義處! set(MY_VAL "888" PARENT_SCOPE) message("functions is MY_VAL=${MY_VAL}") endfunction(xyz) xyz(${MY_VAL}) # 調用函數 message("第二次在父目錄,MY_VAL=${MY_VAL}")
運行結果:
父目錄 CMakeLists.txt 文件 第一次在父目錄 MY_VAL=666 functions is MY_VAL=666 第二次在父目錄,MY_VAL=888
能夠看出在該函數中使用 MY_VAL 這個變量值,其實就是一份父目錄變量的值拷貝,此時打印值爲 666。在 函數中修改值,那麼也是用 set(${MY_VAL} 888 PARENT_SCOPE)。此時,退出函數第二次打印變量值時。該值就是在函數中修改好的值 888。 本質講,對於 function() 而言,剛剛說到的父目錄其實不是嚴格正確的。由於函數定義能夠是在其餘 .cmake 模塊文件中定義的。也能夠在其餘 CMakeLists.txt 文件中調用,所以準確的說,這裏的父目錄應該改成『調用函數的地方所屬的 CMakeLists.txt 』,咱們作的這個實驗是在根目錄 CMakeLists.txt 文件中定義了函數,又在本文件中使用了。所以以前的說法理解其意思便可。對於 add_subdirectory() 而言,其實也是說調用的地方。下面的 include()、macro() 例子會涉及到,將 function() 放在一個外部的 .cmake 文件中。那裏也會說明 function() 與 macro() 的不一樣。
如今在上面的根目錄中加入了一個 cmake_modules 目錄。目錄中有一個 Findtest.cmake 文件。新的目錄結構以下:
$ tree
.
├── CMakeLists.txt
├── cmake_modules
│ └── Findtest.cmake
└── src
└── CMakeLists.txt
2 directories, 3 files
在根目錄中的 CMakeLists.txt 文件包含的代碼以下:
cmake_minimum_required(VERSION 3.10) message("父目錄 CMakeLists.txt 文件") set(MY_VAL "666") message("第一次在父目錄 MY_VAL=${MY_VAL}") # 使用 include() 文件的宏 list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake_modules) include(Findtest) # 從 CMAKE_MODULE_PATH 包含的路徑中搜索 Findtest.cmake 文件 #test(${MY_VAL}) # 調用宏 #xyz(${MY_VAL}) # 調用函數 #find_package(test REQUIRED) # 從 CMAKE_MODULE_PATH 包含的路徑中搜索 Findtest.cmake 文件 與 include () 二者的效果是同樣的! message("第二次在父目錄,MY_VAL=${MY_VAL}") message("include test=${test_VAL}") #message("macro_val=${macro_val}")
cmake_modules/Findtest.cmake 文件內容以下:
# 該文件定義了一個函數以及一個宏 message("進入 Findtest.cmake 文件") set(test_VAL "222") # 驗證根目錄 CMake 文件可以訪問這個變量 set(MY_VAL "000") # 測試 include() 效果 # 宏定義 macro(test MY_VA) # 定義一個宏! set(macro_val "1") # 宏內部定義變量 message("macro is MY_VAL=${MY_VA}") set(MY_VAL "999") # 直接修改的就是調用該宏所處的文件中的 Normal 變量 endmacro(test) # 函數定義 function(xyz test_VAL) set(MY_VAL "888" PARENT_SCOPE) # 修改 調用者的 變量 message("function is MY_VAL=${MY_VAL}") endfunction(xyz)
運行結果:
$ cmake . 父目錄 CMakeLists.txt 文件 第一次在父目錄 MY_VAL=666 進入 Findtest.cmake 文件 第二次在父目錄,MY_VAL=000 include test=222
從結果能夠看出,include() 內部是能夠修改調用者 MY_VAL 變量。include() 包含的文件內定義的變量 test_VAL,也能夠在調用 include() 的 CMakeLists.txt 文件中直接訪問,一樣的對於 macro() 也適用,在根目錄 CMake 文件中調用宏,即取消 test(${MY_VAL}) 以及 message(「macro_val=${macro_val}」) 部分的註釋,此時最後輸出結果 :
$ cmake . 父目錄 CMakeLists.txt 文件 第一次在父目錄 MY_VAL=666 進入 Findtest.cmake 文件 macro is MY_VAL=000 第二次在父目錄,MY_VAL=999 include test=222 macro_val=1
能夠看出,此次輸出的結果在第二次進入父目錄後,MY_VAL 變量的值就是 999 了。注意到在根目錄中 CMakeLists.txt 中 註釋語句中有一個 find_package() ,這個和 include() 其實都是同樣的結果。
結合 include() 、macro() 最後結果,可以得出一個結論:經過 include() 和 macro() 至關於把這兩部分包含的代碼直接加入根目錄 CMakeLists.txt 文件中去執行,至關於他們是一個總體。所以變量直接都是互通的。這就有點像 C/C++ 中的 #include 包含頭文件的預處理過程了。這一點其實與剛開始講的 function() 、add_subdirectory() 徹底不一樣,在函數以及 add_subdirectory() 中,他們自己就是一個不一樣的做用域範圍,僅僅經過拷貝調用者的 Normal 值 (僅僅在調用 add_subdirectory() / function() 以前的 Normal 變量),若是要修改調用者包含的 Normal 變量,那麼只能經過 set(MY_VAL "某個值" PARENT_SCOPE)註明咱們修改的是調用者 Normal 值。雖然在 C/C++ 中,能夠經過指針的方式,經過函數能夠修改外部變量值,可是在 CMake 腳本語言中 function() 雖然可以傳入形式參數,可是者本質上就是 C/C++ 中的值拷貝。而不是引用。上面所說的 Normal 變量其實就是一個局部變量。
至關於一個全局變量,咱們在同一個 cmake 工程中均可以使用。Cache 變量有如下幾點說明:
下面經過一個例子來講明以上三點:
首先看一下目錄樹結構:
$ tree . ├── CMakeLists.txt └── src └── CMakeLists.txt 1 directory, 2 files
根目錄 CMakeLists.txt 文件內容以下:
cmake_minimum_required(VERSION 3.10) set(MY_GLOBAL_VAR "666" CACHE STRING INTERNAL ) message("第一次在父目錄 CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") message("第一次在父目錄 MY_GLOBAL_VAR=${MY_GLOBAL_VAR}") add_subdirectory(src) message("第二次在父目錄 CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") message("第二次在父目錄 MY_GLOBAL_VAR=${MY_GLOBAL_VAR}") set(CMAKE_INSTALL_PREFIX "-->usr" ) message("第三次在父目錄 CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}")
src/CMakeLists.txt 文件內容以下:
cmake_minimum_required(VERSION 3.10) message("子目錄,CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}") message("子目錄,MY_GLOBAL_VAR=${MY_GLOBAL_VAR}") set(CMAKE_INSTALL_PREFIX "/usr" CACHE STRING INTERNAL FORCE) set(MY_GLOBAL_VAR "777" CACHE STRING INTERNAL FORCE )
運行結果:
$ cmake . 第一次在父目錄 CMAKE_INSTALL_PREFIX=/usr/local 第一次在父目錄 MY_GLOBAL_VAR=666 子目錄,CMAKE_INSTALL_PREFIX=/usr/local 子目錄,MY_GLOBAL_VAR=666 第二次在父目錄 CMAKE_INSTALL_PREFIX=/usr 第二次在父目錄 MY_GLOBAL_VAR=777 第三次在父目錄 CMAKE_INSTALL_PREFIX=-->usr
程序說明:首先在根目錄中打印一下當前的 Cache 變量 CMAKE_INSTALL_PREFIX 值,主要看看默認值是什麼,而後在子目錄 src/CMakeLists.txt 中再次打印和修改該 Cache 值,目的是熟悉修改全局 Cache 變量,當返回根目錄 CMakeLists.txt 文件中再次執行第二次打印該 Cache 值時,主要看一看在子目錄中修改後的效果。接着在根目錄中設定一個 CMAKE_INSTALL_PREFIX 的 Normal 同名變量,此時第三次打印 CMAKE_INSTALL_PREFIX 的值,此時是爲了證實,當有與 Cache 同名的 Normal 變量出現時,CMake 會優先使用 Normal 屬性的值。經過設定 MY_GLOBAL_VAR 主要是爲了說明能夠本身設定全局 Cache 變量。最後的結果如上面顯示,當咱們再次執行 cmake . 的時候,程序結果以下:
$ cmake . 第一次在父目錄 CMAKE_INSTALL_PREFIX=/usr 第一次在父目錄 MY_GLOBAL_VAR=777 子目錄,CMAKE_INSTALL_PREFIX=/usr 子目錄,MY_GLOBAL_VAR=777 第二次在父目錄 CMAKE_INSTALL_PREFIX=/usr 第二次在父目錄 MY_GLOBAL_VAR=777 第三次在父目錄 CMAKE_INSTALL_PREFIX=-->usr
能夠發現第一次在父目錄打印 CMAKE_INSTALL_PREFIX 和 MY_GOLBAL_VAR 時,他們的結果是上次cmake .後生成的值,存儲在 CMakeCache.txt 中,本身能夠找到,解決方案就是能夠把 CMakeCache.txt 文件刪除,而後在 cmake .咱們之後在實際使用時要注意這個坑。對於修改 Cache 變量的另外一種方式就是cmake -D CMAKE_INSTALL_PREFIX=/usr。能夠本身驗證。這裏說一個重要的點,就是在終端中輸入的 cmake -D var=value . 若是 CMake 中默認有這個 var Cache 變量,那麼此時就是賦值,沒有的話,CMake 就會默認建立了一個全局 Cache 變量而後賦值。(tips: $CACHE{VAR}表示獲取 CACHE 緩存變量的值)。例子以下:(目錄結構同上)
根目錄 CMakeLists.txt :
cmake_minimum_required(VERSION 3.10) set(MY_GLOBAL_VAR "666") message("第一次在父目錄 MY_GLOBAL_VAR=$CACHE{MY_GLOBAL_VAR}") add_subdirectory(src) message("第二次在父目錄局部 MY_GLOBAL_VAR=${MY_GLOBAL_VAR}") message("第二次在父目錄全局 MY_GLOBAL_VAR=$CACHE{MY_GLOBAL_VAR}")
src/CMakeLists.txt :
cmake_minimum_required(VERSION 3.10) message("子目錄,MY_GLOBAL_VAR=${MY_GLOBAL_VAR}") set(MY_GLOBAL_VAR "777" CACHE STRING INTERNAL FORCE )
運行結果:
第一次在父目錄 MY_GLOBAL_VAR=8 子目錄,MY_GLOBAL_VAR=666 第二次在父目錄局部 MY_GLOBAL_VAR=666 第二次在父目錄全局 MY_GLOBAL_VAR=777
有了上面的基礎,相信這個例子很快能看明白。