本文主要參考正點原子的應用開發手冊,僅作為本人學習筆記使用。
目錄
cmake 的使用方法其實還是非常簡單的,重點在于編寫 CMakeLists.txt,CMakeLists.txt 的語法規則也簡單,并沒有 Makefile的語法規則那么復雜難以理解!本文我們來學習 CMakeLists.txt 的語法規則。
注釋
在 CMakeLists.txt 文件中,使用“#”號進行單行注釋,譬如:
# #這是注釋信息 #cmake_minimum_required(VERSION 3.5) project(HELLO)
大多數腳本語言都是使用“#”號進行注釋。
命令(command)
通常在 CMakeLists.txt 文件中,使用最多的是命令,譬如上例中的 cmake_minimum_required、project 都是命令;命令的使用方式有點類似于 C 語言中的函數,因為命令后面需要提供一對括號,并且通常需要我們提供參數,多個參數使用空格分隔而不是逗號“,”,這是與函數不同的地方。命令的語法格式如下所示:
command(參數 1 參數 2 參數 3 ...)
不同的命令所需的參數不同,需要注意的是,參數可以分為必要參數和可選參數(通常稱為選項),很多命令都提供了這兩類參數,必要參數使用<參數>表示,而可選參數使用[參數]表示,譬如 set 命令:
set(<variable> <value>... [PARENT_SCOPE])
set 命令用于設置變量,第一個參數<variable>和第二個參數<value>是必要參數,在參數列表(…表示參數個數沒有限制)的最后可以添加一個可選參數 PARENT_SCOPE(PARENT_SCOPE 選項),既然是可選的,那就不是必須的,根據實際使用情況確定是否需要添加。
在 CMakeLists.txt 中,命令名不區分大小寫,可以使用大寫字母或小寫字母書寫命令名,譬如:
project(HELLO) #小寫 PROJECT(HELLO) #大寫
這倆的效果是相同的,指定的是同一個命令,并沒區別;這個主要看個人喜好,個人喜歡用小寫字母,主要是為了和變量區分開來,因為 cmake 的內置變量其名稱都是使用大寫字母組成的。
部分常用命令
cmake 提 供 了 很 多 命 令 , 每 一 個 命 令 都 有 它 自 己 的 功 能 、 作 用 , 通 過 這 個 鏈 接 地 址 cmake-commands(7) — CMake 3.5.2 Documentation 可以查詢到所有的命令及其相應的介紹、使用方法等等,如下所示:
大家可以把這個鏈接地址保存起來,可以把它當成字典的形式在有需要的時候進行查詢,由于命令非常多,筆者不可能將所有命令都給大家介紹一遍,這里給大家介紹一些基本的命令,如下表所示:
接下來詳細地給大家介紹每一個命令。
? add_executable
add_executable 命令用于添加一個可執行程序目標,并設置目標所需的源文件,該命令定義如下所示:
add_executable(<name> [WIN32] [MACOSX_BUNDLE] [EXCLUDE_FROM_ALL] source1 [source2 ...])
該命令提供了一些可選參數,這些可選參數的含義筆者就不多說了,通常不需要加入,具體的含義大家可以自己查看 cmake 官方文檔(add_executable — CMake 3.5.2 Documentation);只需傳入目標名和對應的源文件即可,譬如:
#生成可執行文件 hello
add_executable(hello 1.c 2.c 3.c)
定義了一個可執行程序目標 hello,生成該目標文件所需的源文件為 1.c、2.c 和 3.c。需要注意的是,源文件路徑既可以使用相對路徑、也可以使用絕對路徑,相對路徑被解釋為相對于當前源碼路徑(注意,這里源碼指的是 CMakeLists.txt 文件,因為 CMakeLists.txt 被稱為 cmake 的源碼,若無特別說明,后續將沿用這個概念!)。
? add_library
add_library 命令用于添加一個庫文件目標,并設置目標所需的源文件,該命令定義如下所示:
add_library(<name> [STATIC | SHARED | MODULE] [EXCLUDE_FROM_ALL] source1 [source2 ...])
第一個參數 name 指定目標的名字,參數 source1…source2 對應源文件列表;add_library 命令默認生成的庫文件是靜態庫文件,通過 SHARED 選項可使其生成動態庫文件,具體的使用方法如下:
#生成靜態庫文件 libmylib.a add_library(mylib STATIC 1.c 2.c 3.c)#生成動態庫文件 libmylib.so add_library(mylib SHARED 1.c 2.c 3.c)
與 add_executable 命令相同,add_library 命令中源文件既可以使用相對路徑指定、也可以使用絕對路徑指定,相對路徑被解釋為相對于當前源碼路徑。
不管是 add_executable、還是 add_library,它們所定義的目標名在整個工程中必須是唯一的,不可出現兩個目標名相同的目標。
? add_subdirectory
……
更多直接百度或者查看正點原子的手冊吧,不一一摘錄了。
變量(variable)
在 CMakeLists.txt 文件中可以使用變量,使用 set 命令可以對變量進行設置,譬如:
設置變量 MY_VAL
set(MY_VAL "Hello World!")
上例中,通過 set 命令對變量 MY_VAL 進行設置,將其內容設置為"Hello World!";那如何引用這個變量呢?這與 Makefile 是相同的,通過${MY_VAL}方式來引用變量,如下所示:
#設置變量 MY_VAL
set(MY_VAL "Hello World!")
#引用變量 MY_VAL
message(${MY_VAL})
變量可以分為 cmake 內置變量以及自定義變量,譬如上例中所定義的 MY_VAL 就是一個自定義變量;譬如在 32.3.5 小節中所使用的 LIBRARY_OUTPUT_PATH 和 EXECUTABLE_OUTPUT_PATH 變量則是cmake 的內置變量,每一個內置變量都有自己的含義,像這樣的內置變量還有很多,稍后向大家介紹。
部分常用變量
變量也是 cmake 中的一個重頭戲,cmake 提供了很多內置變量,每一個變量都有它自己的含義,通過這個鏈接地址 cmake-variables(7) — CMake 3.5.2 Documentation 可以查詢到所有的內置變量及其相應的介紹,如下所示:
在這一份文檔中,對變量進行分類,分為:提供信息的變量、改變行為的變量、描述系統的變量、控制編譯的變量等等,筆者也按照這個分類給大家介紹一些基本、常用的變量。
? 提供信息的變量
顧名思義,這種變量可以提供某種信息,既然如此,那么我們通常只需要讀取變量即可,而不需要對變量進行修改:
? 改變行為的變量
顧名思義,意味著這些變量可以改變某些行為,所以我們可以通過對這些變量進行設置以改變行為。
? 描述系統的變量
顧名思義,這些變量描述了系統相關的一些信息:
? 控制編譯的變量
這些變量可以控制編譯過程,具體如下所示:
更多直接參考正點原子手冊或者自行百度。
按需要另外自行學習即可。
雙引號的作用
CMake 中,雙引號的作用我們可以從兩個方面進行介紹,命令參數和引用變量。
命令參數
調用命令時,參數可以使用雙引號,譬如:
project("HELLO")
也可以不使用雙引號,譬如:
project(HELLO)
那它們有什么區別呢?在本例中是沒有區別的,命令中多個參數之間使用空格進行分隔,而 cmake 會將雙引號引起來的內容作為一個整體,當它當成一個參數,假如你的參數中有空格(空格是參數的一部分),那么就可以使用雙引號,如下所示:
message(Hello World) message("Hello World")
在這個例子中,第一個 message 命令傳入了兩個參數,而第二個 message 命令只傳入一個參數;在第一個 message 命令中,打印信息時,會將兩個獨立的字符串 Hello 和 World 都打印出來,而且 World 會緊跟在Hello 之后,如下:
HelloWorld
而第二個 message 命令只有一個參數,所以打印信息如下:
Hello World
這就是雙引號在參數中的一個作用。
引用變量
我們先來看個例子,如下所示:
CMakeLists.txt
set(MY_LIST Hello World China) message(${MY_LIST})
這個例子的打印信息如下:
HelloWorldChina
在這個例子中,MY_LIST 是一個列表,該列表包含了 3 個元素,分別是 Hello、World、China。但這個message 命令打印時卻將這三個元素全部打印出來,并且各個元素之間沒有任何分隔。此時我們可以在引用變量(${MY_LIST})時加上雙引號,如下所示:
CMakeLists.txt
set(MY_LIST Hello World China) message("${MY_LIST}")
此時 message 打印信息如下:
Hello;World;China
因為此時${MY_LIST}是一個列表,我們用"${MY_LIST}"這種形式的時候,表示要讓 CMake 把這個數組的所有元素當成一個整體,而不是分散的個體。于是,為了保持數組的含義,又提供一個整體的表達方式,CMake 就會用分號“;”把這數組的多個元素連接起來。而如果不加雙引號時,CMake 不會數組當成一個整體看待,而是會將數組中的各個元素提取出進行打印輸出。
條件判斷
在 cmake 中可以使用條件判斷,條件判斷形式如下:
else 和 endif 括號中的<expression>可寫可不寫,如果寫了,就必須和 if 中的<expression>一致。
expression 就是一個進行判斷的表達式,表達式對照表如下:
上 表 中 只是 列 出其 中一 部 分 表達 式 ,還 有其 它 一 些表 達 式這 里并 未 列 出, 大 家可 以通 過if — CMake 3.5.2 Documentation 這個鏈接地址進行查看。
老規矩,更多自行百度或者參考正點原子手冊。
循環語句
cmake 中除了 if 條件判斷之外,還支持循環語句,包括 foreach()循環、while()循環。
一、foreach 循環
①、foreach 基本用法
foreach 循環的基本用法如下所示:
endforeach 括號中的<loop_var>可寫可不寫,如果寫了,就必須和 foreach 中的<loop_var>一致。
參數 loop_var 是一個循環變量,循環過程中會將參數列表中的變量依次賦值給他,類似于 C 語言 for 循環中經常使用的變量 i。
foreach 循環測試
foreach(loop_var A B C D)message("${loop_var}") endforeach()
打印信息為:
A B C D
使用 foreach 可以編譯一個列表中的所有元素,如下所示:
foreach 循環測試
set(my_list hello world china)foreach(loop_var ${my_list})message("${loop_var}") endforeach()
打印信息如下:
②、foreach 循環之 RANGE 關鍵字
用法如下所示:
foreach(loop_var RANGE stop) foreach(loop_var RANGE start stop [step])
對于第一種方式,循環會從 0 到指定的數字 stop,包含 stop,stop 不能為負數。
而對于第二種,循環從指定的數字 start 開始到 stop 結束,步長為 step,不過 step 參數是一個可選參數,如果不指定,默認 step=1;三個參數都不能為負數,而且 stop 不能比 start 小。
接下來我們進行測試,測試一:
foreach 循環測試
foreach(loop_var RANGE 4)message("${loop_var}") endforeach()
打印信息如下:
測試二:
foreach 循環測試
foreach(loop_var RANGE 1 4 1)message("${loop_var}") endforeach()
打印信息如下:
③、foreach 循環之 IN 關鍵字
用法如下:
foreach(loop_var IN [LISTS [list1 [...]]] [ITEMS [item1 [...]]])
循環列表中的每一個元素,或者直接指定元素。
接下來進行測試,測試一:
foreach 循環測試
set(my_list A B C D)foreach(loop_var IN LISTS my_list)message("${loop_var}") endforeach()
打印信息如下:
測試二:
foreach 循環測試
foreach(loop_var IN ITEMS A B C D)message("${loop_var}") endforeach()
打印信息同上。
二、while 循環
while 循環用法如下:
endwhile 括號中的 condition 可寫可不寫,如果寫了,就必須和 while 中的 condition 一致。
cmake 中 while 循環的含義與 C 語言中 while 循環的含義相同,但條件 condition 為真時,執行循環體中的命令,而條件 condition 的語法形式與 if 條件判斷中的語法形式相同。
while 循環測試
set(loop_var 4)while(loop_var GREATER 0)message("${loop_var}")math(EXPR loop_var "${loop_var} 1") endwhile()
輸出結果如下:
上例中,while 循環的條件是(loop_var GREATER 0),等價于(loop_var > 0),當 loop_var 變量的有效數值大于 0 時,執行 while 循環體;在 while 循環體中使用到了 cmake 中的數學運算命令 math(),關于數學運算下小節會向大家介紹。
在 while 循環體中,打印 loop_var,之后將 loop_var 減一。
三、break、continue
cmake 中,也可以在循環體中使用類似于 C 語言中的 break 和 continue 語句。
①、break
break()命令用于跳出循環,和在 C 語言中的作用是一樣的,測試如下:
打印信息如下:
整個代碼筆者就不再解釋了,注釋已經寫得很清楚了!
②、continue
continue()命令用于結束本次循環,執行下一次循環,測試如下:
這段 cmake 代碼是求 0 到 10 之間的偶數(左閉右開),并將偶數打印出來,使用到了 continue()命令,代碼不再解釋,注釋已經寫得很清楚了。
打印結果如下:
關于 break()和 continue()命令的使用就介紹到這里了。
數學運算 math
在 cmake 中如何使用數學運算呢?其實,cmake 提供了一個命令用于實現數學運算功能,這個命令就是 math(),如下所示:
math(EXPR <output variable> <math expression>)
math 命令中,第一個參數是一個固定的關鍵字 EXPR,第二個參數是一個返回參數,將數學運算結果存放在這個變量中;而第三個參數則是一個數學運算表達式,支持的運算符包括:+(加)、-(減)、*(乘)、/(除)、%(求余)、|(按位或)、&(按位與)、^(按位異或)、~(按位取反)、<<(左移)、>>(右移)以及這些運算符的組合運算,它們的含義與 C 語言中相同。
譬如:
math(EXPR out_var "1+1") #計算 1+1 math(EXPR out_var "100 * 2") ##計算 100x2 math(EXPR out_var "10 & 20") #計算 10 & 20
我們進行測試:
math()命令測試
math(EXPR out_var "100 + 100") message("${out_var}")math(EXPR out_var "100 - 50") message("${out_var}")math(EXPR out_var "100 * 100") message("${out_var}")math(EXPR out_var "100 / 50") message("${out_var}")math(EXPR out_var "(100 & 100) * 50 - 2") message("${out_var}")
測試結果如下:
定義函數
在 cmake 中我們也可以定義函數,cmake 提供了 function()命令用于定義一個函數,使用方法如下所示:
endfunction 括號中的<name>可寫可不寫,如果寫了,就必須和 function 括號中的<name>一致。
①、基本使用方法
第一個參數 name 表示函數的名字,arg1、arg2…表示傳遞給函數的參數。調用函數的方法其實就跟使用命令一樣,一個簡單地示例如下所示:
打印信息如下:
②、使用 return()命令
在 function()函數中也可以使用 C 語言中的 return 語句退出函數,如下所示:
執行結果如下:
只打印了 Hello,并沒有打印 World,說明 return()命令是生效的,執行 return()命令之后就已經退出當前函數了,所以并不會打印 World。但是需要注意的是,return 并不可以用于返回參數,那函數中如何返回參數給調用者呢?關于這個問題,后續再給大家講解,因為這里涉及到其它一些問題,本小節暫時先不去理會這個問題。
③、可變參函數
在 cmake 中,調用函數時實際傳入的參數個數不需要等于函數定義的參數個數(甚至函數定義時,參數個數為 0),但是實際傳入的參數個數必須大于或等于函數定義的參數個數,如下所示:
函數 xyz 定義時只有一個參數,但是實際調用時我們傳入了 3 個參數,注意這并不會報錯,是符合function()語法規則的,會正常執行,打印信息如下:
從打印信息可知,message()命令打印出了調用者傳入的第一個參數,也就是 Hello。
這種設計有什么用途呢?正如我們的標題所言,這種設計可用于實現可變參函數(與 C 語言中的可變參數函數概念相同);但是有個問題,就如上例中所示,用戶傳入了 3 個參數,但是函數定義時并沒有定義這些形參,函數中如何引用到第二個參數 World 以及第三個參數 China 呢?其實 cmake 早就為大家考慮到了,并給出了相應的解決方案,就是接下來向大家介紹的內部變量。
④、函數的內部變量
function()函數中可以使用內部變量,所謂函數的內部變量,指的就是在函數內部使用的內置變量,這些內部變量如下所示:
我們可以進行測試:
源碼執行結果如下:
這個大家自己去對照一下就知道了。
更多自行百度吧。
宏定義
cmake 提供了定義宏的方法,cmake 中函數 function 和宏定義 macro 在某種程度上來說是一樣的,都是創建一段有名字的代碼可以在后面被調用,還可以傳參數。通過 macro()命令定義宏,如下所示:
endmacro 括號中的<name>可寫可不寫,如果寫了,就必須和 macro 括號中的<name>一致。參數 name表示宏定義的名字,在宏定義中也可以使用前面給大家介紹的 ARGVX(X 是一個數字)、ARGC、ARGV、ARGN 這些變量,所以這些也是宏定義的內部變量。
更多待補充。
文件操作
cmake 提供了 file()命令可對文件進行一系列操作,譬如讀寫文件、刪除文件、文件重命名、拷貝文件、 創建目錄等等,本小節我們一起來學習這個功能強大的 file()命令。
①、寫文件:寫、追加內容
使用 file()命令寫文件,使用方式如下所示:
file(WRITE <filename> <content>...) file(APPEND <filename> <content>...)
將<content>寫入名為<filename>的文件中。如果文件不存在,它將被創建;如果文件已經存在,WRITE模式將覆蓋它,APPEND 模式將內容追加到文件末尾。
測試代碼如下:
注意文件可以使用絕對路徑或相對路徑指定,相對路徑被解釋為相對于當前源碼路徑。
執行 CMakeLists.txt 代碼之后,會在當前源碼目錄下生成一個名為 wtest.txt 的文件。
更多待補充。
關于 cmake,筆者就給大家介紹這么多,已經基本夠用了;其實還有很多的命令以及變量沒有介紹到,但完全可以自己去看,然后去實戰測試!