1. 程式人生 > >在linux下使用CMake構建應用程式

在linux下使用CMake構建應用程式

Cmake實踐
Cmake Practice --Cjacker
前言:
    cmake已經開發了5,6年的時間,如果沒有KDE4,也許不會有人或者Linux發行版
本重視cmake,因為除了Kitware似乎沒有人使用它。通過KDE4的選型和開發,cmake
逐漸進入了人們的視線,在實際的使用過程中,cmake的優勢也逐漸的被大家所認識,至
少KDE的開發者們給予了cmake極高的評價,同時龐大的KDE專案使用cmake來作為構
建工具也證明了cmake的可用性和大專案管理能力。
    所以,cmake應該感謝KDE,也正因為如此,cmake的開發者投入了KDE從
autotools到cmake的遷移過程中,並相當快速和順利的完成了遷移,現在整個KDE4開

發版本全部使用cmake構建。
    這也是促使我們學習cmake的原因,首先cmake被接受併成功應用,其次,cmake
的優勢在實際使用中不斷的體現出來。
    我們為什麼不來認識一下這款優秀的工程構建工具呢?
    在2006年KDE大會,聽cmake開發者當面介紹了cmake之後,我就開始關注
cmake,並將cmake納入了Everest發行版,作為系統預設元件。最近QT-4.3也正式進
入了Everest系統,為KDE4構建完成了準備工作。
    但是,在學習cmake的過程中,發現官方的文件非常的少,而且錯誤也較多,比如:
在介紹Find模組編寫的文件中,模組名稱為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掌握的更好。
一,初識cmake
Cmake不再使你在構建專案時鬱悶地想自殺了.
--一位KDE開發者
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,特點:
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功能。
3,問題,難道就沒有問題?
1,cmake很簡單,但絕對沒有聽起來或者想象中那麼簡單。
2,cmake編寫的過程實際上是程式設計的過程,跟以前使用autotools一樣,不過你需要編
寫的是CMakeLists.txt(每個目錄一個) ” ,使用的是cmake ” 語言和語法。
3,cmake跟已有體系的配合並不是特別理想,比如pkgconfig,您在實際使用中會有所
體會,雖然有一些擴充套件可以使用,但並不理想。
4,個人的建議:
1,如果你沒有實際的專案需求,那麼看到這裡就可以停下來了,因為cmake的學習過程就
是實踐過程,沒有實踐,讀的再多幾天後也會忘記。
2,如果你的工程只有幾個檔案,直接編寫Makefile是最好的選擇。
3,如果使用的是C/C++/Java之外的語言,請不要使用cmake(至少目前是這樣)
4,如果你使用的語言有非常完備的構建體系,比如java的ant,也不需要學習cmake,
雖然有成功的例子,比如QT4.3的csharp繫結qyoto。
5,如果專案已經採用了非常完備的工程管理工具,並且不存在維護問題,沒有必要遷移到
cmake
4,如果僅僅使用qt程式設計,沒有必要使用cmake,因為qmake管理Qt工程的專業性和自
動化程度比cmake要高很多。
二,安裝cmake
還需要安裝嗎?
cmake目前已經成為各大Linux發行版提供的元件,比如Everest直接在系統中包含,
Fedora在extra倉庫中提供,所以,需要自己動手安裝的可能性很小。如果你使用的操
作系統(比如Windows或者某些Linux版本)沒有提供cmake或者包含的版本較舊,建議
你直接從cmake官方網站下載安裝。
http://www.cmake.org/HTML/Download.html
在這個頁面,提供了原始碼的下載以及針對各種不同作業系統的二進位制下載,可以選擇適合
自己作業系統的版本下載安裝。因為各個系統的安裝方式和包管理格式有所不同,在此就不
再贅述了,相信一定能夠順利安裝cmake。
三,初試cmake – cmake的helloworld
Hello world     ,世界你好
本節選擇了一個最簡單的例子Helloworld來演練一下cmake的完整構建過程,本節並不
會深入的探討cmake,僅僅展示一個簡單的例子,並加以粗略的解釋。我們選擇了
Everest Linux作為基本開發平臺,因為這個只有一張CD的發行版本,包含了gcc-4.2/gtk/qt3/qt4等完整的開發環境,同時,系統預設集成了cmake最新版本2.4.6。
1,準備工作:
首先,在/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檔案內容:
//main.c
#include 
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)
2,開始構建
所有的檔案建立完成後,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命令,大概會得到如下的彩色輸出:
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的使用方法。
3,簡單的解釋:
    我們來重新看一下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變數:
