CMake教學系列二 CMake的觀念
在第一篇我們已經學會如何編譯單一檔案的C++程式,接下來將介紹一些CMake的觀念
關於Targets
在CMake有兩種方式建立target
,target
的名稱必須是唯一的
- add_executable(myexample simple.cpp) : 建立了一個名為myexample的
target
,並且命名輸出的執行檔為myexample - add_library(mylibrary simplelib.cpp) : 建立了一個名為myexample的
target
,並且命名輸出的函式庫為myexampletarget
很像程式裡面物件的概念,他擁有許多屬性,例如SOURCES在這個範例他會擁有simple.cpp這個檔案,target
所有的屬性可以在這裡查到
特別的targets
有些C++函式庫只有標頭檔(例如函式庫Eigen),這種情況下你沒辦法真正的輸出一個library檔,但是他又被製作成target
讓其他target
使用。這種函式庫我們叫做interface libraries
而再CMake我們可以用下面指令把他製作成target
。注意到跟前面不同的是他只需要設為INTERFACE
屬性而且他不需要輸入*.cpp檔。
1 | add_library(some_header_only_lib INTERFACE) |
另一種狀況是你要直接使用預先編譯好的pre-built library,這種情況你也不會有*.cpp可以給CMake編譯。這種library在CMake裡我們稱為imported library,我們可以用關鍵字IMPORTED
來告訴CMake。
1 | add_library(some_library STATIC IMPORTED) |
連接
一旦你有了target
就可以用target_link_libraries連接所需要的東西,連接的時候有PUBLIC, PRIVATE, 和 INTERFACE三種屬性可以選擇,他有點像程式語言中的存取控制。如果TargetB引用TargetA,則TargetA的PUBLIC屬性都會傳遞給TargetB。
範例1: Include directories
target_include_directories(TargetA PRIVATE mydir)
連接TargetA和mydir資料夾屬性為PRIVATE,這時候TargetA的INCLUDE_DIRECTORIES屬性就會包含mydir資料夾target_include_directories(TargetA INTERFACE mydir)
連接TargetA和mydir資料夾屬性為INTERFACE,這時候TargetA的INTERFACE_INCLUDE_DIRECTORIES屬性就會包含mydir資料夾target_include_directories(TargetA PUBLIC mydir)
連接TargetA和mydir資料夾屬性為PUBLIC,則INCLUDE_DIRECTORIES
INTERFACE_INCLUDE_DIRECTORIES
都會包含mydir資料夾
CMake變數
為了方便起見,接下來的範例會直接執行example.cmake
檔,而不是建立一個CMakeLists.txt
。首先你需要先建立一個example.cmake
,如果要執行example.cmake
可以利用CMake的 -p 選項。這樣可以節省去許多編譯設定,也比較容易實驗
1 | # Assuming you have a file called example.cmake: |
Local variables
在example.cmake
輸入以下指令,然後執行cmake -P example.cmake
就可以看到終端機輸出你的變數。在這裡set指令設定變數,message指令印出變數,這裡我們使用的是STATUS
message,還有其他的狀態你可以到官網查看
1 | set(MY_VARIABLE "I am a variable") |
{: file=’example.cmake’}
Cached variables
cached variables在CMake十分重要,通常我們會用CMake圖形介面或是CMake命令介面設定許多cached variables,這些變數都會被寫到CMakeCache.txt
檔案裡面。當你執行CMake的時候CMake會先讀取這些Cached variables。下面這個範例會設定一個Cached variables,不過因為我們用-P
執行*.cmake,所以不會真的輸出一個CMakeCache.txt
,你可以參考上一篇的範例觀察CMake如何產生CMakeCache.txt
。
1 | set(MY_VARIABLE "I am a variable") |
{: file=’example.cmake’}
而因為Cached variables幾乎都是可以讓使用者設定的選項,所以有一個更方便的指令option
1 | option(MY_OPTION "On or off" OFF) |
Other variables
- 你可以藉由
$ENV{my_env}
來取得環境變數my_env
的值,你也可以利用if(DEFINED ENV{my_env})
來檢查my_env
這個環境變數是不是有被設定(注意這個指令沒有$
) target
的屬性其實也是一種變數,你可以利用get_property和set_property,或者是get_target_properties和set_target_properties來查看和設定target
屬性,除此之外還有CMake本身的屬性,可以從cmake-properties這份清單查詢。
Target properties and variables
你已經知道target
的屬性可以控制target
的行為,如果你仔細觀察會發現,有許多target
屬性(例如CXX_EXTENSIONS)會有一個對應的CMake變數,並且以CMAKE_
為開頭(例如CMAKE_CXX_EXTENSIONS),這些CMake變數是用來初始化對應的target
屬性用的。因此你可以先設定這些CMake變數,這樣就可以快速設定target
對應屬性的初始值。
搜尋工具
CMake有一些方便的glob指令可以用來處理string。
1 | file(GLOB OUTPUT_VAR *.cxx) |
在這裡要提到一個很重要的flagCONFIGURE_DEPENDS
(CMake 3.12+),如果沒有這個flag則在重新執行建置的時候,cmake不會再次用glob去搜尋,如此一來如果這些被搜尋的資料夾放入的新的檔案也不會被CMake發現。因此如果你想要在每一次建置專案的時候都重新搜尋,記得加上這個flag
1 | file(GLOB OUTPUT_VAR *.cxx CONFIGURE_DEPENDS) |
參考:
CMake Fundamentals Part 4
https://jeremimucha.com/2021/02/cmake-fundamentals-part4/
Modern CMake is like inheritance
https://kubasejdak.com/modern-cmake-is-like-inheritance
CMake doc : Importing Libraries
https://cmake.org/cmake/help/latest/guide/importing-exporting/index.html#importing-libraries
https://cliutils.gitlab.io/modern-cmake/chapters/basics.html
https://hsf-training.github.io/hsf-training-cmake-webpage/04-targets/index.html
https://hsf-training.github.io/hsf-training-cmake-webpage/05-variables/index.html
CMake教學系列二 CMake的觀念