原文地址:https://cslam.cn/archives/c9f565b5.html
摘要:
本文記錄一下 CMake 變數的定義、原理及其使用,CMake 變數包含 Normal Variables、Cache Variables,通過 set 指令可以設定兩種不同的變數,也可以在 CMake 腳本中使用和設定環境變數,set(ENV{<variable>} <value>...),本文重點講述 CMake 腳本語言特有的兩種變數,
正文:
1、兩種變數的定義參考
Normal Variables
通過 set(<variable> <value>... [PARENT_SCOPE])這個命令來設定的變數就是 Normal Variables,例如 set(MY_VAL “666”) ,此時 MY_VAL 變數的值就是 666,
Cache Variables
通過 set(<variable> <value>... CACHE <type> <docstring> [FORCE])這個命令來設定的變數就是 Cache Variables,例如 set(MY_CACHE_VAL "666" CACHE STRING INTERNAL),此時 MY_CACHE_VAL 就是一個 CACHE 變數,
2、兩種變數的作用域原理及使用
1、Normal Variables
? 作用域屬于整個 CMakeLists.txt 檔案,當該檔案包含了 add_subdirectory()、include()、macro()、function()陳述句時,會出現兩種不同的效果,
(1)、包含 add_subdirectory()、function(),(本質是值拷貝)
假設,我們在工程根目錄 CMakeLists.txt 檔案中使用 add_subdirectory(src) 包含另一個 src 目錄,在 src 目錄中有另一個 CMakeLists.txt 檔案,在終端運行的目錄結構如下:
$ tree . ├── CMakeLists.txt └── src └── CMakeLists.txt 1 directory, 2 files
以根目錄 CMake 檔案為父目錄,src 目錄為子目錄,此時子目錄 CMake 檔案會拷貝一份父目錄檔案的 Normal 變數,需要說明的是,我們在子目錄中如果想要修改父目錄 CMake 檔案包含的 Normal 變數,必須通過 set(… PARENT_SCOPE) 的方式,下面通過例子來說明,
在父 / 根目錄的 CMakeLists.txt 檔案內容如下:
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() 的不同,
(2)、包含 include()、macro() (本質有點類似 c 中的 #include 預處理含義)
現在在上面的根目錄中加入了一個 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 變數其實就是一個區域變數,
2、Cache Variables
相當于一個全域變數,我們在同一個 cmake 工程中都可以使用,Cache 變數有以下幾點說明:
- Cache 變數 CMAKE_INSTALL_PREFIX 默認值是 /usr/local (可以在生成的 CMakeCache.txt 檔案中查看),這時候如果我們 在某個 CMakeLists.txt 中,仍然使用 set(CMAKE_INSTALL_PREFIX “/usr”),那么此時我們 install 的時候,CMake 以后面的 /usr 作為 CMAKE_INSTALL_PREFIX 的值,這是因為 CMake 規定,有一個與 Cache 變數同名的 Normal 變數出現時,后面使用這個變數的值都是以 Normal 為準,如果沒有同名的 Normal 變數,CMake 才會自動使用 Cache 變數,
- 所有的 Cache 變數都會出現在 CMakeCache.txt 檔案中,這個檔案是我們鍵入 cmake .命令后自動出現的檔案,打開這個檔案發現,CMake 本身會有一些默認的全域 Cache 變數,例如:CMAKE_INSTALL_PREFIX、CMAKE_BUILD_TYPE、CMAKE_CXX_FLAGSS 等等,可以自行查看,當然,我們自己定義的 Cache 變數也會出現在這個檔案中,Cache 變數定義格式為 set(<variable> <value> CACHE STRING INTERNAL),這里的 STRING可以替換為 BOOL FILEPATH PATH ,但是要根據前面 value 型別來確定,參考,
- 修改 Cache 變數,可以通過 set(<variable> <value> CACHE INSTERNAL FORCE),另一種方式是直接在終端中使用 cmake -D var=value ..來設定默認存在的 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
有了上面的基礎,相信這個例子很快能看明白,
參考:
- https://stackoverflow.com/questions/31037882/whats-the-cmake-syntax-to-set-and-use-variables/31044116#31044116
- https://stackoverflow.com/questions/3249459/for-the-cmake-include-command-what-is-the-difference-between-a-file-and-a-mod
- https://cmake.org/cmake/help/v3.11/command/set.html#set
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/157127.html
標籤:Linux
