CMake 基本語法(Mastering CMake 筆記)
1 CMake語法
CMakeLists檔案是由註釋、命令以及空白符三種語法組成。註釋是由符號#開始直到一行結束。命令是由命令名稱、括號以及由空白符分隔的引數組成。除了用於分隔空命令引數的空白符,其他的空白符都會被忽略。雙引號中的內容只會被看作是一個引數。反斜槓可以用來對字元進行轉義。
2 基礎命令
CMakeLists檔案的第一個命令是 project 命令,用來指定專案的名稱以及指定專案的語言。它的語法如下:
project (projectname [CXX] [C] [Java] [NONE])
如果沒有指定專案語言則CMake預設支援C和C++。如果使用了NONE引數,則CMake將不會包含任何的語言支援。如果指定了專案語言為C++,則C語言支援也會被載入。
每出現一個 project 命令,CMake都會建立一個IDE的專案檔案,這個專案檔案將會包含CMakeLists檔案中的所有目標檔案以及使用 add_subdirectory 命令包含的子目錄。如果在add_subdirectory 命令中使用了 EXCLUDE_FROM_ALL選項,則子目錄不會出現在頂層的Makefile檔案或IDE的工程檔案中。
set 命令是使用最多的命令,用來定義或修改變數和列表的值與 set 命令相對的是 remove 和 separate_arguments 命令。remove 命令用來從變數列表中移除一個值,separate_arguments 命令用來根據空白符將一個變數分割為一個變數列表。
add_executable 和 add_library 用來定義要生成的庫和可執行檔案,以及庫和可執行檔案包含的原始檔。
3 流程控制
CMake提供了三種流程控制結構:
- 條件語句(例如:if)
- 迴圈結構(例如:foreach 和 while)
- 過程定義(例如:macro 和 function)
條件語句
條件語句在許多方面和其他語言的的if語句一樣,具體如下:
if (FOO)
#do something here
else (FOO)
#do something here
endif (FOO)
else 和 endif 分句中的條件用來提供額外的錯誤檢查,它們必須和 if 的條件完全相同。else 和 endif 分句中的條件也可以省略,具體如下:
if (FOO)
#do something here
else ()
#do something here
endif ()
CMake 也支援用 elseif 來進行多個條件測試。例如
if (MSVC80)
# do something here
elseif (MSVC90)
# do something here
elseif (APPLE)
# do something here
endif()
CMake 中的 if 命令只有有限的幾個操作可以使用,具體如下:
if (variable)
# 當 variable 不為 空值,0,FALSE,OFF 或者 NOTFOUND 時為真
if (NOT variable)
# 當 variable 為 空值,0,FALSE,OFF 或者 NOTFOUND 時為真
if (variable1 AND variable2)
# 當 variable1, variable1 同時不為 空值,0,FALSE,OFF 或者 NOTFOUND 時為真
if (variable1 OR variable2)
# 當 variable1, variable1 有一個不為 空值,0,FALSE,OFF 或者 NOTFOUND 時為真
if (COMMAND command-name)
# 當 command-name 是可呼叫的命令時為真
if (DEFINED variable)
# 當 variable 已經被設定了值時為真
if (EXISTS file-name)
if (EXISTS directory-name)
# 當指定的檔案或者目錄時為真
if (IS_DIRECTORY name)
if (IS_ABSOLUTE name)
# 當 name 是目錄或者是絕對路徑是為真
if (name1 IS_NEWER_THAN name2)
# 當 name1 檔案的修改時間比 name2 檔案的修改時間要新時為真
if (variable MATCHES regex)
if (string MATCHES regex)
# 當給定的變數或者字串與給定的正則表示式相匹配時為真
選項 EQUAL, LESS 和 GREATER 可用來進行數值的比較, STRLESS, STREQUAL 和 STRGREATER 可用於進行字典序的比較, VERSION_LESS, VERSION_EQUAL 和 VERSION_GREATER 能用於 major[.minor[.patch[.tweak]]] 形式的版本比較,這些選項也可以進行組合得到更強的判斷條件。
CMake 定義了條件語句的執行順序:括號裡的語句會首先被執行,然後是執行 EXISTS, COMMAND, DEFINED 以及型別的前置運算子,接著是執行 EQUAL. LESS, GREATER, STREQUAL, STRLESS, STRGREATER 和 MATCHES 運算子, NOT 運算子會接著被評估, 最後是執行 AND 和 OR 運算。同一級別的運算子則是按照從左到右的順序執行,只有所有的表示式被執行了才會評估最終結果。CMake 會將 ON, 1, YES, TRUE, Y 看作是真值,將 OFF, 0, NO, FALSE, N, NOTFOUND, *-NOTFOUND, IGNORER 看作是假值。
迴圈結構
foreach 用來重複執行列表中的一組命令,foreach 中的第一個引數是變數的名字用來在每次迴圈中取不同的值。foreach 迴圈可以進行巢狀,並且迴圈變數比其他變數有更高的展開優先順序,因此在 foreach 的迴圈體裡可以使用迴圈變數構建變數名。
foreach (foo list)
# do something here
endforeach (foo)
while 命令根據一個條件變數來提供迴圈功能。while 命令中的條件變數和之前的 if 命令是一致的。
while (foo)
# do something here
endwhile ()
過程定義
macro 和 function 命令能夠重複執行 CMakeLists 檔案中比較分散的功能。在一個 macro 或者 function 定義後,就可以被任何在它定義之後處理的 CMakeLists 檔案中使用。在macro 和 function 中有一些預定義的標準引數,ARGV0, ARGV1 分別表示 macro 和 function 傳入的第一個引數和第二個引數。ARGV 是 macro 和 function 所有引數的一個列表,ARGN 是表示傳入 macro 和 function 的在正式定義引數之後傳入的不定形式的引數的列表。
CMake 中的 function 與 C 或者是 C++ 中的函式很像,可以傳遞引數到 function 中當作變數使用。function 中,一些例如 ARGC, ARGV, ARGN, ARGV0, ARGV1 的標準變數已經被定義。在 function 中是一個新的變數作用域, 與使用 add_subdirectory 命令進入子目錄產生的新變數作用域一樣,所有在呼叫 function 時已定義的變數在 function 中仍然被定義,但是對變數的修改以及定義新的變數只在這個 function 中起作用, 函式返回後這些變數就被移除了。定義 function 時,第一個引數是 function 的名稱,剩餘的其他引數都會傳遞到 function 中當變數使用。
function (name argv1 argv2)
# do something here
set (${argv1} "val" PARENT_SCOPE)
endfunction ()
在 function 中,使用帶 PARENT_SCOPE 選項的 set 命令可以修改父作用域的中變數的值。
macro 的定義和呼叫方式與 function 一樣,但與 function 最主要的不同是 macro 不會產生新的變數作用域。 macro 中的引數不是當作變數,而是當作在執行前進行替換的字串。這與 C 和 C++ 中, 函式與巨集定義的區別是一樣的。macro 的第一個引數是建立的 macro 的名稱,剩餘的其他引數是 macro 的中的引數。
macro (name argv1 argv2)
# do something here
endmacro (name)
macro 也支援使用引數列表來進行巨集替換,引數可以使用 ARGC, ARGV0, ARGV1 等來進行引用, 其含義與 function 中的一樣。
4 正則表示式
正則表示式是用來進行字元匹配的字串,可以由標準的數字字母以及正則表示式元字元組成。正則表示式的元字元有:
^ 匹配一行或者字串的開頭
$ 匹配一行或者字串的結尾
. 匹配除新行以為的任何字元
[] 匹配方括號內的任何字元
[^] 匹配不在方括號內的任何字元
[-] 匹配橫槓兩端字元範圍內的任何字元
* 匹配正在處理的模式零次或多次
+ 匹配正在處理的模式一次或多次
? 匹配正在處理的模式零次或一次
() 儲存一個匹配的表示式並用於後面的替換操作
(|) 匹配豎線左邊或者右邊
5 檢查CMake的版本
CMake 隨著新版本的釋出會引入一些新的特性,當你需要使用當前版本 CMake 包含而之前的版本不包含的的命令時,可以有幾種方法進行處理。一種方案是使用 if 命令來檢查新的命令是不是存在:
# 測試命令是否存在
if (COMMAND new_command)
# use the new_command do something here
endif (COMMAND new_command)
如果需要更多的資訊,可以使用 CMAKE_VERSION 變數來獲取實際的 CMake 版本進行處理:
# 查詢更新版本的CMake
if (${CMAKE_VERSION} VERSION_GREATER 2.8.0)
# do something special here
endif ()
在編寫 CMakeLists 檔案時可能會不想支援一些舊版本的CMake,同在 CMakeLists 檔案的最頂點放置如下命令可以做到:
cmake_minimum_required (VERSION 2.2)
如果 CMake 版本比上述命令指定的要低時,則會產生一條錯誤的資訊來指示專案需要的最低版本的 CMake。
6 使用模組
CMake 通過使用模組的方式來支援程式碼重用。模組是一段在一個檔案中的 CMake 命令, 它們可以通過 include 命令包含進其他的 CMakeLists 檔案中。一個模組的位置可以通過絕對路徑來指定,也可以讓 CMake 自己查詢。CMake 會在 CMAKE_MODULE_PATH 指定的檔案裡查詢模組。如果沒有找到 CMake 就會在自己的模組子目錄下查詢。模組可以主要分為以下的三類:
(1) Find Modlues
這些模組決定了軟體元素的位置,如標頭檔案和庫。CMake 包含很多的 Find Modlues,Find Modlues 用來定位軟體元素的的位置,如果沒有找到,它們會提供一個快取記錄使得使用者可以設定它們的需要的值。
find_path 命令用來定位標頭檔案的位置,命令的第一個引數是用來儲存結果的變數名稱,第二個引數是需要查詢的標頭檔案名稱,剩餘的引數是用來尋找標頭檔案的路徑:
find_path (VARIABLE_NAME includefile path1 path2)
當頭檔案沒有找到時,VARIABLE_NAME 變數會被設定為 VARIABLE_NAME-NOTFOUND。
find_library 命令用來尋找真正的庫檔案,這個命令執行了額外的檢查來尋找一個合適的的庫名稱,如在 linux 系統中會在名稱前新增 lib,在結尾新增 .so。find_library 命令的呼叫方式與 find_path 命令類似:
find_path (VARIABLE_NAME libraryfile path1 path2)
在 CMake 中尋找模組的基本檔案結構如下:
find_path (MODULE_INCLUDE_DIR header.h path1 path2)
find_library (MODULE_LIBRARY library path1 path2)
if(MODULE_LIBRARY)
if(MODULE_INCLUDE_DIR)
set (MODULE_FOUND "YES")
endif()
endif()
(2) System Introspection Modules
這些模組提供了目標平臺或編譯器的相關資訊,如 float 的長度,支援 ANSI C++ 流等。它們的模組名稱通常有 Test 或者 Check 的字首。為了產生正確的編譯結果,大多數的 System Introspection Modules 參與到程式碼的編譯過程中,它們的原始碼通常有和模組一樣的名稱,並以 .c 或 .cxx 字尾結尾。
(3) Utility Modlues
Utility Modlues 模組提供了額外的功能,如支援一個 CMake 工程依賴另一個的的情形。CMake 包含了一些 Utility Modlues 用來幫助 CMake 的使用。CMakeExportBuildSetting 和 CMakeImportBuildSetting 提供了工具用來區分兩個用相同的編譯器和關鍵標誌進行編譯的 C++ 專案。CMakePrintSystemInformation 模組用來列印 CMake 的關鍵設定來幫助除錯。