轉自http://blog.csdn.net/ljt20061908/article/details/11736713
0. 前言
一個多月前,由於工程項目的需要,匆匆的學習了一下cmake的使用方法,現在有時間拿出來整理一下。本文假設你已經學會了cmake的使用方法,如果你還不會使用cmake,請參考相關資料之后再繼續向下看。
本文中介紹的是生成可執行程序的方法和步驟,生成動態庫和靜態庫的方法與此有所不同,隨后會介紹動態庫和靜態庫項目中cmake的編寫方法。
本文參考《CMake Practice》這篇文章完成,旨在指導用戶快速使用CMake,如果需要更詳細的內容,請通讀《CMake Practice》這篇文章。下載路徑:http://sewm.pku.edu.cn/src/paradise/reference/CMake%20Practice.pdf
1. 項目目錄結構
我們項目的名稱為CRNode,假設我們項目的所有文件存放再~/workspace/CRNode,之后沒有特殊說明的話,我們所指的目錄都以此目錄為相對路徑。
我們的目錄結構如下:
|
其中,src存放源代碼文件和一個CMakeLists.txt文件,CMakeLists文件的編寫我們稍候介紹;doc目錄中存放項目的幫助文檔,該文檔以及COPYRIGHT和README一起安裝到/usr/share/doc/crnode目錄中;COPYRIGHT文件存放項目的版權信息,README存放一些說明性文字;crnode.sh存放CRNode的啟動命令;CMakeLists.txt文件稍候介紹。
除此之外,項目還依賴兩個外部庫:Facebook開發的thrift庫,其頭文件存放在/usr/include/thrift目錄中;log4cpp庫,其頭文件存放再/usr/include下。
2. CMakeLists.txt文件
本工程中使用了兩個CMakeLists.txt文件,分別項目的根目錄(即~/workspace/CRNode目錄,下同)和src目錄中(參考以上目錄結構)。我們先給出兩個CMakeLists.txt的內容,在下一節中再對兩個CMakeLists.txt進行詳細介紹。兩個CMakeLists.txt文件的內容分別如下:
2.1 根目錄中CMakeLists內容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
cmake_minimum_required (VERSION 2.6)
project (CRNode)
ADD_SUBDIRECTORY(src bin)
#SET(CMAKE_INSTALL_PREFIX ${PROJECT_BINARY_DIR})
SET(CMAKE_INSTALL_PREFIX /usr/local)
INSTALL(PROGRAMS crnode.sh DESTINATION bin)
INSTALL(FILES COPYRIGHT README DESTINATION share/doc/crnode)
INSTALL(DIRECTORY doc/ DESTINATION share/doc/crnode) |
2.2 src/CMakeLists.txt內容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
INCLUDE_DIRECTORIES(/usr/include/thrift)
SET(SRC_LIST main.cc
rpc/CRMasterCaller.cpp
rpc/CRNode_server.skeleton.cpp
rpc/Schd_constants.cpp
rpc/CRMaster.cpp
rpc/CRNode.cpp
rpc/Schd_types.cpp
task/TaskExecutor.cpp
task/TaskMoniter.cpp
util/Const.cpp
util/Globals.cc
util/utils.cc
util/Properties.cpp
)
ADD_EXECUTABLE(crnode ${SRC_LIST})
TARGET_LINK_LIBRARIES(crnode log4cpp thrift)
INSTALL(TARGETS crnode
RUNTIME DESTINATION bin
) |
3. CMake語法
A. 變量使用${}方式取值,但是在 IF 控制語句中是直接使用變量名;
B. 指令(參數 1 參數 2…),參數使用括弧括起,參數之間使用空格或分號分開;
C. 指令是大小寫無關的,參數和變量是大小寫相關的。但,推薦你全部使用大寫指令。
4. CMakeLists.txt剖析
4.1 cmake_minimum_required命令
1 |
cmake_minimum_required (VERSION 2.6) |
規定cmake程序的最低版本。這行命令是可選的,我們可以不寫這句話,但在有些情況下,如果CMakeLists.txt文件中使用了一些高版本cmake特有的一些命令的時候,就需要加上這樣一行,提醒用戶升級到該版本之后再執行cmake。
4.2 project命令
3 |
project (CRNode) |
指定項目的名稱。項目最終編譯生成的可執行文件並不一定是這個項目名稱,而是由另一條命令確定的,稍候我們再介紹。
但是這個項目名稱還是必要的,在cmake中有兩個預定義變量:< projectname >_BINARY_DIR以及< projectname >_SOURCE_DIR,在我們的項目中,兩個變量分別為:CRNode_BINARY_DIR和CRNode_SOURCE_DIR。內部編譯情況下兩者相同,后面我們會講到外部編譯,兩者所指代的內容會有所不同。要理解這兩個變量的定義,我們首先需要了解什么是“外部構建(out-of-source build)”,我們將在下一小節中介紹“外部構建”。
同時cmake還預定義了PROJECT_BINARY_DIR和PROJECT_SOURCE_DIR變量。在我們的項目中,PROJECT_BINARY_DIR等同於CRNode_BINARY_DIR,PROJECT_SOURCE_DIR等同於CRNode_SOURCE_DIR。在實際的應用用,我強烈推薦使用PROJECT_BINARY_DIR和PROJECT_SOURCE_DIR變量,這樣即使項目名稱發生變化也不會影響CMakeLists.txt文件。
4.3 外部構建
假設我們此時已經完成了兩個CMakeLists.txt文件的編寫,可以執行cmake命令生成Makefile文件了。此時我們由兩種方法可以執行cmake、編譯和安裝:
1 2 |
cmake . make |
或者
1 2 3 4 |
mkdir build cd build cmake .. make |
兩種方法最大的不同在於執行cmake和make的工作路徑不同。第一種方法中,cmake生成的所有中間文件和可執行文件都會存放在項目目錄中;而第二種方法中,中間文件和可執行文件都將存放再build目錄中。第二種方法的優點顯而易見,它最大限度的保持了代碼目錄的整潔。同時由於第二種方法的生成、編譯和安裝是發生在不同於項目目錄的其他目錄中,所以第二種方法就叫做“外部構建”。
回到之前的疑問,再外部構建的情況下,PROJECT_SOURCE_DIR指向的目錄同內部構建相同,仍然為~/workspace/CRNode,而PROJECT_BINARY_DIR則有所不同,指向~/workspace/CRNode/build目錄。
當然,cmake強烈推薦使用外部構建的方法。
4.4 ADD_SUBDIRECTORY命令
5 |
ADD_SUBDIRECTORY(src bin) |
ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])這個指令用於向當前工程添加存放源文件的子目錄,並可以指定中間二進制和目標二進制存放的位置。EXCLUDE_FROM_ALL 參數的含義是將這個目錄從編譯過程中排除。比如,工程的 example,可能就需要工程構建完成后,再進入 example 目錄單獨進行構建。
在我們的項目中,我們添加了src目錄到項目中,而把對應於src目錄生成的中間文件和目標文件存放到bin目錄下,在上一節舉例中“外部構建”的情況下,中間文件和目標文件將存放在build/srcobj目錄下。
4.5 SET命令
8 |
SET(CMAKE_INSTALL_PREFIX /usr/local) |
現階段,只需要了解SET命令可以用來顯式的定義變量即可。在以上的例子中,我們顯式的將CMAKE_INSTALL_PREFIX的值定義為/usr/local,如此在外部構建情況下執行make install命令時,make會將生成的可執行文件拷貝到/usr/local/bin目錄下。
當然,可執行文件的安裝路徑CMAKE_INSTALL_PREFIX也可以在執行cmake命令的時候指定,cmake參數如下:
cmake -DCMAKE_INSTALL_PREFIX=/usr .. |
如果cmake參數和CMakeLists.txt文件中都不指定該值的話,則該值為默認的/usr/local。
4.6 INCLUDE_DIRECTORIES命令
1 |
INCLUDE_DIRECTORIES(/usr/include/thrift) |
INCLUDE_DIRECTORIES類似gcc中的編譯參數“-I”,指定編譯過程中編譯器搜索頭文件的路徑。當項目需要的頭文件不在系統默認的搜索路徑時,需要指定該路徑。在我們的項目中,log4cpp所需的頭文件都存放在/usr/include下,不需要指定;但thrift的頭文件沒有存放在系統路徑下,需要指定搜索其路徑。
4.7 ADD_EXECUTABLE和ADD_LIBRARY
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
SET(SRC_LIST main.cc
rpc/CRMasterCaller.cpp
rpc/CRNode_server.skeleton.cpp
rpc/Schd_constants.cpp
rpc/CRMaster.cpp
rpc/CRNode.cpp
rpc/Schd_types.cpp
task/TaskExecutor.cpp
task/TaskMoniter.cpp
util/Const.cpp
util/Globals.cc
util/utils.cc
util/Properties.cpp
)
ADD_EXECUTABLE(CRNode ${SRC_LIST}) |
ADD_EXECUTABLE定義了這個工程會生成一個文件名為 CRNode 的可執行文件,相關的源文件是 SRC_LIST 中定義的源文件列表。需要注意的是,這里的CRNode和之前的項目名稱沒有任何關系,可以任意定義。
4.8 EXECUTABLE_OUTPUT_PATH和LIBRARY_OUTPUT_PATH
我們可以通過 SET 指令重新定義 EXECUTABLE_OUTPUT_PATH 和 LIBRARY_OUTPUT_PATH 變量來指定最終的目標二進制的位置(指最終生成的CRNode可執行文件或者最終的共享庫,而不包含編譯生成的中間文件)。
命令如下:
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib) |
需要注意的是,在哪里 ADD_EXECUTABLE 或 ADD_LIBRARY,如果需要改變目標存放路徑,就在哪里加入上述的定義。
4.9 TARGET_LINK_LIBRARIES命令
20 |
TARGET_LINK_LIBRARIES(CRNode log4cpp thrift) |
這句話指定在鏈接目標文件的時候需要鏈接的外部庫,其效果類似gcc的編譯參數“-l”,可以解決外部庫的依賴問題。
4.10 INSTALL命令
在執行INSTALL命令的時候需要注意CMAKE_INSTALL_PREFIX參數的值。該參數在3.5中已經有所介紹。其命令形式如下:
INSTALL(TARGETS targets... [[ARCHIVE|LIBRARY|RUNTIME] [DESTINATION < dir >] [PERMISSIONS permissions...] [CONFIGURATIONS [Debug|Release|...]] [COMPONENT < component >] [OPTIONAL] ] [...]) |
參數中的 TARGETS 后面跟的就是我們通過 ADD_EXECUTABLE 或者 ADD_LIBRARY 定義的目標文件,可能是可執行二進制、動態庫、靜態庫。
DESTINATION 定義了安裝的路徑,如果路徑以/開頭,那么指的是絕對路徑,這時候CMAKE_INSTALL_PREFIX 其實就無效了。如果你希望使用 CMAKE_INSTALL_PREFIX 來定義安裝路徑,就要寫成相對路徑,即不要以/開頭,那么安裝后的路徑就是${CMAKE_INSTALL_PREFIX} /< destination 定義的路徑>
你不需要關心 TARGETS 具體生成的路徑,只需要寫上 TARGETS 名稱就可以了。
非目標文件的可執行程序安裝(比如腳本之類):
INSTALL(PROGRAMS files... DESTINATION < dir > [PERMISSIONS permissions...] [CONFIGURATIONS [Debug|Release|...]] [COMPONENT < component >] [RENAME < name >] [OPTIONAL]) |
跟上面的 FILES 指令使用方法一樣,唯一的不同是安裝后權限為OWNER_EXECUTE, GROUP_EXECUTE, 和 WORLD_EXECUTE,即 755 權限目錄的安裝。
安裝一個目錄的命令如下:
INSTALL(DIRECTORY dirs... DESTINATION < dir > [FILE_PERMISSIONS permissions...] [DIRECTORY_PERMISSIONS permissions...] [USE_SOURCE_PERMISSIONS] [CONFIGURATIONS [Debug|Release|...]] [COMPONENT < component >] [[PATTERN < pattern > | REGEX < regex >] [EXCLUDE] [PERMISSIONS permissions...]] [...]) |
DIRECTORY 后面連接的是所在 Source 目錄的相對路徑,但務必注意:abc 和 abc/有很大的區別。如果目錄名不以/結尾,那么這個目錄將被安裝為目標路徑下的 abc,如果目錄名以/結尾,代表將這個目錄中的內容安裝到目標路徑,但不包括這個目錄本身。我們來看一個例子:
1 2 3 4 5 |
INSTALL(DIRECTORY icons scripts/ DESTINATION share/myproj PATTERN "CVS" EXCLUDE PATTERN "scripts/*" PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ) |
這條指令的執行結果是:
將 icons 目錄安裝到 < prefix >/share/myproj,將 scripts/中的內容安裝到< prefix >/share/myproj,不包含目錄名為 CVS 的目錄,對於 scripts/*文件指定權限為 OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ。
因為crnode.txt 要安裝到/< prefix >/share/doc/crnode,所以我們不能直接安裝整個 doc 目錄,這里采用的方式是安裝 doc 目錄中的內容,也就是使用”doc/”在工程文件中添加:
1 |
INSTALL(DIRECTORY doc/ DESTINATION share/doc/crnode) |
5. 編譯安裝
編譯安裝結果如下:
|
大功告成!更多內容請參考《CMake Practice》,再次對《CMake Practice》的作者表示感謝!
來自: Now Code, 轉載請注明出處: http://now-code.com/archives/208
