《CMake實踐》筆記一:PROJECT/MESSAGE/ADD_EXECUTABLEphp
《CMake實踐》筆記二:INSTALL/CMAKE_INSTALL_PREFIXhtml
《CMake實踐》筆記三:構建靜態庫與動態庫 及 如何使用外部共享庫和頭文件java
開發了5,6年的時間,若是沒有KDE4,也許不會有人或者Linux發行版本重視cmake,由於除了Kitware彷佛沒有人使用它。經過KDE4的選型和開發,cmake逐漸進入了人們的視線,在實際的使用過程當中,cmake的優點也逐漸的被你們所認識,至少KDE的開發者們給予了cmake極高的評價,同時龐大的KDE項目使用cmake來做爲構建工具也證實了cmake的可用性和大項目管理能力。c++
因此,cmake應該感謝KDE,也正由於如此,cmake的開發者投入了KDE從autotools到cmake的遷移過程當中,並至關快速和順利的完成了遷移,如今整個KDE4開發版本所有使用cmake構建。編程
這也是促使咱們學習cmake的緣由,首先cmake被接受併成功應用,其次,cmake的優點在實際使用中不斷的體現出來。xcode
咱們爲何不來認識一下這款優秀的工程構建工具呢?bash
在2006年KDE大會,聽cmake開發者當面介紹了cmake以後,我就開始關注cmake,並將cmake歸入了Everest發行版,做爲系統默認組件。最近QT-4.3也正式進入了Everest系統,爲KDE4構建完成了準備工做。svn
可是,在學習cmake的過程當中,發現官方的文檔很是的少,並且錯誤也較多,好比:在介紹Find<Name>模塊編寫的文檔中,模塊名稱爲FOO,可是後面卻出現了Foo_FIND_QUIETLY的定義,這顯然是錯誤的,這樣的定義永遠不可能有效,正確的定義是FOO_FIND_QUIETLY。種種緣由,促使我開始寫一份「面向使用和實用」的cmake文檔,也就是本教程《Cmake實踐》(Cmake Practice)工具
本文檔是邊學習邊編寫的成果,更像是一個學習筆記和Tutorial,所以不免有失誤或者理解不夠透徹的地方,好比,我仍然不能理解爲何絕大部分使用變量的狀況要經過${}引用,而在IF語句中卻必須直接使用變量名。也但願可以有cmake的高手來指點迷津。學習
補:從cmake的maillist,我找到了一些答案,原文是:
The `IF(var)' or `IF(NOT var)' command expects `var' to be the name of a variable. This is stated in CMake's manual. So, for your situation `IF(${libX})' is the same as `IF(/usr/lib/xorg)' and then CMake will check the value of the variable named `/usr/lib/xorg'.
也就是說IF須要的是變量名而不是變量值
這個文檔是開放的,開放的目的是爲了讓更多的人可以讀到而且可以修改,任何人均可以對它做出修改和補充,可是,爲了你們都可以得到你關於cmake的經驗和積累,若是你現錯誤或者添加了新內容後,請務必CC給我一份,讓咱們共同把cmake掌握的更好。
再也不使你在構建項目時鬱悶地想自殺了.
--一位KDE開發者
一、背景知識:
cmake是kitware公司以及一些開源開發者在開發幾個工具套件(VTK)的過程當中衍生品,最終造成體系,成爲一個獨立的開放源代碼項目。項目的誕生時間是2001年。其官方網站是 www.cmake.org,能夠經過訪問官方網站得到更多關於cmake的信息。cmake的流行其實要歸功於KDE4的開發(彷佛跟當年的svn同樣,KDE將代碼倉庫從CVS遷移到SVN,同時證實了SVN管理大型項目的可用性),在KDE開發者使用了近10年autotools以後,他們終於決定爲KDE4選擇一個新的工程構建工具,其根本緣由用KDE開發者的話來講就是:只有少數幾個「編譯專家」可以掌握KDE如今的構建體系(admin/Makefile.common),在經歷了unsermake, scons以及cmake的選型和嘗試以後,KDE4決定使用cmake做爲本身的構建系統。在遷移過程當中,進展異常的順利,並得到了cmake開發者的支持。因此,目前的KDE4開發版本已經徹底使用cmake來進行構建。像kdesvn,rosegarden等項目也開始使用cmake,這也註定了cmake必然會成爲一個主流的構建體系。
二、特色:
cmake的特色主要有:
(1)、開放源代碼,使用類BSD許可發佈。http://cmake.org/HTML/Copyright.html
(2)、跨平臺,並可生成native編譯配置文件,在Linux/Unix平臺,生成makefile,在蘋果平臺,能夠生成xcode,在Windows平臺,能夠生成MSVC的工程文件。
(3)、可以管理大型項目,KDE4就是最好的證實。
(4)、簡化編譯構建過程和編譯過程。Cmake的工具鏈很是簡單:cmake+make。
(5)、高效慮,按照KDE官方說法,CMake構建KDE4的kdelibs要比使用autotools來構建KDE3.5.6的kdelibs快40%,主要是由於 Cmake在工具鏈中沒有libtool。
(6)、可擴展,能夠爲cmake編寫特定功能的模塊,擴充cmake功能。
三、問題,難道就沒有問題?
(1)、cmake很簡單,但絕對沒有聽起來或者想象中那麼簡單。
(2)、cmake編寫的過程其實是編程的過程,跟之前使用autotools同樣,不過你須要編寫的是CMakeLists.txt(每一個目錄一個),使用的是」cmake語言和語法」。
(3)、cmake跟已有體系的配合並非特別理想,好比pkgconfig,您在實際使用中會有所體會,雖然有一些擴展可使用,但並不理想。
四、我的的建議:
(1)、若是你沒有實際的項目需求,那麼看到這裏就能夠停下來了,由於cmake的學習過程就是實踐過程,沒有實踐,讀的再多幾天後也會忘記。
(2)、若是你的工程只有幾個文件,直接編寫Makefile是最好的選擇。
(3)、若是使用的是C/C++/Java以外的語言,請不要使用cmake(至少目前是這樣)
(4)、若是你使用的語言有很是完備的構建體系,好比java的ant,也不須要學習cmake,雖然有成功的例子,好比QT4.3的csharp綁定qyoto。
(5)、若是項目已經採用了很是完備的工程管理工具,而且不存在維護問題,沒有必要遷移到cmake
(6)、若是僅僅使用qt編程,沒有必要使用cmake,由於qmake管理Qt工程的專業性和自動化程度比cmake要高不少。
須要安裝嗎?
cmake目前已經成爲各大Linux發行版提供的組件,好比Everest直接在系統中包含,Fedora在extra倉庫中提供,因此,須要本身動手安裝的可能性很小。若是你使用的操做系統(好比Windows或者某些Linux版本)沒有提供cmake或者包含的版本較舊,建議你直接從cmake官方網站下載安裝。
在這個頁面,提供了源代碼的下載以及針對各類不一樣操做系統的二進制下載,能夠選擇適合本身操做系統的版本下載安裝。由於各個系統的安裝方式和包管理格式有所不一樣,在此就再也不贅述了,相信必定可以順利安裝cmake。
Hello world,世界 你好
本節選擇了一個最簡單的例子Helloworld來演練一下cmake的完整構建過程,本節並不會深刻的探討cmake,僅僅展現一個簡單的例子,並加以粗略的解釋。咱們選擇了Everest Linux做爲基本開發平臺,由於這個只有一張CD的發行版本,包含了gcc-4.2/gtk/qt3/qt4等完整的開發環境,同時,系統默認集成了cmake最新版本2.4.6。
一、準備工做:
首先,在/backup目錄創建一個cmake目錄,用來放置咱們學習過程當中的全部練習。
mkdir -p /backup/cmake
之後咱們全部的cmake練習都會放在/backup/cmake的子目錄下(你也能夠自行安排目錄,這個並非限制,僅僅是爲了敘述的方便)
而後在cmake創建第一個練習目錄t1
cd /backup/cmake
mkdir t1
cd t1
在t1目錄創建 main.c 和 CMakeLists.txt (注意文件名大小寫):
main.c文件內容:
#include <stdio.h>
int main()
{
printf("Hello World from t1 Main!\n");
return 0;
}
CmakeLists.txt文件內容:
PROJECT(HELLO)
SET(SRC_LIST main.c)
MESSAGE(STATUS "This is BINARY dir " ${HELLO_BINARY_DIR})
MESSAGE(STATUS "This is SOURCE dir "${HELLO_SOURCE_DIR})
ADD_EXECUTABLE(hello ${SRC_LIST})
二、開始構建
全部的文件建立完成後,t1目錄中應該存在main.c和CMakeLists.txt兩個文件接下來咱們來構建這個工程,在這個目錄運行:
cmake . # 注意命令後面的點號,表明本目錄
輸出大概是這個樣子:
-- Check for working C compiler: /usr/bin/gcc
-- Check for working C compiler: /usr/bin/gcc -- works
-- Check size of void*
-- Check size of void* - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- This is BINARY dir /backup/cmake/t1
-- This is SOURCE dir /backup/cmake/t1
-- Configuring done
-- Generating done
-- Build files have been written to: /backup/cmake/t1
再讓咱們看一下目錄中的內容:你會發現,系統自動生成了:
CMakeFiles,CMakeCache.txt,cmake_install.cmake等文件,而且生成了Makefile。
如今不須要理會這些文件的做用,之後你也能夠不去理會。最關鍵的是,它自動生成了Makefile。而後進行工程的實際構建,在這個目錄輸入make命令,
make
大概會獲得以下的彩色輸出:
Scanning dependencies of target hello
[100%] Building C object CMakeFiles/hello.dir/main.o
Linking C executable hello
[100%] Built target hello
若是你須要看到make構建的詳細過程,可使用make VERBOSE=1或者VERBOSE=1 make命令來進行構建。這時候,咱們須要的目標文件hello已經構建完成,位於當前目錄,嘗試運行一下:
./hello
獲得輸出:
Hello World from Main!
恭喜您,到這裏爲止您已經徹底掌握了cmake的使用方法。
三、簡單的解釋:
咱們來從新看一下CMakeLists.txt,這個文件是cmake的構建定義文件,文件名是大小寫相關的,若是工程存在多個目錄,須要確保每一個要管理的目錄都存在一個CMakeLists.txt。(關於多目錄構建,後面咱們會提到,這裏不做過多解釋)。
上面例子中的CMakeLists.txt文件內容以下:
PROJECT (HELLO)
SET(SRC_LIST main.c)
MESSAGE(STATUS "This is BINARY dir " ${HELLO_BINARY_DIR})
MESSAGE(STATUS "This is SOURCE dir "${HELLO_SOURCE_DIR})
ADD_EXECUTABLE(hello ${SRC_LIST})
PROJECT指令的語法是:
PROJECT(projectname [CXX] [C] [Java])
你能夠用這個指令定義工程名稱,並可指定工程支持的語言,支持的語言列表是能夠忽略的,這個指令隱式的定義了兩個cmake變量:
<projectname>_BINARY_DIR 以及 <projectname>_SOURCE_DIR,這裏就是 HELLO_BINARY_DIR 和 HELLO_SOURCE_DIR (因此CMakeLists.txt中兩個MESSAGE指令能夠直接使用了這兩個變量),由於採用的是內部編譯,兩個變量目前指的都是工程所在路徑/backup/cmake/t1,後面咱們會講到外部編譯,二者所指代的內容會有所不一樣。
同時cmake系統也幫助咱們預約義了 PROJECT_BINARY_DIR 和 PROJECT_SOURCE_DIR 變量,他們的值分別跟 HELLO_BINARY_DIR 與 HELLO_SOURCE_DIR 一致。
爲了統一塊兒見,建議之後直接使用 PROJECT_BINARY_DIR,PROJECT_SOURCE_DIR,即便修改了工程名稱,也不會影響這兩個變量。若是使用了<projectname>_SOURCE_DIR,修改工程名稱後,須要同時修改這些變量。
SET指令的語法是:
SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])
現階段,你只須要了解SET指令能夠用來顯式的定義變量便可。好比咱們用到的是SET(SRC_LIST main.c),若是有多個源文件,也能夠定義成:
SET(SRC_LIST main.c t1.c t2.c)
MESSAGE指令的語法是:
MESSAGE([SEND_ERROR | STATUS | FATAL_ERROR] "message to display"...)
這個指令用於向終端輸出用戶定義的信息,包含了三種類型:
咱們在這裏使用的是STATUS信息輸出,演示了由PROJECT指令定義的兩個隱式變量HELLO_BINARY_DIR和HELLO_SOURCE_DIR。
ADD_EXECUTABLE(hello ${SRC_LIST})
定義了這個工程會生成一個文件名爲hello的可執行文件,相關的源文件是SRC_LIST中定義的源文件列表, 本例中你也能夠直接寫成 ADD_EXECUTABLE(hello main.c)。
在本例咱們使用了${}來引用變量,這是cmake的變量應用方式,可是,有一些例外,好比在IF控制語句,變量是直接使用變量名引用,而不須要${}。若是使用了${}去應用變量,其實IF會去判斷名爲${}所表明的值的變量,那固然是不存在的了。
將本例改寫成一個最簡化的CMakeLists.txt:
PROJECT(HELLO)
ADD_EXECUTABLE(hello main.c)
四、基本語法規則
前面提到過,cmake其實仍然要使用「cmake語言和語法」去構建,上面的內容就是所謂的「cmake語言和語法」,最簡單的語法規則是:
(1)、變量使用${}方式取值,可是在IF控制語句中是直接使用變量名
(2)、指令(參數1 參數2...)
參數使用括弧括起,參數之間使用空格或分號分開。
以上面的ADD_EXECUTABLE指令爲例,若是存在另一個func.c源文件,就要寫成:ADD_EXECUTABLE(hello main.c func.c)或者ADD_EXECUTABLE(hello main.c;func.c)
(3)、指令是大小寫無關的,參數和變量是大小寫相關的。但,推薦你所有使用大寫指令。
上面的MESSAGE指令咱們已經用到了這條規則:
MESSAGE(STATUS 「This is BINARY dir」 ${HELLO_BINARY_DIR})
也能夠寫成:
MESSAGE(STATUS 「This is BINARY dir ${HELLO_BINARY_DIR}」)
這裏須要特別解釋的是做爲工程名的HELLO和生成的可執行文件hello是沒有任何關係的。hello定義了可執行文件的文件名,你徹底能夠寫成:ADD_EXECUTABLE(t1 main.c)編譯後會生成一個t1可執行文件。
五、關於語法的疑惑
cmake的語法仍是比較靈活並且考慮到各類狀況,好比
SET(SRC_LIST main.c) 也能夠寫成 SET(SRC_LIST "main.c")
是沒有區別的,可是假設一個源文件的文件名是fu nc.c(文件名中間包含了空格)。這時候就必須使用雙引號,若是寫成了SET(SRC_LIST fu nc.c),就會出現錯誤,提示你找不到fu文件和nc.c文件。這種狀況,就必須寫成:
SET(SRC_LIST "fu nc.c")
此外,你能夠能夠忽略掉source列表中的源文件後綴,好比能夠寫成ADD_EXECUTABLE(t1 main),cmake會自動的在本目錄查找main.c或者main.cpp等,固然,最好不要偷這個懶,以避免這個目錄確實存在一個main.c一個main.cpp
同時參數也可使用分號來進行分割。下面的例子也是合法的:
ADD_EXECUTABLE(t1 main.c t1.c)能夠寫成ADD_EXECUTABLE(t1 main.c;t1.c).
咱們只須要在編寫CMakeLists.txt時注意造成統一的風格便可。
六、清理工程:
跟經典的autotools系列工具同樣,運行:
make clean
便可對構建結果進行清理。
七、問題?問題!
「我嘗試運行了make distclean,這個指令通常用來清理構建過程當中產生的中間文件的,若是要發佈代碼,必然要清理掉全部的中間文件,可是爲何在cmake工程中這個命令是無效的?」
是的,cmake並不支持make distclean,關於這一點,官方是有明確解釋的:
由於CMakeLists.txt能夠執行腳本並經過腳本生成一些臨時文件,可是卻沒有辦法來跟蹤這些臨時文件究竟是哪些。所以,沒有辦法提供一個可靠的make distclean方案。
Some build trees created with GNU autotools have a "makedistclean" target that cleans the build and also removes Makefiles and other parts of the generated build system. CMake does not generate a "make distclean" target because CMakeLists.txt files can run scripts and arbitrary commands; CMake has no way of tracking exactly which files are generated as part of running CMake. Providing a distclean target would give users the false impression that it would work as expected. (CMake does generate a "make clean" target to remove files generated by the compiler and linker.)
A "make distclean" target is only necessary if the user performs an in-source build. CMake supports in-source builds, but we strongly encourage users to adopt the notion of an out-of-source build. Using a build tree that is separate from the source tree will prevent CMake from generating any files in the source tree. Because CMake does not change the source tree, there is no need for a distclean target. One can start a fresh build by deleting the build tree or creating a separate build tree.
同時,還有另一個很是重要的提示,就是:咱們剛纔進行的是內部構建(in-source build),而cmake強烈推薦的是外部構建(out-of-source build)。
八、內部構建 與 外部構建:
上面的例子展現的是「內部構建」,相信看到生成的臨時文件比您的代碼文件還要多的時候,估計這輩子你都不但願再使用內部構建!
舉個簡單的例子來講明外部構建,以編譯wxGTK動態庫和靜態庫爲例,在Everest中打包方式是這樣的:
解開wxGTK後。
在其中創建static和shared目錄。
進入static目錄,運行 ../configure –enable-static;make 會在static目錄生成wxGTK的靜態庫。
進入shared目錄,運行 ../configure –enable-shared;make 就會在shared目錄生成動態庫。
這就是外部編譯的一個簡單例子。
對於cmake,內部編譯上面已經演示過了,它生成了一些沒法自動刪除的中間文件,因此,引出了咱們對外部編譯的探討,外部編譯的過程以下:
(1)、首先,請清除t1目錄中除main.c CMakeLists.txt以外的全部中間文件,最關鍵的是CMakeCache.txt。
(2)、在t1目錄中創建build 目錄,固然你也能夠在任何地方創建build目錄,不必定必須在工程目錄中。
(3)、進入build目錄,運行 cmake ..
(注意,..表明父目錄,由於父目錄存在咱們須要的CMakeLists.txt,若是你在其餘地方創建了build目錄,須要運行cmake <工程的全路徑>),查看一下build目錄,就會發現了生成了編譯須要的Makefile以及其餘的中間文件。
(4)、運行make構建工程,就會在當前目錄(build目錄)中得到目標文件hello。上述過程就是所謂的out-of-source外部編譯,一個最大的好處是,對於原有的工程沒有任何影響,全部動做所有發生在編譯目錄。經過這一點,也足以說服咱們所有采用外部編譯方式構建工程。
這裏須要特別注意的是:
經過外部編譯進行工程構建,HELLO_SOURCE_DIR 仍然指代工程路徑,即 /backup/cmake/t1 而HELLO_BINARY_DIR 則指代編譯路徑,即/backup/cmake/t1/build
九、小結:
本小節描述了使用cmake構建Hello World程序的所有過程,並介紹了三個簡單的指令:PROJECT/MESSAGE/ADD_EXECUTABLE以及變量調用的方法,同時說起了兩個隱式變量<projectname>_SOURCE_DIR及<projectname>_BINARY_DIR,演示了變量調用的方法,從這個過程來看,有些開發者可能會想,這實在比我直接寫Makefile要複雜多了,甚至我均可以不編寫Makefile,直接使用gcc main.c便可生成須要的目標文件。是的,正如第一節提到的,若是工程只有幾個文件,仍是直接編寫Makefile最簡單。可是,kdelibs壓縮包達到了50多M,您認爲使用什麼方案會更容易一點呢?
下一節,咱們的任務是讓HelloWorld看起來更像一個工程。
未完,待續。。。。
《CMake實踐》筆記一:PROJECT/MESSAGE/ADD_EXECUTABLE