一、cmake簡介
1.背景知識
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 必然會成為一個主流的構建體系。
2.特點
a.開放源代碼,使用類 BSD 許可發布。http://cmake.org/HTML/Copyright.html
b.跨平台,並可生成 native 編譯配置文件,在 Linux/Unix 平台,生成 makefile,在蘋果平台,可以生成 xcode,在 Windows 平台,可以生成 MSVC 的工程文件。
c.能夠管理大型項目,KDE4 就是最好的證明。
d.簡化編譯構建過程和編譯過程。Cmake 的工具鏈非常簡單:cmake+make。
e.高效率,按照 KDE 官方說法,CMake 構建 KDE4 的 kdelibs 要比使用 autotools 來構建 KDE3.5.6 的 kdelibs 快 40%,主要是因為 Cmake 在工具鏈中沒有 libtool。
d.可擴展,可以為 cmake 編寫特定功能的模塊,擴充 cmake 功能。
二、安裝cmake
可以直接在命令行下載cmake:
$ sudo apt-get install cmakek
也可以從官網下載最新版本:
http://www.cmake.org/HTML/Download.html
三、初試cmake
1.首先創建工作文件夾
$ mkdir -p ./cmake/cmTest $ cd cmake/cmTest
2.編寫測試代碼和CMakeLists.txt
//main.c #include<stdio.h> int main() { printf("Hello world!\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})
3.構建工程
$ cmake .
. 表示在當前目錄
這里需要注意的地方:
a.CMakeLists.txt文件名的大小寫
$ cmake .
CMake Error: The source directory "/home/hwade/hwade/CMake/cmTest2" does not appear to contain CMakeLists.txt.
文件名必須嚴格按照標題的書寫,正確的文件名在書寫CMakeLists.txt時關鍵字和變量會高亮.
b.CMakeLists.txt最后一行的 ${SRC_LIST},在官網中是直接寫着SRC_LIST,這樣會報錯找不到SRC_LIST
$ cmake .
-- This is BINARY dir /home/hwade/hwade/CMake/cmTest -- This is SOURCE dir /home/hwade/hwade/CMake/cmTest -- Configuring done CMake Error at CMakeLists.txt:5 (ADD_EXECUTABLE): Cannot find source file: SRC_LIST Tried extensions .c .C .c++ .cc .cpp .cxx .m .M .mm .h .hh .h++ .hm .hpp .hxx .in .txx -- Build files have been written to: /home/hwade/hwade/CMake/cmTest
盡管錯誤,同樣會生成如下文件:
CMakeCache.txt CMakeFiles
但並未生成makefile文件
正確的執行結果:
$ cmake .
-- This is BINARY dir /home/hwade/hwade/CMake/cmTest -- This is SOURCE dir /home/hwade/hwade/CMake/cmTest -- Configuring done -- Generating done -- Build files have been written to: /home/hwade/hwade/CMake/cmTest
目錄中的文件
CMakeCache.txt cmake_install.cmake main.c
CMakeFiles CMakeLists.txt Makefile
輸入make命令
$ make
Scanning dependencies of target hello
[100%] Building C object CMakeFiles/hello.dir/main.c.o
Linking C executable hello
[100%] Built target hello
執行完make命令后就會生成可執行文件
$ ls CMakeCache.txt cmake_install.cmake hello Makefile CMakeFiles CMakeLists.txt main.c
$ ./hello Hello World!
4.CMakeLists.txt解釋
我們來重新看一下 CMakeLists.txt,這個文件是 cmake 的構建定義文件,文件名是大小寫相關的,如果工程存在多個目錄,需要確保每個要管理的目錄都存在一個
CMakeLists.txt。
a. PROJECT 指令的語法是:
PROJECT(projectname [CXX] [C] [Java])
你可以用這個指令定義工程名稱,並可指定工程支持的語言,支持的語言列表是可以忽略的,默認情況表示支持所有語言。這個指令隱式的定義了兩個 cmake 變量:
<projectname>_BINARY_DIR 以及<projectname>_SOURCE_DIR,這里就是HELLO_BINARY_DIR 和 HELLO_SOURCE_DIR(所以 CMakeLists.txt 中兩個 MESSAGE指令可以直接使用了這兩個變量),因為采用的是內部編譯,兩個變量目前指的都是工程所在路徑/home/hwade/CMake/cmTest,后面我們會講到外部編譯,兩者所指代的內容會有所不同。同時 cmake 系統也幫助我們預定義了 PROJECT_BINARY_DIR 和 PROJECT_SOURCE_DIR變量,他們的值分別跟 HELLO_BINARY_DIR 與 HELLO_SOURCE_DIR 一致。為了統一起見,建議以后直接使用 PROJECT_BINARY_DIR,PROJECT_SOURCE_DIR,即使修改了工程名稱,也不會影響這兩個變量。如果使用了<projectname>_SOURCE_DIR,修改工程名稱后,需要同時修改這些變量。
b. SET 指令的語法是:
SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])
現階段,只需要了解 SET 指令可以用來顯式地定義變量即可。比如我們用到的是 SET(SRC_LIST main.c),如果有多個源文件,也可以定義成: SET(SRC_LIST main.c t1.c t2.c)。
c. MESSAGE 指令的語法是:
MESSAGE([SEND_ERROR | STATUS | FATAL_ERROR] "message to display"...)
這個指令用於向終端輸出用戶定義的信息,包含了三種類型:
SEND_ERROR,產生錯誤,生成過程被跳過。
SATUS,輸出前綴為—的信息。
FATAL_ERROR,立即終止所有 cmake 過程.
我們在這里使用的是 STATUS 信息輸出,演示了由 PROJECT 指令定義的兩個隱式變量HELLO_BINARY_DIR 和 HELLO_SOURCE_DIR。
d. ADD_EXECUTABLE指令的語法是:
ADD_EXECUTABLE(hello ${SRC_LIST})
定義了這個工程會生成一個文件名為 hello 的可執行文件,相關的源文件是 SRC_LIST 中定義的源文件列表, 本例中也可以直接寫成 ADD_EXECUTABLE(hello main.c)。
本例使用了${}來引用變量,這是 cmake 的變量應用方式,但是,有一些例外,比如在 IF 控制語句,變量是直接使用變量名引用,而不需要${}。如果使用了${}去應用變
量,其實 IF 會去判斷名為${}所代表的值的變量,那當然是不存在的了。
5. 基本語法規則
前面提到過,cmake 其實仍然要使用”cmake 語言和語法”去構建,上面的內容就是所謂的”cmake 語言和語法”,最簡單的語法規則是:
a. 變量使用${}方式取值,但是在 IF 控制語句中是直接使用變量名
b. 指令(參數 1 參數 2...)
參數使用括弧括起,參數之間使用空格或分號分開。以上面的 ADD_EXECUTABLE 指令為例,如果存在另外一個 func.c 源文件,就要寫成:
ADD_EXECUTABLE(hello main.c func.c)
或者 ADD_EXECUTABLE(hello main.c;func.c)
c. 指令是大小寫無關的,參數和變量是大小寫相關的。但推薦全部使用大寫指令。上面的 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 可執行文件。
6. 關於語法的疑惑
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。
同時參數也可以使用分號來進行分割。下面的例子也是合法的:
ADD_EXECUTABLE(t1 main.c t1.c)
可以寫成
ADD_EXECUTABLE(t1 main.c;t1.c)
只需要在編寫 CMakeLists.txt 時注意形成統一的風格即可。
7. 清理工程:
跟經典的 autotools 系列工具一樣,運行:
$ make clean
即可對構建結果進行清理。
8. Q&A
Q: “我嘗試運行了 make distclean,這個指令一般用來清理構建過程中產生的中間文件的,如果要發布代碼,必然要清理掉所有的中間文件,但是為什么在 cmake 工程中這個命令是無效的?”
A: 是的,cmake 並不支持 make distclean,關於這一點,官方是有明確解釋的:
因為 CMakeLists.txt 可以執行腳本並通過腳本生成一些臨時文件,但是卻沒有辦法來跟蹤這些臨時文件到底是哪些。因此,沒有辦法提供一個可靠的 make distclean 方案。
Some build trees created with GNU autotools have a "make distclean" 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)。
9. 內部構建與外部構建:
上面的例子展示的是“內部構建”,相信看到生成的臨時文件比代碼文件還要多的時候,估計這輩子都不希望再使用內部構建,舉個簡單的例子來說明外部構建,以編譯 wxGTK 動態庫和靜態庫為例,在 Everest 中打包方式是這樣的:
解開 wxGTK 后,在其中建立 static 和 shared 目錄。進入 static 目錄,運行
$ ../configure –enable-static
$ make
會在 static 目錄生成 wxGTK 的靜態庫。進入 shared 目錄,運行
$ ../configure –enable-shared
$ make
就會在 shared 目錄生成動態庫。這就是外部編譯的一個簡單例子。
對於 cmake,內部編譯上面已經演示過了,它生成了一些無法自動刪除的中間文件,所以引出了對外部編譯的探討,外部編譯的過程如下:
a. 首先,請清除 t1 目錄中除 main.c CmakeLists.txt 之外的所有中間文件,最關鍵的是 CMakeCache.txt。
b. 在 t1 目錄中建立 build 目錄,當然你也可以在任何地方建立 build 目錄,不一定必須在工程目錄中。
c. 進入 build 目錄,運行
$ cmake ..
(注意,..代表父目錄,因為父目錄存在我們需要的CMakeLists.txt,如果你在其他地方建立了 build 目錄,需要運行 cmake <工程的全路徑>)
查看一下 build 目錄,就會發現了生成了編譯需要的 Makefile 以及其他的中間文件。
d. 運行
$ make
構建工程,就會在當前目錄(build 目錄)中獲得目標文件 hello。上述過程就是所謂的 out-of-source 外部編譯,一個最大的好處是,對於原有的工程沒有任何影響,所有動作全部發生在編譯目錄。通過這一點,也足以說服我們全部采用外部編譯方式構建工程。
這里需要特別注意的是:
通過外部編譯進行工程構建,HELLO_SOURCE_DIR 仍然指代工程路徑,即/backup/cmake/t1,而 HELLO_BINARY_DIR 則指代編譯路徑,即/backup/cmake/t1/build
10. 小結:
本小節描述了使用 cmake 構建 Hello World 程序的全部過程,並介紹了三個簡單的指令PROJECT/MESSAGE/ADD_EXECUTABLE 以及變量調用的方法,同時提及了兩個隱式變量<projectname>_SOURCE_DIR 及<projectname>_BINARY_DIR,演示了變量調用的方法,從這個過程來看,有些開發者可能會想,這實在比我直接寫 Makefile 要復雜多了,甚至我都可以不編寫 Makefile,直接使用 gcc main.c 即可生成需要的目標文件。是的,如果工程只有幾個文件,還是直接編寫 Makefile 最簡單。但是,kdelibs 壓縮包達到了 50 多 M,您認為使用什么方案會更容易一點呢?