_BINARY_DIR以及_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,即
使修改了工程名稱,也不會影響這兩個變數。如果使用了
_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" 
...)
這個指令用於向終端輸出使用者定義的資訊,包含了三種類型:
SEND_ERROR,產生錯誤,生成過程被跳過。
SATUS — ,輸出字首為的資訊。
FATAL_ERROR,立即終止所有cmake過程.
我們在這裡使用的是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)
4,基本語法規則
前面提到過,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可執行檔案。
5,關於語法的疑惑
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時注意形成統一的風格即可。
6,清理工程:
跟經典的autotools系列工具一樣,執行:
make clean
即可對構建結果進行清理。
7,問題?問題!
“我嘗試運行了make distclean,這個指令一般用來清理構建過程中產生的中間檔案的,
如果要釋出程式碼,必然要清理掉所有的中間檔案,但是為什麼在cmake工程中這個命令是
” 無效的?
是的,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)。
8,內部構建與外部構建:
“ ” 上面的例子展示的是內部構建,相信看到生成的臨時檔案比您的程式碼檔案還要多的時候,
估計這輩子你都不希望再使用內部構建:-D
舉個簡單的例子來說明外部構建,以編譯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
9,小結:
本小節描述了使用cmake構建Hello World程式的全部過程,並介紹了三個簡單的指令:
PROJECT/MESSAGE/ADD_EXECUTABLE以及變數呼叫的方法,同時提及了兩個隱式變數
_SOURCE_DIR及_BINARY_DIR,演示了變數呼叫的方
法,從這個過程來看,有些開發者可能會想,這實在比我直接寫Makefile要複雜多了,
甚至我都可以不編寫Makefile,直接使用gcc main.c即可生成需要的目標檔案。是的,
正如第一節提到的,如果工程只有幾個檔案,還是直接編寫Makefile最簡單。但是,
kdelibs壓縮包達到了50多M,您認為使用什麼方案會更容易一點呢?
下一節,我們的任務是讓HelloWorld看起來更像一個工程。
四,更好一點的Hello World
沒有最好,只有更好
從本小節開始,後面所有的構建我們都將採用out-of-source外部構建,約定的構建目
錄是工程目錄下的build自錄。
本小節的任務是讓前面的Hello World更像一個工程,我們需要作的是:
1,為工程新增一個子目錄src,用來放置工程原始碼;
2,新增一個子目錄doc,用來放置這個工程的文件hello.txt
3,在工程目錄新增文字檔案COPYRIGHT, README;
4,在工程目錄新增一個runhello.sh指令碼,用來呼叫hello二進位制
4,將構建後的目標檔案放入構建目錄的bin子目錄;
5,最終安裝這些檔案:將hello二進位制與runhello.sh安裝至/usr/bin,將doc目錄
的內容以及COPYRIGHT/README安裝到/usr/share/doc/cmake/t2,將
1,準備工作:
在/backup/cmake/目錄下建立t2目錄。
將t1工程的main.c和CMakeLists.txt拷貝到t2目錄中。
2,新增子目錄src:
mkdir src
mv main.c src
現在的工程看起來是這個樣子:
一個子目錄src,一個CMakeLists.txt。
上一節我們提到,需要為任何子目錄建立一個CMakeLists.txt,
進入子目錄src,編寫CMakeLists.txt如下:
ADD_EXECUTABLE(hello main.c)
將t2工程的CMakeLists.txt修改為:
PROJECT(HELLO)
ADD_SUBDIRECTORY(src bin)
然後建立build目錄,進入build目錄進行外部編譯。
cmake  ..
make
構建完成後,你會發現生成的目標檔案hello位於build/bin目錄中。
語法解釋:
ADD_SUBDIRECTORY指令
ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
這個指令用於向當前工程新增存放原始檔的子目錄,並可以指定中間二進位制和目標二進位制存
放的位置。EXCLUDE_FROM_ALL引數的含義是將這個目錄從編譯過程中排除,比如,工程
的example,可能就需要工程構建完成後,再進入example目錄單獨進行構建(當然,你
也可以通過定義依賴來解決此類問題)。
上面的例子定義了將src子目錄加入工程,並指定編譯輸出(包含編譯中間結果)路徑為
bin目錄。如果不進行bin目錄的指定,那麼編譯結果(包括中間結果)都將存放在
build/src目錄(這個目錄跟原有的src目錄對應),指定bin目錄後,相當於在編譯時
將src重新命名為bin,所有的中間結果和目標二進位制都將存放在bin目錄。
這裡需要提一下的是SUBDIRS指令,使用方法是:
SUBDIRS(dir1 dir2...),但是這個指令已經不推薦使用。它可以一次新增多個子目錄,
並且,即使外部編譯,子目錄體系仍然會被儲存。
如果我們在上面的例子中將ADD_SUBDIRECTORY (src bin)修改為SUBDIRS(src)。
那麼在build目錄中將出現一個src目錄,生成的目的碼hello將存放在src目錄中。
3,換個地方儲存目標二進位制
不論是SUBDIRS還是ADD_SUBDIRECTORY指令(不論是否指定編譯輸出目錄),我們都可
以通過SET指令重新定義EXECUTABLE_OUTPUT_PATH和LIBRARY_OUTPUT_PATH變數
來指定最終的目標二進位制的位置(指最終生成的hello或者最終的共享庫,不包含編譯生成
的中間檔案)
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
在第一節我們提到了_BINARY_DIR和PROJECT_BINARY_DIR變數,他
們指的編譯發生的當前目錄,如果是內部編譯,就相當於PROJECT_SOURCE_DIR也就是
工程程式碼所在目錄,如果是外部編譯,指的是外部編譯所在目錄,也就是本例中的build
目錄。
所以,上面兩個指令分別定義了:
可執行二進位制的輸出路徑為build/bin和庫的輸出路徑為build/lib.
本節我們沒有提到共享庫和靜態庫的構建,所以,你可以不考慮第二條指令。
問題是,我應該把這兩條指令寫在工程的CMakeLists.txt還是src目錄下的
CMakeLists.txt,把握一個簡單的原則,在哪裡ADD_EXECUTABLE或ADD_LIBRARY,
如果需要改變目標存放路徑,就在哪裡加入上述的定義。
在這個例子裡,當然就是指src下的CMakeLists.txt了。
4,如何安裝。
安裝的需要有兩種,一種是從程式碼編譯後直接make install安裝,一種是打包時的指定
目錄安裝。
所以,即使最簡單的手工編寫的Makefile,看起來也是這個樣子的:
DESTDIR=
install:
mkdir -p $(DESTDIR)/usr/bin
install -m 755 hello $(DESTDIR)/usr/bin
你可以通過:
make install
將hello直接安裝到/usr/bin目錄,也可以通過make install 
DESTDIR=/tmp/test將他安裝在
/tmp/test/usr/bin目錄,打包時這個方式經常被使用。
稍微複雜一點的是還需要定義PREFIX,一般autotools工程,會執行這樣的指令:
./configure –prefix=/usr或者./configure --prefix=/usr/local來指定
PREFIX
比如上面的Makefile就可以改寫成:
DESTDIR=
PREFIX=/usr
install:
mkdir -p $(DESTDIR)/$(PREFIX)/bin
install -m 755 hello $(DESTDIR)/$(PREFIX)/bin
那麼我們的HelloWorld應該怎麼進行安裝呢?
這裡需要引入一個新的cmake    指令INSTALL和一個非常有用的變數
CMAKE_INSTALL_PREFIX。
CMAKE_INSTALL_PREFIX變數類似於configure  – 指令碼的 prefix,常見的使用方法看
起來是這個樣子:
cmake -DCMAKE_INSTALL_PREFIX=/usr .
INSTALL指令用於定義安裝規則,安裝的內容可以包括目標二進位制、動態庫、靜態庫以及
檔案、目錄、指令碼等。
INSTALL指令包含了各種安裝型別,我們需要一個個分開解釋:
目標檔案的安裝:
        INSTALL(TARGETS targets...
            [[ARCHIVE|LIBRARY|RUNTIME]
                        [DESTINATION ]] [...])
