1. 程式人生 > >跟我一起學CMake

跟我一起學CMake

    如今CMake使用的人數越來越多,包括我專案組裡,很多大牛們在寫Qt程式的時候都不用自帶的qmake,貌似會出現很多問題,他們往往都用自己寫的CMake來編譯系統,今天我也和大家一起來學學這個高大上的工具--CMake。

    首先,一個CMake要想執行,必須在同目錄下有CMake指令碼,說說是指令碼,其實並沒什麼可怕,說白了就是一串CMake作者自己寫的token語法分析檔案--CMakeLists.txt。

    這個檔案比如你的專案有很多資料夾,必須在每個原始碼資料夾下都有一個CMakeLists.txt.它會根據CMake命令中的add_subdirectory自動的遞迴分析。

    講完指令碼的大致概念,讓我們現在開始學寫我們自己的指令碼,在這裡我引用CMake官網的相關Sample我們一起學習。注:在CMake中命令是不區分大小寫的,而變數跟C一樣是區分大小寫的,這一點需要注意,我的預設習慣是命令用小寫,比較順手。

    [1] cmake_minium_required(VERSION 2.6),版本號VERSION後面的數字根據你所決定的CMake最低版本號來決定,在這裡我們用了官網sample的2.6。

    [2] project(HELLO), 這代表你整個工程的工程名,同時CMake會根據這個工程名生成兩個變數一個是${HELLO_SOURCE_DIR}一個是${HELLO_BINARY_DIR},前者是當前source原始檔所在路徑,後者是生成的二進位制檔案鎖在路徑,如果你選用的CMake官方推薦的外部編譯模式(建立build,在build生成中間檔案和目標檔案,和原始檔分離)這個時候兩者的變數值是不同的,否則相同。

    [3] add_subdirectory(Hello) add_subdirectory(Demo),在這裡官方例子給出了本目錄下有兩個sub目錄,所以你在頂層寫完CMakeLists之後同時也必須在你add_sub的目錄下寫相應的CMake指令碼,CMake程式並不會再建立額外的程序來進行遍歷,它會一層一層的逐步遞迴下去,其實這也是UNIX設計的經典哲學--分而治之。

    [4] 在Hello目錄中: add_library(Hello hello.cxx),表明在這個sub中是生成一個Hello的庫,第一個引數是庫名,之後的原始檔名(可以有多個)。

    [5] 在Demo目錄中:include_directories(${HELLO_SOURCE_DIR}/Hello)表明當前工程需要去include標頭檔案的路徑為當前原始碼所在路徑下的Hello目錄下,相當於GCC中的-I.另外要注意你如果依次往下寫,比如include_directories之後又來一句include_directories,這個是個追加過程而不是覆蓋過程,同學看到可以不用擔心自己之前的目錄是否被覆蓋了。

    [6] link_directories(${HELLO_SOURCE_DIR}/Hello)表明當前工程所需要連結的庫的路徑所在地址,相當於GCC中的-L.

    [7] add_executable(helloDemo demo.cxx demo_b.cxx),表明在當前Demo目錄下會生成一個可執行檔案,可執行檔案的字尾名你不需要擔心,CMake會自動判斷當前環境合理加上。

    [8] target_link_libraries(helloDemo Hello)表明可執行檔案的生成必須需要庫Hello的支援(包括前者include Hello的標頭檔案)。

    這個簡單的sample講完,接下來我們來看一下CMake的語法方面的注意點。

    首先講一下新增給GCC預處理巨集的幾種用法,有的同學一開始肯定想當然的給cmake .. -DMY_FLAG這樣來用,其實這樣是不對的,這樣其實只是給CMake賦予的MY_FLAG的變數,而最終GCC是根本沒有感知到該巨集有沒有定義的,所以一般用法是兩種,一種是在cmake ..的時候加一把CMAKE_CXX_COMPILE_FLAGS=xxx,另外一種是在CMake指令碼內部手動加上一句add_definitions(-DMY_FLAG)這兩種方法都可以傳給GCC相關的預處理巨集,但是還是有區別的,前者針對的有效目錄是所有目錄,而後者針對的目錄是當前目錄乃至當前目錄以下的所有子目錄,所以在這裡需要特別注意。

    command (args ...),arg之間可以用空格分離,如果arg中有空格需要用雙引號括起來。

    其次,CMake中對於字串支援list格式,即一個變數可以支援字串陣列,比如set(VAR a b c)和set(VAR a;b;c)兩種形式都可以支援${VAR}為a b c,這種格式時候foreach()進行迴圈遍歷。但是這個時候要注意command(${VAR})表明同時執行了三次command(command a; command b; command c;)而command("${VAR}")注意其中的雙引號表明只進行了一次遍歷(command("a b c"))這個是需要我們注意的。

    CMake也提供了流程控制,比如對於C中的if,CMake的寫法也是如此if(var) command endif(var)(其中endif中的var可以寫成空),判斷語句可以有多重形式,可以驗證變數是否為true,也可以是否FOUND,(empty, 0, N, NO, OFF, FALSE, NOTFOUND, or -NOTFOUND)等。

   CMake同樣提供了迭代結構,類似於Qt中的foreach,比如你設定了一個列表字串var,這個時候你就可以簡單的foreach(temp ${var}) command(${temp}) endforeach(${temp})來對var中的變數進行迴圈迭代處理,這個時候要注意有沒有雙引號的問題。

   CMake也支援自定義編寫巨集和函式,函式可以像C語言一樣內部定義區域性變數,巨集:macro(var VALUE) command({var}) endmacro(var)和function(var VALUE) command(${var}) enfunciton(hello)等。

    CMake支援include其他.cmake控制,類似於Qt中的pri可以寫一些全域性的通用指令碼。同時CMake的add_definitions也要重點理解一下,在CMake中,預設的變數命名是name:type=value,所以在cmake命令進行編譯的時候可以動態的增加一些你自己的CMake變數,比如cmake .. -DMY_VALUE=ON,這個時候你的${MY_VALUE}就被定義了,在CMake腳本里面你可以用if({MY_VALUE})來進行判斷當前時候這個變數被指定,如果被指定了,你可以通過腳本里的add_definitons給gcc新增命令比如add_definitons(-DMY_VALUE),這個時候的MY_VALUE才真正到了你的編譯環境中,前者的MY_VALUE其實是給CMake指令碼用的,這裡需要注意。

    另外CMake中的set_target_properties也很強大,可以用他來做很多事情,我目前用到的就是給Qt自動moc,set_target_properties(${target} PROPERTIES AUTOMOC ON)這個時候你的target就會被自動moc不需要自己手動去添加了。

    到這裡,我們差不多簡單的學習了一下CMake的相關語法,不得不說CMake是一個管理專案很棒的高階工具,要學會利用好這個工具,我們還需要投入更多的精力來學習,所以一起努力來迎接CMake的美好吧,enjoy! 同時也希望志同道合的朋友可以互相交流,給我留言或者私信,謝謝!