Home CMake教學系列二 CMake的觀念
Post
Cancel

CMake教學系列二 CMake的觀念

在第一篇我們已經學會如何編譯單一檔案的C++程式,接下來將介紹一些CMake的觀念

關於Targets

在CMake有兩種方式建立targettarget的名稱必須是唯一的

  • add_executable(myexample simple.cpp) : 建立了一個名為myexample的target,並且命名輸出的執行檔為myexample
  • add_library(mylibrary simplelib.cpp) : 建立了一個名為myexample的target,並且命名輸出的函式庫為myexample target很像程式裡面物件的概念,他擁有許多屬性,例如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
2
# Assuming you have a file called example.cmake:
cmake -P example.cmake

Local variables

example.cmake輸入以下指令,然後執行cmake -P example.cmake就可以看到終端機輸出你的變數。在這裡set指令設定變數,message指令印出變數,這裡我們使用的是STATUSmessage,還有其他的狀態你可以到官網查看

1
2
set(MY_VARIABLE "I am a variable")
message(STATUS "${MY_VARIABLE}")

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
2
set(MY_VARIABLE "I am a variable")
message(STATUS "${MY_VARIABLE}")

而因為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_propertyset_property,或者是get_target_propertiesset_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](https://cmake.org/cmake/help/latest/command/string.html、filelist。例如下面指令會幫你產生一個list,裡標包含所有附檔名為*.cxx的檔案,並且把它儲存到OUTPUT_VAR裡面。你也可以用GLOB_RECURSE讓你可以連仔資料夾都尋找。這些工具可以在這份文件找到

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

This post is licensed under CC BY 4.0 by the author.

安裝Vagrant製作測試環境

CMake教學系列三建立CMake專案