SCRIPT引數用於在安裝時呼叫cmake指令碼檔案(也就是.cmake檔案)
CODE引數用於執行CMAKE指令,必須以雙引號括起來。比如:
INSTALL(CODE "MESSAGE(\"Sample install message.\")")
安裝還有幾個被標記為過時的指令,比如INSTALL_FILES等,這些指令已經不再推薦使
用,所以,這裡就不再贅述了。
下面,我們就來改寫我們的工程檔案,讓他來支援各種檔案的安裝,並且,我們要使用
CMAKE_INSTALL_PREFIX指令。
5,修改Helloworld支援安裝
在本節開頭我們定義了本節的任務如下:
1,為工程新增一個子目錄src,用來儲存原始碼;
2,新增一個子目錄doc,用來儲存這個工程的文件hello.txt
3,在工程目錄新增文字檔案COPYRIGHT, README;
4,在工程目錄新增一個runhello.sh指令碼,用來呼叫hello二進位制
4,將構建後的目標檔案放入構建目錄的bin子目錄;
5,最終安裝這些檔案:將hello二進位制與runhello.sh安裝至//bin,將
doc目錄中的hello.txt以及COPYRIGHT/README安裝到
//share/doc/cmake/t2,將
首先我們先補上為新增的檔案。
新增doc目錄及檔案:
 cd /backup/cmake/t2
 mkdir doc
 vi doc/hello.txt
隨便填寫一些內容並儲存
在工程目錄新增runhello.sh指令碼,內容為:
hello
新增工程目錄中的COPYRIGHT和README
touch COPYRIGHT
touch README
下面改寫各目錄的CMakeLists.txt檔案。
1,安裝COPYRIGHT/README,直接修改主工程檔案CMakelists.txt,加入以下指令:
INSTALL(FILES COPYRIGHT README DESTINATION share/doc/cmake/t2)
2,安裝runhello.sh,直接修改主工程檔案CMakeLists.txt,加入如下指令:
INSTALL(PROGRAMS runhello.sh DESTINATION bin)
3,安裝doc中的hello.txt,這裡有兩種方式:一是通過在doc目錄建立
CMakeLists.txt並將doc目錄通過ADD_SUBDIRECTORY加入工程來完成。另一種方法
是直接在工程目錄通過
INSTALL(DIRECTORY來完成),前者比較簡單,各位可以根據興趣自己完成,我們來嘗試
後者,順便演示以下DIRECTORY的安裝。
因為hello.txt要安裝到//share/doc/cmake/t2,所以我們不能直接安裝
整個doc目錄,這裡採用的方式是安裝doc ” 目錄中的內容,也就是使用doc/”
在工程檔案中新增
INSTALL(DIRECTORY doc/ DESTINATION share/doc/cmake/t2)
6,嘗試我們修改的結果:
現在進入build目錄進行外部編譯,注意使用CMAKE_INSTALL_PREFIX引數,這裡我們
將它安裝到了/tmp/t2目錄:
cmake -DCMAKE_INSTALL_PREFIX=/tmp/t2/usr ..
然後執行
make
make install
讓我們進入/tmp/t2目錄看一下安裝結果:
./usr
./usr/share
./usr/share/doc
./usr/share/doc/cmake
./usr/share/doc/cmake/t2
./usr/share/doc/cmake/t2/hello.txt
./usr/share/doc/cmake/t2/README
./usr/share/doc/cmake/t2/COPYRIGHT
./usr/bin
./usr/bin/hello
./usr/bin/runhello.sh
如果你要直接安裝到系統,可以使用如下指令:
cmake -DCMAKE_INSTALL_PREFIX=/usr ..
7,一個疑問
如果我沒有定義CMAKE_INSTALL_PREFIX會安裝到什麼地方?
你可以嘗試以下,cmake ..;make;make install,你會發現
CMAKE_INSTALL_PREFIX的預設定義是/usr/local
8,小結:
本小節主要描述瞭如何在工程中使用多目錄、各種安裝指令以及
CMAKE_INSTALL_PREFIX變數(你真夠牛的,這麼點東西居然羅唆了這麼多文字)
在下一小節,我們將探討如何在cmake中構建動態庫和靜態庫,以及如何使用外部標頭檔案
和外部共享庫,畢竟,這是程式編寫中最長使用的(對了,你知道用怎樣的gcc引數可以
直接構建靜態庫和動態庫嗎?)
五,靜態庫與動態庫構建
讀者雲,太能羅唆了,一個Hello World就折騰了兩個大節。OK,從本節開始,我們不
再折騰Hello World了,我們來折騰Hello World的共享庫。
本節的任務:
1,建立一個靜態庫和動態庫,提供HelloFunc函式供其他程式程式設計使用,HelloFunc
向終端輸出Hello World字串。
2,安裝標頭檔案與共享庫。
一,準備工作:
在/backup/cmake目錄建立t3目錄,用於存放本節涉及到的工程
二,建立共享庫
cd /backup/cmake/t3
mkdir lib
在t3目錄下建立CMakeLists.txt,內容如下:
PROJECT(HELLOLIB)
ADD_SUBDIRECTORY(lib)
在lib目錄下建立兩個原始檔hello.c與hello.h
hello.c內容如下:
#include “hello.h”
void HelloFunc()
{
printf(“Hello World\n”);
}
hello.h內容如下:
#ifndef HELLO_H
#define HELLO_H
#include 
void HelloFunc();
#endif
在lib目錄下建立CMakeLists.txt,內容如下:
SET(LIBHELLO_SRC hello.c)
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
三,編譯共享庫:
仍然採用out-of-source編譯的方式,按照習慣,我們建立一個build目錄,在build
目錄中
cmake ..
make
這時,你就可以在lib目錄得到一個libhello.so,這就是我們期望的共享庫。
如果你要指定libhello.so生成的位置,可以通過在主工程檔案CMakeLists.txt中修
改ADD_SUBDIRECTORY(lib)指令來指定一個編譯輸出位置或者
在lib/CMakeLists.txt中新增
SET(LIBRARY_OUTPUT_PATH <路徑>)來指定一個新的位置。
這兩者的區別我們上一節已經提到了,所以,這裡不再贅述,下面,我們解釋一下一個新的
指令ADD_LIBRARY
        ADD_LIBRARY(libname    [SHARED|STATIC|MODULE]
          [EXCLUDE_FROM_ALL]
                source1 source2 ... sourceN)
你不需要寫全libhello.so,只需要填寫hello即可,cmake系統會自動為你生成
libhello.X
型別有三種:
SHARED,動態庫
STATIC,靜態庫
MODULE,在使用dyld的系統有效,如果不支援dyld,則被當作SHARED對待。
EXCLUDE_FROM_ALL引數的意思是這個庫不會被預設構建,除非有其他的元件依賴或者手
工構建。
四,新增靜態庫:
同樣使用上面的指令,我們在支援動態庫的基礎上再為工程新增一個靜態庫,按照一般的習
慣,靜態庫名字跟動態庫名字應該是一致的,只不過字尾是.a罷了。
下面我們用這個指令再來新增靜態庫:
ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC})
然後再在build目錄進行外部編譯,我們會發現,靜態庫根本沒有被構建,仍然只生成了
一個動態庫。因為hello作為一個target是不能重名的,所以,靜態庫構建指令無效。
如果我們把上面的hello修改為hello_static:
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})
就可以構建一個libhello_static.a的靜態庫了。
這種結果顯示不是我們想要的,我們需要的是名字相同的靜態庫和動態庫,因為target名
稱是唯一的,所以,我們肯定不能通過ADD_LIBRARY指令來實現了。這時候我們需要用到
另外一個指令:
SET_TARGET_PROPERTIES,其基本語法是:
        SET_TARGET_PROPERTIES(target1 target2 ...
                      PROPERTIES prop1 value1
                      prop2 value2 ...)
這條指令可以用來設定輸出的名稱,對於動態庫,還可以用來指定動態庫版本和API版本。
在本例中,我們需要作的是向lib/CMakeLists.txt中新增一條:
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")
這樣,我們就可以同時得到libhello.so/libhello.a兩個庫了。
與他對應的指令是:
GET_TARGET_PROPERTY(VAR target property)
具體用法如下例,我們向lib/CMakeListst.txt中新增:
GET_TARGET_PROPERTY(OUTPUT_VALUE hello_static OUTPUT_NAME)
MESSAGE(STATUS “This is the hello_static 
OUTPUT_NAME:”${OUTPUT_VALUE})
如果沒有這個屬性定義,則返回NOTFOUND.
讓我們來檢查一下最終的構建結果,我們發現,libhello.a已經構建完成,位於
build/lib目錄中,但是libhello.so去消失了。這個問題的原因是:cmake在構建一
個新的target時,會嘗試清理掉其他使用這個名字的庫,因為,在構建libhello.a時,
就會清理掉libhello.so.
為了迴避這個問題,比如再次使用SET_TARGET_PROPERTIES定義
CLEAN_DIRECT_OUTPUT屬性。
向lib/CMakeLists.txt中新增:
SET_TARGET_PROPERTIES(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1)
SET_TARGET_PROPERTIES(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 
1)
這時候,我們再次進行構建,會發現build/lib目錄中同時生成了libhello.so和
libhello.a
五,動態庫版本號
按照規則,動態庫是應該包含一個版本號的,我們可以看一下系統的動態庫,一般情況是
libhello.so.1.2
libhello.so ->libhello.so.1
libhello.so.1->libhello.so.1.2
為了實現動態庫版本號,我們仍然需要使用SET_TARGET_PROPERTIES指令。
具體使用方法如下:
SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1)
VERSION指代動態庫版本,SOVERSION指代API版本。
將上述指令加入lib/CMakeLists.txt中,重新構建看看結果。
在build/lib目錄會生成:
libhello.so.1.2
libhello.so.1->libhello.so.1.2
libhello.so ->libhello.so.1
六,安裝共享庫和標頭檔案
以上面的例子,我們需要將libhello.a, libhello.so.x以及hello.h安裝到系統目
錄,才能真正讓其他人開發使用,在本例中我們將hello的共享庫安裝到/lib
目錄,將hello.h安裝到/include/hello目錄。
利用上一節瞭解到的INSTALL指令,我們向lib/CMakeLists.txt中新增如下指令:
INSTALL(TARGETS hello hello_static 
LIBRARY DESTINATION lib 
ARCHIVE DESTINATION lib)
INSTALL(FILES hello.h DESTINATION include/hello)
注意,靜態庫要使用ARCHIVE關鍵字
通過:
cmake -DCMAKE_INSTALL_PREFIX=/usr ..
make
make install
我們就可以將標頭檔案和共享庫安裝到系統目錄/usr/lib和/usr/include/hello中了。
七,小結:
本小節,我們談到了:
如何通過ADD_LIBRARY指令構建動態庫和靜態庫。
如何通過SET_TARGET_PROPERTIES同時構建同名的動態庫和靜態庫。
如何通過SET_TARGET_PROPERTIES控制動態庫版本
最終使用上一節談到的INSTALL指令來安裝標頭檔案和動態、靜態庫。
在下一節,我們需要編寫另一個高階一點的Hello World來演示怎麼使用我們已經構建的
構建的共享庫libhello和外部標頭檔案。
六,如何使用外部共享庫和標頭檔案
抱歉,本節仍然繼續折騰Hello World.
上一節我們已經完成了libhello動態庫的構建以及安裝,本節我們的任務很簡單:
編寫一個程式使用我們上一節構建的共享庫。
1,準備工作:
請在/backup/cmake目錄建立t4目錄,本節所有資源將儲存在t4目錄。
2,重複以前的步驟,建立src目錄,編寫原始檔main.c,內容如下:
#include 
int main()
{
HelloFunc();
return 0;
}
編寫工程主檔案CMakeLists.txt
PROJECT(NEWHELLO)
ADD_SUBDIRECTORY(src)
編寫src/CMakeLists.txt
ADD_EXECUTABLE(main main.c)
上述工作已經嚴格按照我們前面季節提到的內容完成了。
3,外部構建
按照習慣,仍然建立build目錄,使用cmake ..方式構建。
過程:
cmake ..
make
構建失敗,如果需要檢視細節,可以使用第一節提到的方法
make VERBOSE=1來構建
錯誤輸出為是:
/backup/cmake/t4/src/main.c:1:19: error: hello.h: 沒有那個檔案或目錄
4,引入標頭檔案搜尋路徑。
hello.h位於/usr/include/hello目錄中,並沒有位於系統標準的標頭檔案路徑,
(有人會說了,白痴啊,你就不會include ,同志,要這麼幹,我這
一節就沒什麼可寫了,只能選擇一個glib或者libX11來寫了,這些程式碼寫出來很多同志
是看不懂的)
為了讓我們的工程能夠找到hello.h標頭檔案,我們需要引入一個新的指令
INCLUDE_DIRECTORIES,其完整語法為:
INCLUDE_DIRECTORIES([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)
這條指令可以用來向工程新增多個特定的標頭檔案搜尋路徑,路徑之間用空格分割,如果路徑
中包含了空格,可以使用雙引號將它括起來,預設的行為是追加到當前的標頭檔案搜尋路徑的
後面,你可以通過兩種方式來進行控制搜尋路徑新增的方式:
1,CMAKE_INCLUDE_DIRECTORIES_BEFORE,通過SET這個cmake變數為on,可以
將新增的標頭檔案搜尋路徑放在已有路徑的前面。
2,通過AFTER或者BEFORE引數,也可以控制是追加還是置前。
現在我們在src/CMakeLists.txt中新增一個頭檔案搜尋路徑,方式很簡單,加入:
INCLUDE_DIRECTORIES(/usr/include/hello)
進入build目錄,重新進行構建,這是找不到hello.h的錯誤已經消失,但是出現了一個
新的錯誤:
main.c:(.text+0x12): undefined reference to `HelloFunc'
因為我們並沒有link到共享庫libhello上。
5,為target新增共享庫
我們現在需要完成的任務是將目標檔案連結到libhello,這裡我們需要引入兩個新的指令
LINK_DIRECTORIES和TARGET_LINK_LIBRARIES
LINK_DIRECTORIES的全部語法是:
LINK_DIRECTORIES(directory1 directory2 ...)
這個指令非常簡單,新增非標準的共享庫搜尋路徑,比如,在工程內部同時存在共享庫和可
執行二進位制,在編譯時就需要指定一下這些共享庫的路徑。這個例子中我們沒有用到這個指
令。
TARGET_LINK_LIBRARIES的全部語法是:
TARGET_LINK_LIBRARIES(target library1
                      library2
                      ...)
這個指令可以用來為target新增需要連結的共享庫,本例中是一個可執行檔案,但是同樣
可以用於為自己編寫的共享庫新增共享庫連結。
為了解決我們前面遇到的HelloFunc未定義錯誤,我們需要作的是向
src/CMakeLists.txt中新增如下指令:
TARGET_LINK_LIBRARIES(main hello)
也可以寫成
TARGET_LINK_LIBRARIES(main libhello.so)
這裡的hello指的是我們上一節構建的共享庫libhello.
進入build目錄重新進行構建。
cmake ..
make
這是我們就得到了一個連線到libhello的可執行程式main,位於build/src目錄,運
行main的結果是輸出:
Hello World
讓我們來檢查一下main的連結情況:
ldd src/main
        linux-gate.so.1 =>  (0xb7ee7000)
        libhello.so.1 => /usr/lib/libhello.so.1 (0xb7ece000)
        libc.so.6 => /lib/libc.so.6 (0xb7d77000)
        /lib/ld-linux.so.2 (0xb7ee8000)
可以清楚的看到main確實連結了共享庫libhello,而且連結的是動態庫
libhello.so.1
那如何連結到靜態庫呢?
方法很簡單:
將TARGET_LINK_LIBRRARIES指令修改為:
TARGET_LINK_LIBRARIES(main libhello.a)
重新構建後再來看一下main的連結情況
ldd src/main
        linux-gate.so.1 =>  (0xb7fa8000)
        libc.so.6 => /lib/libc.so.6 (0xb7e3a000)
        /lib/ld-linux.so.2 (0xb7fa9000)
說明,main確實連結到了靜態庫libhello.a
6,特殊的環境變數CMAKE_INCLUDE_PATH和CMAKE_LIBRARY_PATH
務必注意,這兩個是環境變數而不是cmake變數。
使用方法是要在bash中用export或者在csh中使用set命令設定或者
CMAKE_INCLUDE_PATH=/home/include cmake ..等方式。
這兩個變數主要是用來解決以前autotools工程中
--extra-include-dir等引數的支援的。
也就是,如果標頭檔案沒有存放在常規路徑(/usr/include, /usr/local/include等),
則可以通過這些變數就行彌補。
我們以本例中的hello.h為例,它存放在/usr/include/hello目錄,所以直接查詢肯
定是找不到的。
前面我們直接使用了絕對路徑INCLUDE_DIRECTORIES(/usr/include/hello)告訴工
程這個標頭檔案目錄。
為了將程式更智慧一點,我們可以使用CMAKE_INCLUDE_PATH來進行,使用bash的方法
如下:
export CMAKE_INCLUDE_PATH=/usr/include/hello
然後在標頭檔案中將INCLUDE_DIRECTORIES(/usr/include/hello)替換為:
FIND_PATH(myHeader hello.h)
IF(myHeader)
INCLUDE_DIRECTORIES(${myHeader})
ENDIF(myHeader)
上述的一些指令我們在後面會介紹。
這裡簡單說明一下,FIND_PATH用來在指定路徑中搜索檔名,比如:
FIND_PATH(myHeader NAMES hello.h PATHS /usr/include 
/usr/include/hello)
這裡我們沒有指定路徑,但是,cmake仍然可以幫我們找到hello.h存放的路徑,就是因
為我們設定了環境變數CMAKE_INCLUDE_PATH。
如果你不使用FIND_PATH,CMAKE_INCLUDE_PATH變數的設定是沒有作用的,你不能指
望它會直接為編譯器命令新增引數-I。
以此為例,CMAKE_LIBRARY_PATH可以用在FIND_LIBRARY中。
同樣,因為這些變數直接為FIND_指令所使用,所以所有使用FIND_指令的cmake模組都
會受益。
7,小節:
本節我們探討了:
如何通過INCLUDE_DIRECTORIES指令加入非標準的標頭檔案搜尋路徑。
如何通過LINK_DIRECTORIES指令加入非標準的庫檔案搜尋路徑。
如果通過TARGET_LINK_LIBRARIES為庫或可執行二進位制加入庫連結。
並解釋瞭如果連結到靜態庫。
到這裡為止,您應該基本可以使用cmake工作了,但是還有很多高階的話題沒有探討,比
如編譯條件檢查、編譯器定義、平臺判斷、如何跟pkgconfig配合使用等等。
“ 到這裡,或許你可以理解前面講到的cmake的使用過程其實就是學習cmake語言並編寫
cmake ” “ 程式的過程,既然是cmake ” 語言,自然涉及到變數、語法等.
下一節,我們將拋開程式的話題,看看常用的CMAKE變數以及一些基本的控制語法規則。
七,cmake常用變數和常用環境變數
一,cmake變數引用的方式:
前面我們已經提到了,使用${}進行變數的引用。在IF等語句中,是直接使用變數名而不
通過${}取值
二,cmake自定義變數的方式:
主要有隱式定義和顯式定義兩種,前面舉了一個隱式定義的例子,就是PROJECT指令,他
會隱式的定義_BINARY_DIR和_SOURCE_DIR兩個變
量。
顯式定義的例子我們前面也提到了,使用SET指令,就可以構建一個自定義變量了。
比如:
SET(HELLO_SRC main.SOURCE_PATHc),就PROJECT_BINARY_DIR可以通過
${HELLO_SRC}來引用這個自定義變量了.
三,cmake常用變數:
1,CMAKE_BINARY_DIR
   PROJECT_BINARY_DIR
   _BINARY_DIR
這三個變數指代的內容是一致的,如果是in source編譯,指得就是工程頂層目錄,如果
是out-of-source編譯,指的是工程編譯發生的目錄。PROJECT_BINARY_DIR跟其他
指令稍有區別,現在,你可以理解為他們是一致的。
2,CMAKE_SOURCE_DIR
    PROJECT_SOURCE_DIR
    _SOURCE_DIR
這三個變數指代的內容是一致的,不論採用何種編譯方式,都是工程頂層目錄。
也就是在in source編譯時,他跟CMAKE_BINARY_DIR等變數一致。
PROJECT_SOURCE_DIR跟其他指令稍有區別,現在,你可以理解為他們是一致的。
3,CMAKE_CURRENT_SOURCE_DIR
指的是當前處理的CMakeLists.txt所在的路徑,比如上面我們提到的src子目錄。
4,CMAKE_CURRRENT_BINARY_DIR
如果是in-source編譯,它跟CMAKE_CURRENT_SOURCE_DIR一致,如果是out-of-source編譯,他指的是target編譯目錄。
使用我們上面提到的ADD_SUBDIRECTORY(src bin)可以更改這個變數的值。
使用SET(EXECUTABLE_OUTPUT_PATH <新路徑>)並不會對這個變數造成影響,它僅僅
修改了最終目標檔案存放的路徑。
5,CMAKE_CURRENT_LIST_FILE
輸出呼叫這個變數的CMakeLists.txt的完整路徑
6,CMAKE_CURRENT_LIST_LINE
輸出這個變數所在的行
7,CMAKE_MODULE_PATH
這個變數用來定義自己的cmake模組所在的路徑。如果你的工程比較複雜,有可能會自己
編寫一些cmake模組,這些cmake模組是隨你的工程釋出的,為了讓cmake在處理
CMakeLists.txt時找到這些模組,你需要通過SET指令,將自己的cmake模組路徑設
置一下。
比如
SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
這時候你就可以通過INCLUDE指令來呼叫自己的模組了。
8,EXECUTABLE_OUTPUT_PATH和LIBRARY_OUTPUT_PATH
分別用來重新定義最終結果的存放目錄,前面我們已經提到了這兩個變數。
9,PROJECT_NAME
返回通過PROJECT指令定義的專案名稱。
四,cmake呼叫環境變數的方式
使用$ENV{NAME}指令就可以呼叫系統的環境變量了。
比如
MESSAGE(STATUS “HOME dir: $ENV{HOME}”)
設定環境變數的方式是:
SET(ENV{變數名} 值)
1,CMAKE_INCLUDE_CURRENT_DIR
自動新增CMAKE_CURRENT_BINARY_DIR和CMAKE_CURRENT_SOURCE_DIR到當前處理
的CMakeLists.txt。相當於在每個CMakeLists.txt加入:
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR} 
${CMAKE_CURRENT_SOURCE_DIR})
2,CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE
將工程提供的標頭檔案目錄始終至於系統標頭檔案目錄的前面,當你定義的標頭檔案確實跟系統發
生衝突時可以提供一些幫助。
3,CMAKE_INCLUDE_PATH和CMAKE_LIBRARY_PATH我們在上一節已經提及。
五,系統資訊
1,CMAKE_MAJOR_VERSION,CMAKE主版本號,比如2.4.6中的2
2,CMAKE_MINOR_VERSION,CMAKE次版本號,比如2.4.6中的4
3,CMAKE_PATCH_VERSION,CMAKE補丁等級,比如2.4.6 中的6
4,CMAKE_SYSTEM,系統名稱,比如Linux-2.6.22
5,CMAKE_SYSTEM_NAME,不包含版本的系統名,比如Linux
6,CMAKE_SYSTEM_VERSION,系統版本,比如2.6.22
7,CMAKE_SYSTEM_PROCESSOR,處理器名稱,比如i686.
8,UNIX,在所有的類UNIX平臺為TRUE,包括OS X和cygwin
9,WIN32,在所有的win32平臺為TRUE,包括cygwin
六,主要的開關選項:
1,CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS,用來控制IF ELSE語句的書寫方式,在
下一節語法部分會講到。
2,BUILD_SHARED_LIBS
這個開關用來控制預設的庫編譯方式,如果不進行設定,使用ADD_LIBRARY並沒有指定庫
型別的情況下,預設編譯生成的庫都是靜態庫。
如果SET(BUILD_SHARED_LIBS ON)後,預設生成的為動態庫。
3,CMAKE_C_FLAGS
設定C編譯選項,也可以通過指令ADD_DEFINITIONS()新增。
4,CMAKE_CXX_FLAGS
設定C++編譯選項,也可以通過指令ADD_DEFINITIONS()新增。
小結:
本章介紹了一些較常用的cmake變數,這些變數僅僅是所有cmake變數的很少一部分,目
前cmake的英文文件也是比較缺乏的,如果需要了解更多的cmake變數,更好的方式是閱
讀一些成功專案的cmake工程檔案,比如KDE4的程式碼。
八,cmake常用指令
前面我們講到了cmake “ 常用的變數,相信cmake ” 即程式設計的感覺會越來越明顯,無論如何,
我們仍然可以看到cmake比autotools要簡單很多。接下來我們就要集中的看一看
cmake所提供的常用指令。在前面的章節我們已經討論了很多指令的用法,如
PROJECT,ADD_EXECUTABLE,INSTALL,ADD_SUBDIRECTORY,SUBDIRS,INCLUDE
_DIRECTORIES,LINK_DIRECTORIES,TARGET_LINK_LIBRARIES,SET等。
本節會引入更多的cmake指令,為了編寫的方便,我們將按照cmake man page的順序
來介紹各種指令,不再推薦使用的指令將不再介紹,INSTALL系列指令在安裝部分已經做
了非常詳細的說明,本節也不在提及。(你可以將本章理解成選擇性翻譯,但是會加入更多
的個人理解)
一,基本指令
1,ADD_DEFINITIONS
向C/C++編譯器新增-D定義,比如:
ADD_DEFINITIONS(-DENABLE_DEBUG  -DABC),引數之間用空格分割。
如果你的程式碼中定義了#ifdef ENABLE_DEBUG #endif,這個程式碼塊就會生效。
如果要新增其他的編譯器開關,可以通過CMAKE_C_FLAGS變數和CMAKE_CXX_FLAGS變
量設定。
2,ADD_DEPENDENCIES
定義target依賴的其他target,確保在編譯本target之前,其他的target已經被構
建。
ADD_DEPENDENCIES(target-name depend-target1
                 depend-target2 ...)
3,ADD_EXECUTABLE、ADD_LIBRARY、ADD_SUBDIRECTORY前面已經介紹過了,這裡
不再羅唆。
4,ADD_TEST與ENABLE_TESTING指令。
ENABLE_TESTING指令用來控制Makefile是否構建test目標,涉及工程所有目錄。語
法很簡單,沒有任何引數,ENABLE_TESTING(),一般情況這個指令放在工程的主
CMakeLists.txt中.
ADD_TEST指令的語法是:
ADD_TEST(testname Exename arg1 arg2 ...)
testname是自定義的test名稱,Exename可以是構建的目標檔案也可以是外部指令碼等
等。後面連線傳遞給可執行檔案的引數。如果沒有在同一個CMakeLists.txt中開啟
ENABLE_TESTING()指令,任何ADD_TEST都是無效的。
比如我們前面的Helloworld例子,可以在工程主CMakeLists.txt中新增
ADD_TEST(mytest ${PROJECT_BINARY_DIR}/bin/main)
ENABLE_TESTING()
生成Makefile後,就可以執行make test來執行測試了。
5,AUX_SOURCE_DIRECTORY
基本語法是:
AUX_SOURCE_DIRECTORY(dir VARIABLE)
作用是發現一個目錄下所有的原始碼檔案並將列表儲存在一個變數中,這個指令臨時被用來
自動構建原始檔列表。因為目前cmake還不能自動發現新新增的原始檔。
比如
AUX_SOURCE_DIRECTORY(. SRC_LIST)
ADD_EXECUTABLE(main ${SRC_LIST})
你也可以通過後面提到的FOREACH指令來處理這個LIST
6,CMAKE_MINIMUM_REQUIRED
其語法為CMAKE_MINIMUM_REQUIRED(VERSION versionNumber [FATAL_ERROR])
比如CMAKE_MINIMUM_REQUIRED(VERSION 2.5 FATAL_ERROR)
如果cmake版本小與2.5,則出現嚴重錯誤,整個過程中止。
7,EXEC_PROGRAM
在CMakeLists.txt處理過程中執行命令,並不會在生成的Makefile中執行。具體語法
為:
EXEC_PROGRAM(Executable [directory in which to run]
                 [ARGS ]
                 [OUTPUT_VARIABLE ]
                 [RETURN_VALUE ])
用於在指定的目錄執行某個程式,通過ARGS新增引數,如果要獲取輸出和返回值,可通過
OUTPUT_VARIABLE和RETURN_VALUE分別定義兩個變數.
這個指令可以幫助你在CMakeLists.txt處理過程中支援任何命令,比如根據系統情況去
修改程式碼檔案等等。
舉個簡單的例子,我們要在src目錄執行ls命令,並把結果和返回值存下來。
可以直接在src/CMakeLists.txt中新增:
EXEC_PROGRAM(ls ARGS "*.c" OUTPUT_VARIABLE LS_OUTPUT RETURN_VALUE 
LS_RVALUE)
IF(not LS_RVALUE)
MESSAGE(STATUS "ls result: " ${LS_OUTPUT})
ENDIF(not LS_RVALUE)
在cmake 生成Makefile的過程中,就會執行ls命令,如果返回0,則說明成功執行,
那麼就輸出ls *.c的結果。關於IF語句,後面的控制指令會提到。
8,FILE指令
檔案操作指令,基本語法為:
        FILE(WRITE filename "message to write"... )
        FILE(APPEND filename "message to write"... )
        FILE(READ filename variable)
        FILE(GLOB  variable [RELATIVE path] [globbing 
expressions]...)
        FILE(GLOB_RECURSE variable [RELATIVE path]
             [globbing expressions]...)
        FILE(REMOVE [directory]...)
        FILE(REMOVE_RECURSE [directory]...)
        FILE(MAKE_DIRECTORY [directory]...)
        FILE(RELATIVE_PATH variable directory file)
        FILE(TO_CMAKE_PATH path result)
        FILE(TO_NATIVE_PATH path result)
這裡的語法都比較簡單,不在展開介紹了。
9,INCLUDE指令,用來載入CMakeLists.txt檔案,也用於載入預定義的cmake模組.
        INCLUDE(file1 [OPTIONAL])
        INCLUDE(module [OPTIONAL])
OPTIONAL引數的作用是檔案不存在也不會產生錯誤。
你可以指定載入一個檔案,如果定義的是一個模組,那麼將在CMAKE_MODULE_PATH中搜
索這個模組並載入。
載入的內容將在處理到INCLUDE語句是直接執行。
二,INSTALL指令
INSTALL系列指令已經在前面的章節有非常詳細的說明,這裡不在贅述,可參考前面的安
裝部分。
三,FIND_指令
FIND_系列指令主要包含一下指令:
FIND_FILE( name1 path1 path2 ...)
VAR變數代表找到的檔案全路徑,包含檔名
FIND_LIBRARY( name1 path1 path2 ...)
VAR變量表示找到的庫全路徑,包含庫檔名
FIND_PATH( name1 path1 path2 ...)
VAR變數代表包含這個檔案的路徑。
FIND_PROGRAM( name1 path1 path2 ...)
VAR變數代表包含這個程式的全路徑。
FIND_PACKAGE( [major.minor] [QUIET] [NO_MODULE]
                 [[REQUIRED|COMPONENTS] [componets...]])
用來呼叫預定義在CMAKE_MODULE_PATH下的Find.cmake模組,你也可以自己
定義Find模組,通過SET(CMAKE_MODULE_PATH dir)將其放入工程的某個目錄
中供工程使用,我們在後面的章節會詳細介紹FIND_PACKAGE的使用方法和Find模組的
編寫。
FIND_LIBRARY示例:
FIND_LIBRARY(libX X11 /usr/lib)
IF(NOT libX)
MESSAGE(FATAL_ERROR “libX not found”)
ENDIF(NOT libX)
四,控制指令:
1,IF指令,基本語法為:
        IF(expression)
          # THEN section.
          COMMAND1(ARGS ...)
          COMMAND2(ARGS ...)
          ...
        ELSE(expression)
          # ELSE section.
          COMMAND1(ARGS ...)
          COMMAND2(ARGS ...)
          ...
        ENDIF(expression)
另外一個指令是ELSEIF,總體把握一個原則,凡是出現IF的地方一定要有對應的
ENDIF.出現ELSEIF的地方,ENDIF是可選的。
表示式的使用方法如下:
IF(var),如果變數不是:空,0,N, NO, OFF, FALSE, NOTFOUND或
_NOTFOUND時,表示式為真。
IF(NOT var ),與上述條件相反。
IF(var1 AND var2),當兩個變數都為真是為真。
IF(var1 OR var2),當兩個變數其中一個為真時為真。
IF(COMMAND cmd),當給定的cmd確實是命令並可以呼叫是為真。
IF(EXISTS dir)或者IF(EXISTS file),當目錄名或者檔名存在時為真。
IF(file1  IS_NEWER_THAN file2),當file1比file2新,或者file1/file2其
中有一個不存在時為真,檔名請使用完整路徑。
IF(IS_DIRECTORY dirname),當dirname是目錄時,為真。
IF(variable MATCHES regex)
IF(string MATCHES regex)
當給定的變數或者字串能夠匹配正則表示式regex時為真。比如:
IF("hello" MATCHES "ell")
MESSAGE("true")
ENDIF("hello" MATCHES "ell")
IF(variable LESS number)
IF(string LESS number)
IF(variable GREATER number)
IF(string GREATER number)
IF(variable EQUAL number)
IF(string EQUAL number)
數字比較表示式
IF(variable STRLESS string)
IF(string STRLESS string)
IF(variable STRGREATER string)
IF(string STRGREATER string)
IF(variable STREQUAL string)
IF(string STREQUAL string)
按照字母序的排列進行比較.
IF(DEFINED variable),如果變數被定義,為真。
一個小例子,用來判斷平臺差異:
IF(WIN32)
MESSAGE(STATUS “This is windows.”)
#作一些Windows相關的操作
ELSE(WIN32)
MESSAGE(STATUS “This is not windows”)
#作一些非Windows相關的操作
ENDIF(WIN32)
上述程式碼用來控制在不同的平臺進行不同的控制,但是,閱讀起來卻並不是那麼舒服,
ELSE(WIN32)之類的語句很容易引起歧義。
“ ”   這就用到了我們在常用變數一節提到的CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS開
關。
可以SET(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS ON)
這時候就可以寫成:
IF(WIN32)
ELSE()
ENDIF()
如果配合ELSEIF使用,可能的寫法是這樣:
IF(WIN32)
#do something related to WIN32
ELSEIF(UNIX)
#do something related to UNIX
ELSEIF(APPLE)
#do something related to APPLE
ENDIF(WIN32)
2,WHILE
WHILE指令的語法是:
        WHILE(condition)
          COMMAND1(ARGS ...)
          COMMAND2(ARGS ...)
          ...
        ENDWHILE(condition)
其真假判斷條件可以參考IF指令。
3,FOREACH
FOREACH指令的使用方法有三種形式:
1,列表
        FOREACH(loop_var arg1 arg2 ...)
          COMMAND1(ARGS ...)
          COMMAND2(ARGS ...)
          ...
        ENDFOREACH(loop_var)
像我們前面使用的AUX_SOURCE_DIRECTORY的例子
AUX_SOURCE_DIRECTORY(. SRC_LIST)
FOREACH(F ${SRC_LIST})
MESSAGE(${F})
ENDFOREACH(F)
2,範圍
FOREACH(loop_var RANGE total)
ENDFOREACH(loop_var)
從0到total以1為步進
舉例如下:
FOREACH(VAR RANGE 10)
MESSAGE(${VAR})
ENDFOREACH(VAR)
最終得到的輸出是:
0
1
2
3
4<