小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

在linux下使用CMake構(gòu)建應(yīng)用程序

 娛天樂 2014-07-24
    Cmake實(shí)踐
Cmake Practice --Cjacker
前言:
    cmake已經(jīng)開發(fā)了5,6年的時間,如果沒有KDE4,也許不會有人或者Linux發(fā)行版
本重視cmake,因?yàn)槌薑itware似乎沒有人使用它。通過KDE4的選型和開發(fā),cmake
逐漸進(jìn)入了人們的視線,在實(shí)際的使用過程中,cmake的優(yōu)勢也逐漸的被大家所認(rèn)識,至
少KDE的開發(fā)者們給予了cmake極高的評價,同時龐大的KDE項(xiàng)目使用cmake來作為構(gòu)
建工具也證明了cmake的可用性和大項(xiàng)目管理能力。
    所以,cmake應(yīng)該感謝KDE,也正因?yàn)槿绱?,cmake的開發(fā)者投入了KDE從
autotools到cmake的遷移過程中,并相當(dāng)快速和順利的完成了遷移,現(xiàn)在整個KDE4開
發(fā)版本全部使用cmake構(gòu)建。
    這也是促使我們學(xué)習(xí)cmake的原因,首先cmake被接受并成功應(yīng)用,其次,cmake
的優(yōu)勢在實(shí)際使用中不斷的體現(xiàn)出來。
    我們?yōu)槭裁床粊碚J(rèn)識一下這款優(yōu)秀的工程構(gòu)建工具呢?
    在2006年KDE大會,聽cmake開發(fā)者當(dāng)面介紹了cmake之后,我就開始關(guān)注
cmake,并將cmake納入了Everest發(fā)行版,作為系統(tǒng)默認(rèn)組件。最近QT-4.3也正式進(jìn)
入了Everest系統(tǒng),為KDE4構(gòu)建完成了準(zhǔn)備工作。
    但是,在學(xué)習(xí)cmake的過程中,發(fā)現(xiàn)官方的文檔非常的少,而且錯誤也較多,比如:
在介紹Find<Name>模塊編寫的文檔中,模塊名稱為FOO,但是后面卻出現(xiàn)了
Foo_FIND_QUIETLY的定義,這顯然是錯誤的,這樣的定義永遠(yuǎn)不可能有效,正確的定義
是FOO_FIND_QUIETLY “ ” 。種種原因,促使我開始寫一份面向使用和實(shí)用的cmake文檔,
也就是本教程《cmake實(shí)踐》(Cmake Practice)
    本文檔是邊學(xué)習(xí)邊編寫的成果,更像是一個學(xué)習(xí)筆記和Tutorial,因此難免有失誤
或者理解不夠透徹的地方,比如,我仍然不能理解為什么絕大部分使用變量的情況要通過$
{}引用,而在IF語句中卻必須直接使用變量名。也希望能夠有cmake的高手來指點(diǎn)迷津。
補(bǔ):從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需要的是變量名而不是變量值
    這個文檔是開放的,開放的目的是為了讓更多的人能夠讀到并且能夠修改,任何人都
可以對它作出修改和補(bǔ)充,但是,為了大家都能夠獲得你關(guān)于cmake的經(jīng)驗(yàn)和積累,如果
你現(xiàn)錯誤或者添加了新內(nèi)容后,請務(wù)必CC給我一份,讓我們共同把cmake掌握的更好。
一,初識cmake
Cmake不再使你在構(gòu)建項(xiàng)目時郁悶地想自殺了.
--一位KDE開發(fā)者
1,背景知識:
    cmake是kitware公司以及一些開源開發(fā)者在開發(fā)幾個工具套件(VTK)的過程中衍
生品,最終形成體系,成為一個獨(dú)立的開放源代碼項(xiàng)目。項(xiàng)目的誕生時間是2001年。其官
方網(wǎng)站是www.,可以通過訪問官方網(wǎng)站獲得更多關(guān)于cmake的信息。cmake
的流行其實(shí)要?dú)w功于KDE4的開發(fā)(似乎跟當(dāng)年的svn一樣,KDE將代碼倉庫從CVS遷移到
SVN,同時證明了SVN管理大型項(xiàng)目的可用性),在KDE開發(fā)者使用了近10年autotools
之后,他們終于決定為KDE4選擇一個新的工程構(gòu)建工具,其根本原因用KDE開發(fā)者的話來
“ ” 說就是:只有少數(shù)幾個編譯專家能夠掌握KDE現(xiàn)在的構(gòu)建體系
(admin/Makefile.common),在經(jīng)歷了unsermake, scons以及cmake的選型和嘗
試之后,KDE4決定使用cmake作為自己的構(gòu)建系統(tǒng)。在遷移過程中,進(jìn)展異常的順利,并
獲得了cmake開發(fā)者的支持。所以,目前的KDE4開發(fā)版本已經(jīng)完全使用cmake來進(jìn)行構(gòu)
建。像kdesvn,rosegarden等項(xiàng)目也開始使用cmake,這也注定了cmake必然會成為
一個主流的構(gòu)建體系。
2,特點(diǎn):
cmake的特點(diǎn)主要有:
1,開放源代碼,使用類BSD許可發(fā)布。http:///HTML/Copyright.html
2,跨平臺,并可生成native編譯配置文件,在Linux/Unix平臺,生成makefile,在
蘋果平臺,可以生成xcode,在Windows平臺,可以生成MSVC的工程文件。
3,能夠管理大型項(xiàng)目,KDE4就是最好的證明。
4,簡化編譯構(gòu)建過程和編譯過程。Cmake的工具鏈非常簡單:cmake+make。
5,高效慮,按照KDE官方說法,CMake構(gòu)建KDE4的kdelibs要比使用autotools來
構(gòu)建KDE3.5.6的kdelibs快40%   ,主要是因?yàn)镃make在工具鏈中沒有l(wèi)ibtool。
6,可擴(kuò)展,可以為cmake編寫特定功能的模塊,擴(kuò)充cmake功能。
3,問題,難道就沒有問題?
1,cmake很簡單,但絕對沒有聽起來或者想象中那么簡單。
2,cmake編寫的過程實(shí)際上是編程的過程,跟以前使用autotools一樣,不過你需要編
寫的是CMakeLists.txt(每個目錄一個) ” ,使用的是cmake ” 語言和語法。
3,cmake跟已有體系的配合并不是特別理想,比如pkgconfig,您在實(shí)際使用中會有所
體會,雖然有一些擴(kuò)展可以使用,但并不理想。
4,個人的建議:
1,如果你沒有實(shí)際的項(xiàng)目需求,那么看到這里就可以停下來了,因?yàn)閏make的學(xué)習(xí)過程就
是實(shí)踐過程,沒有實(shí)踐,讀的再多幾天后也會忘記。
2,如果你的工程只有幾個文件,直接編寫Makefile是最好的選擇。
3,如果使用的是C/C++/Java之外的語言,請不要使用cmake(至少目前是這樣)
4,如果你使用的語言有非常完備的構(gòu)建體系,比如java的ant,也不需要學(xué)習(xí)cmake,
雖然有成功的例子,比如QT4.3的csharp綁定qyoto。
5,如果項(xiàng)目已經(jīng)采用了非常完備的工程管理工具,并且不存在維護(hù)問題,沒有必要遷移到
cmake
4,如果僅僅使用qt編程,沒有必要使用cmake,因?yàn)閝make管理Qt工程的專業(yè)性和自
動化程度比cmake要高很多。
二,安裝cmake
還需要安裝嗎?
cmake目前已經(jīng)成為各大Linux發(fā)行版提供的組件,比如Everest直接在系統(tǒng)中包含,
Fedora在extra倉庫中提供,所以,需要自己動手安裝的可能性很小。如果你使用的操
作系統(tǒng)(比如Windows或者某些Linux版本)沒有提供cmake或者包含的版本較舊,建議
你直接從cmake官方網(wǎng)站下載安裝。
http://www./HTML/Download.html
在這個頁面,提供了源代碼的下載以及針對各種不同操作系統(tǒng)的二進(jìn)制下載,可以選擇適合
自己操作系統(tǒng)的版本下載安裝。因?yàn)楦鱾€系統(tǒng)的安裝方式和包管理格式有所不同,在此就不
再贅述了,相信一定能夠順利安裝cmake。
三,初試cmake – cmake的helloworld
Hello world     ,世界你好
本節(jié)選擇了一個最簡單的例子Helloworld來演練一下cmake的完整構(gòu)建過程,本節(jié)并不
會深入的探討cmake,僅僅展示一個簡單的例子,并加以粗略的解釋。我們選擇了
Everest Linux作為基本開發(fā)平臺,因?yàn)檫@個只有一張CD的發(fā)行版本,包含了gcc-4.2/gtk/qt3/qt4等完整的開發(fā)環(huán)境,同時,系統(tǒng)默認(rèn)集成了cmake最新版本2.4.6。
1,準(zhǔn)備工作:
首先,在/backup目錄建立一個cmake目錄,用來放置我們學(xué)習(xí)過程中的所有練習(xí)。
mkdir -p /backup/cmake
以后我們所有的cmake練習(xí)都會放在/backup/cmake的子目錄下(你也可以自行安排目錄,
這個并不是限制,僅僅是為了敘述的方便)
然后在cmake建立第一個練習(xí)目錄t1
cd /backup/cmake
mkdir t1
cd t1
在t1目錄建立main.c和CMakeLists.txt(注意文件名大小寫):
main.c文件內(nèi)容:
//main.c
#include <stdio.h>
int main()
{
printf(“Hello World from t1 Main!\n”);
return 0;
}
CmakeLists.txt文件內(nèi)容:
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,開始構(gòu)建
所有的文件創(chuàng)建完成后,t1目錄中應(yīng)該存在main.c和CMakeLists.txt兩個文件
接下來我們來構(gòu)建這個工程,在這個目錄運(yùn)行:
cmake . (注意命令后面的點(diǎn)號,代表本目錄)。
輸出大概是這個樣子:
-- 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
再讓我們看一下目錄中的內(nèi)容:
你會發(fā)現(xiàn),系統(tǒng)自動生成了:
CMakeFiles, CMakeCache.txt, cmake_install.cmake等文件,并且生成了
Makefile.
現(xiàn)在不需要理會這些文件的作用,以后你也可以不去理會。最關(guān)鍵的是,它自動生成了
Makefile.
然后進(jìn)行工程的實(shí)際構(gòu)建,在這個目錄輸入make命令,大概會得到如下的彩色輸出:
Scanning dependencies of target hello
[100%] Building C object CMakeFiles/hello.dir/main.o
Linking C executable hello
[100%] Built target hello
如果你需要看到make構(gòu)建的詳細(xì)過程,可以使用make VERBOSE=1或者VERBOSE=1
make命令來進(jìn)行構(gòu)建。
這時候,我們需要的目標(biāo)文件hello已經(jīng)構(gòu)建完成,位于當(dāng)前目錄,嘗試運(yùn)行一下:
./hello
得到輸出:
Hello World from Main
恭喜您,到這里為止您已經(jīng)完全掌握了cmake的使用方法。
3,簡單的解釋:
    我們來重新看一下CMakeLists.txt,這個文件是cmake的構(gòu)建定義文件,文件名
是大小寫相關(guān)的,如果工程存在多個目錄,需要確保每個要管理的目錄都存在一個
CMakeLists.txt。(關(guān)于多目錄構(gòu)建,后面我們會提到,這里不作過多解釋)。
上面例子中的CMakeLists.txt文件內(nèi)容如下:
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])
你可以用這個指令定義工程名稱,并可指定工程支持的語言,支持的語言列表是可以忽略的,
默認(rèn)情況表示支持所有語言。這個指令隱式的定義了兩個cmake變量:
<projectname>_BINARY_DIR以及<projectname>_SOURCE_DIR,這里就是
HELLO_BINARY_DIR和HELLO_SOURCE_DIR(所以CMakeLists.txt中兩個MESSAGE
指令可以直接使用了這兩個變量),因?yàn)椴捎玫氖莾?nèi)部編譯,兩個變量目前指的都是工程所
在路徑/backup/cmake/t1,后面我們會講到外部編譯,兩者所指代的內(nèi)容會有所不同。
同時cmake系統(tǒng)也幫助我們預(yù)定義了PROJECT_BINARY_DIR和PROJECT_SOURCE_DIR
變量,他們的值分別跟HELLO_BINARY_DIR與HELLO_SOURCE_DIR一致。
為了統(tǒng)一起見,建議以后直接使用PROJECT_BINARY_DIR,PROJECT_SOURCE_DIR,即
使修改了工程名稱,也不會影響這兩個變量。如果使用了
<projectname>_SOURCE_DIR   ,修改工程名稱后,需要同時修改這些變量。
SET指令的語法是:
SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])
現(xiàn)階段,你只需要了解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,產(chǎn)生錯誤,生成過程被跳過。
SATUS — ,輸出前綴為的信息。
FATAL_ERROR,立即終止所有cmake過程.
我們在這里使用的是STATUS信息輸出,演示了由PROJECT指令定義的兩個隱式變量
HELLO_BINARY_DIR和HELLO_SOURCE_DIR。
ADD_EXECUTABLE(hello ${SRC_LIST})
定義了這個工程會生成一個文件名為hello的可執(zhí)行文件,相關(guān)的源文件是SRC_LIST中
  定義的源文件列表,本例中你也可以直接寫成ADD_EXECUTABLE(hello main.c)。
在本例我們使用了${}來引用變量,這是cmake的變量應(yīng)用方式,但是,有一些例外,比
如在IF控制語句,變量是直接使用變量名引用,而不需要${}。如果使用了${}去應(yīng)用變
量,其實(shí)IF會去判斷名為${}所代表的值的變量,那當(dāng)然是不存在的了。
將本例改寫成一個最簡化的CMakeLists.txt:
PROJECT(HELLO)
ADD_EXECUTABLE(hello main.c)
4,基本語法規(guī)則
前面提到過,cmake ” 其實(shí)仍然要使用cmake ” 語言和語法去構(gòu)建,上面的內(nèi)容就是所謂的
”cmake ” 語言和語法,最簡單的語法規(guī)則是:
1,變量使用${}方式取值,但是在IF控制語句中是直接使用變量名
2,指令(參數(shù)1 參數(shù)2...)
參數(shù)使用括弧括起,參數(shù)之間使用空格或分號分開。
以上面的ADD_EXECUTABLE指令為例,如果存在另外一個func.c源文件,就要寫成:
ADD_EXECUTABLE(hello main.c func.c)或者
ADD_EXECUTABLE(hello main.c;func.c)
3,指令是大小寫無關(guān)的,參數(shù)和變量是大小寫相關(guān)的。但,推薦你全部使用大寫指令。
上面的MESSAGE指令我們已經(jīng)用到了這條規(guī)則:
MESSAGE(STATUS “This is BINARY dir” ${HELLO_BINARY_DIR})
也可以寫成:
MESSAGE(STATUS “This is BINARY dir ${HELLO_BINARY_DIR}”)
這里需要特別解釋的是作為工程名的HELLO和生成的可執(zhí)行文件hello是沒有任何關(guān)系的。
hello定義了可執(zhí)行文件的文件名,你完全可以寫成:
ADD_EXECUTABLE(t1 main.c)
編譯后會生成一個t1可執(zhí)行文件。
5,關(guān)于語法的疑惑
cmake的語法還是比較靈活而且考慮到各種情況,比如
SET(SRC_LIST main.c)也可以寫成SET(SRC_LIST “main.c”)
是沒有區(qū)別的,但是假設(shè)一個源文件的文件名是fu nc.c(文件名中間包含了空格)。
這時候就必須使用雙引號,如果寫成了SET(SRC_LIST fu nc.c),就會出現(xiàn)錯誤,提示
你找不到fu文件和nc.c文件。這種情況,就必須寫成:
SET(SRC_LIST “fu nc.c”)
此外,你可以可以忽略掉source列表中的源文件后綴,比如可以寫成
ADD_EXECUTABLE(t1 main),cmake會自動的在本目錄查找main.c或者main.cpp
等,當(dāng)然,最好不要偷這個懶,以免這個目錄確實(shí)存在一個main.c一個main.
同時參數(shù)也可以使用分號來進(jìn)行分割。
下面的例子也是合法的:
ADD_EXECUTABLE(t1 main.c t1.c)可以寫成ADD_EXECUTABLE(t1
main.c;t1.c).
我們只需要在編寫CMakeLists.txt時注意形成統(tǒng)一的風(fēng)格即可。
6,清理工程:
跟經(jīng)典的autotools系列工具一樣,運(yùn)行:
make clean
即可對構(gòu)建結(jié)果進(jìn)行清理。
7,問題?問題!
“我嘗試運(yùn)行了make distclean,這個指令一般用來清理構(gòu)建過程中產(chǎn)生的中間文件的,
如果要發(fā)布代碼,必然要清理掉所有的中間文件,但是為什么在cmake工程中這個命令是
” 無效的?
是的,cmake并不支持make distclean,關(guān)于這一點(diǎn),官方是有明確解釋的:
因?yàn)镃MakeLists.txt可以執(zhí)行腳本并通過腳本生成一些臨時文件,但是卻沒有辦法來跟
蹤這些臨時文件到底是哪些。因此,沒有辦法提供一個可靠的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.
同時,還有另外一個非常重要的提示,就是:我們剛才進(jìn)行的是內(nèi)部構(gòu)建(in-source
build),而cmake強(qiáng)烈推薦的是外部構(gòu)建(out-of-source build)。
8,內(nèi)部構(gòu)建與外部構(gòu)建:
“ ” 上面的例子展示的是內(nèi)部構(gòu)建,相信看到生成的臨時文件比您的代碼文件還要多的時候,
估計這輩子你都不希望再使用內(nèi)部構(gòu)建:-D
舉個簡單的例子來說明外部構(gòu)建,以編譯wxGTK動態(tài)庫和靜態(tài)庫為例,在Everest中打包
方式是這樣的:
解開wxGTK后。
在其中建立static和shared目錄。
進(jìn)入static目錄,運(yùn)行../configure –enable-static;make會在static目錄生
成wxGTK的靜態(tài)庫。
進(jìn)入shared目錄,運(yùn)行../configure –enable-shared;make就會在shared目錄
生成動態(tài)庫。
這就是外部編譯的一個簡單例子。
對于cmake,內(nèi)部編譯上面已經(jīng)演示過了,它生成了一些無法自動刪除的中間文件,所以,
引出了我們對外部編譯的探討,外部編譯的過程如下:
1,首先,請清除t1目錄中除main.c CmakeLists.txt之外的所有中間文件,最關(guān)鍵
的是CMakeCache.txt。
2,在t1目錄中建立build 目錄,當(dāng)然你也可以在任何地方建立build目錄,不一定必
須在工程目錄中。
3,進(jìn)入build目錄,運(yùn)行cmake ..(注意,..代表父目錄,因?yàn)楦改夸洿嬖谖覀冃枰?br> CMakeLists.txt,如果你在其他地方建立了build目錄,需要運(yùn)行cmake <工程的全
路徑>),查看一下build目錄,就會發(fā)現(xiàn)了生成了編譯需要的Makefile以及其他的中間
文件.
4,運(yùn)行make構(gòu)建工程,就會在當(dāng)前目錄(build目錄)中獲得目標(biāo)文件hello。
上述過程就是所謂的out-of-source外部編譯,一個最大的好處是,對于原有的工程沒
有任何影響,所有動作全部發(fā)生在編譯目錄。通過這一點(diǎn),也足以說服我們?nèi)坎捎猛獠烤?br> 譯方式構(gòu)建工程。
這里需要特別注意的是:
通過外部編譯進(jìn)行工程構(gòu)建,HELLO_SOURCE_DIR仍然指代工程路徑,即
/backup/cmake/t1
而HELLO_BINARY_DIR則指代編譯路徑,即/backup/cmake/t1/build
9,小結(jié):
本小節(jié)描述了使用cmake構(gòu)建Hello World程序的全部過程,并介紹了三個簡單的指令:
PROJECT/MESSAGE/ADD_EXECUTABLE以及變量調(diào)用的方法,同時提及了兩個隱式變量
<projectname>_SOURCE_DIR及<projectname>_BINARY_DIR,演示了變量調(diào)用的方
法,從這個過程來看,有些開發(fā)者可能會想,這實(shí)在比我直接寫Makefile要復(fù)雜多了,
甚至我都可以不編寫Makefile,直接使用gcc main.c即可生成需要的目標(biāo)文件。是的,
正如第一節(jié)提到的,如果工程只有幾個文件,還是直接編寫Makefile最簡單。但是,
kdelibs壓縮包達(dá)到了50多M,您認(rèn)為使用什么方案會更容易一點(diǎn)呢?
下一節(jié),我們的任務(wù)是讓HelloWorld看起來更像一個工程。
四,更好一點(diǎn)的Hello World
沒有最好,只有更好
從本小節(jié)開始,后面所有的構(gòu)建我們都將采用out-of-source外部構(gòu)建,約定的構(gòu)建目
錄是工程目錄下的build自錄。
本小節(jié)的任務(wù)是讓前面的Hello World更像一個工程,我們需要作的是:
1,為工程添加一個子目錄src,用來放置工程源代碼;
2,添加一個子目錄doc,用來放置這個工程的文檔hello.txt
3,在工程目錄添加文本文件COPYRIGHT, README;
4,在工程目錄添加一個runhello.sh腳本,用來調(diào)用hello二進(jìn)制
4,將構(gòu)建后的目標(biāo)文件放入構(gòu)建目錄的bin子目錄;
5,最終安裝這些文件:將hello二進(jìn)制與runhello.sh安裝至/usr/bin,將doc目錄
的內(nèi)容以及COPYRIGHT/README安裝到/usr/share/doc/cmake/t2,將
1,準(zhǔn)備工作:
在/backup/cmake/目錄下建立t2目錄。
將t1工程的main.c和CMakeLists.txt拷貝到t2目錄中。
2,添加子目錄src:
mkdir src
mv main.c src
現(xiàn)在的工程看起來是這個樣子:
一個子目錄src,一個CMakeLists.txt。
上一節(jié)我們提到,需要為任何子目錄建立一個CMakeLists.txt,
進(jìn)入子目錄src,編寫CMakeLists.txt如下:
ADD_EXECUTABLE(hello main.c)
將t2工程的CMakeLists.txt修改為:
PROJECT(HELLO)
ADD_SUBDIRECTORY(src bin)
然后建立build目錄,進(jìn)入build目錄進(jìn)行外部編譯。
cmake  ..
make
構(gòu)建完成后,你會發(fā)現(xiàn)生成的目標(biāo)文件hello位于build/bin目錄中。
語法解釋:
ADD_SUBDIRECTORY指令
ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
這個指令用于向當(dāng)前工程添加存放源文件的子目錄,并可以指定中間二進(jìn)制和目標(biāo)二進(jìn)制存
放的位置。EXCLUDE_FROM_ALL參數(shù)的含義是將這個目錄從編譯過程中排除,比如,工程
的example,可能就需要工程構(gòu)建完成后,再進(jìn)入example目錄單獨(dú)進(jìn)行構(gòu)建(當(dāng)然,你
也可以通過定義依賴來解決此類問題)。
上面的例子定義了將src子目錄加入工程,并指定編譯輸出(包含編譯中間結(jié)果)路徑為
bin目錄。如果不進(jìn)行bin目錄的指定,那么編譯結(jié)果(包括中間結(jié)果)都將存放在
build/src目錄(這個目錄跟原有的src目錄對應(yīng)),指定bin目錄后,相當(dāng)于在編譯時
將src重命名為bin,所有的中間結(jié)果和目標(biāo)二進(jìn)制都將存放在bin目錄。
這里需要提一下的是SUBDIRS指令,使用方法是:
SUBDIRS(dir1 dir2...),但是這個指令已經(jīng)不推薦使用。它可以一次添加多個子目錄,
并且,即使外部編譯,子目錄體系仍然會被保存。
如果我們在上面的例子中將ADD_SUBDIRECTORY (src bin)修改為SUBDIRS(src)。
那么在build目錄中將出現(xiàn)一個src目錄,生成的目標(biāo)代碼hello將存放在src目錄中。
3,換個地方保存目標(biāo)二進(jìn)制
不論是SUBDIRS還是ADD_SUBDIRECTORY指令(不論是否指定編譯輸出目錄),我們都可
以通過SET指令重新定義EXECUTABLE_OUTPUT_PATH和LIBRARY_OUTPUT_PATH變量
來指定最終的目標(biāo)二進(jìn)制的位置(指最終生成的hello或者最終的共享庫,不包含編譯生成
的中間文件)
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
在第一節(jié)我們提到了<projectname>_BINARY_DIR和PROJECT_BINARY_DIR變量,他
們指的編譯發(fā)生的當(dāng)前目錄,如果是內(nèi)部編譯,就相當(dāng)于PROJECT_SOURCE_DIR也就是
工程代碼所在目錄,如果是外部編譯,指的是外部編譯所在目錄,也就是本例中的build
目錄。
所以,上面兩個指令分別定義了:
可執(zhí)行二進(jìn)制的輸出路徑為build/bin和庫的輸出路徑為build/lib.
本節(jié)我們沒有提到共享庫和靜態(tài)庫的構(gòu)建,所以,你可以不考慮第二條指令。
問題是,我應(yīng)該把這兩條指令寫在工程的CMakeLists.txt還是src目錄下的
CMakeLists.txt,把握一個簡單的原則,在哪里ADD_EXECUTABLE或ADD_LIBRARY,
如果需要改變目標(biāo)存放路徑,就在哪里加入上述的定義。
在這個例子里,當(dāng)然就是指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目錄,打包時這個方式經(jīng)常被使用。
稍微復(fù)雜一點(diǎn)的是還需要定義PREFIX,一般autotools工程,會運(yùn)行這樣的指令:
./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應(yīng)該怎么進(jìn)行安裝呢?
這里需要引入一個新的cmake    指令I(lǐng)NSTALL和一個非常有用的變量
CMAKE_INSTALL_PREFIX。
CMAKE_INSTALL_PREFIX變量類似于configure  – 腳本的 prefix,常見的使用方法看
起來是這個樣子:
cmake -DCMAKE_INSTALL_PREFIX=/usr .
INSTALL指令用于定義安裝規(guī)則,安裝的內(nèi)容可以包括目標(biāo)二進(jìn)制、動態(tài)庫、靜態(tài)庫以及
文件、目錄、腳本等。
INSTALL指令包含了各種安裝類型,我們需要一個個分開解釋:
目標(biāo)文件的安裝:
        INSTALL(TARGETS targets...
            [[ARCHIVE|LIBRARY|RUNTIME]
                        [DESTINATION <dir>]
                        [PERMISSIONS permissions...]
                        [CONFIGURATIONS
          [Debug|Release|...]]
                        [COMPONENT <component>]
                        [OPTIONAL]
                       ] [...])
參數(shù)中的TARGETS后面跟的就是我們通過ADD_EXECUTABLE或者ADD_LIBRARY定義的
目標(biāo)文件,可能是可執(zhí)行二進(jìn)制、動態(tài)庫、靜態(tài)庫。
目標(biāo)類型也就相對應(yīng)的有三種,ARCHIVE特指靜態(tài)庫,LIBRARY特指動態(tài)庫,RUNTIME
特指可執(zhí)行目標(biāo)二進(jìn)制。
DESTINATION定義了安裝的路徑,如果路徑以/開頭,那么指的是絕對路徑,這時候
CMAKE_INSTALL_PREFIX其實(shí)就無效了。如果你希望使用CMAKE_INSTALL_PREFIX來
定義安裝路徑,就要寫成相對路徑,即不要以/開頭,那么安裝后的路徑就是
${CMAKE_INSTALL_PREFIX}/<DESTINATION定義的路徑>
舉個簡單的例子:
INSTALL(TARGETS myrun mylib mystaticlib
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION libstatic
)
上面的例子會將:
可執(zhí)行二進(jìn)制myrun安裝到${CMAKE_INSTALL_PREFIX}/bin目錄
動態(tài)庫libmylib安裝到${CMAKE_INSTALL_PREFIX}/lib目錄
靜態(tài)庫libmystaticlib安裝到${CMAKE_INSTALL_PREFIX}/libstatic目錄
特別注意的是你不需要關(guān)心TARGETS具體生成的路徑,只需要寫上TARGETS名稱就可以
了。
普通文件的安裝:
        INSTALL(FILES files... DESTINATION <dir>
            [PERMISSIONS permissions...]
            [CONFIGURATIONS [Debug|Release|...]]
            [COMPONENT <component>]
            [RENAME <name>] [OPTIONAL])
可用于安裝一般文件,并可以指定訪問權(quán)限,文件名是此指令所在路徑下的相對路徑。如果
默認(rèn)不定義權(quán)限PERMISSIONS,安裝后的權(quán)限為:
OWNER_WRITE, OWNER_READ,  GROUP_READ,和WORLD_READ,即644權(quán)限。
非目標(biāo)文件的可執(zhí)行程序安裝(比如腳本之類):
        INSTALL(PROGRAMS files... DESTINATION <dir>
            [PERMISSIONS permissions...]
            [CONFIGURATIONS [Debug|Release|...]]
            [COMPONENT <component>]
            [RENAME <name>] [OPTIONAL])
跟上面的FILES指令使用方法一樣,唯一的不同是安裝后權(quán)限為:
OWNER_EXECUTE, GROUP_EXECUTE, 和WORLD_EXECUTE,即755權(quán)限
目錄的安裝:
        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、PATTERN以及PERMISSIONS參數(shù)。
DIRECTORY后面連接的是所在Source目錄的相對路徑,但務(wù)必注意:
abc和abc/有很大的區(qū)別。
如果目錄名不以/結(jié)尾,那么這個目錄將被安裝為目標(biāo)路徑下的abc,如果目錄名以/結(jié)尾,
代表將這個目錄中的內(nèi)容安裝到目標(biāo)路徑,但不包括這個目錄本身。
PATTERN用于使用正則表達(dá)式進(jìn)行過濾,PERMISSIONS用于指定PATTERN過濾后的文件
權(quán)限。
我們來看一個例子:
        INSTALL(DIRECTORY icons scripts/ DESTINATION share/myproj
            PATTERN "CVS" EXCLUDE
            PATTERN "scripts/*"
            PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ
                    GROUP_EXECUTE GROUP_READ)
這條指令的執(zhí)行結(jié)果是:
將icons   目錄安裝到<prefix>/share/myproj,將scripts/中的內(nèi)容安裝到
<prefix>/share/myproj
不包含目錄名為CVS的目錄,對于scripts/*   文件指定權(quán)限為OWNER_EXECUTE
OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ.
安裝時CMAKE腳本的執(zhí)行:
INSTALL([[SCRIPT <file>] [CODE <code>]] [...])
SCRIPT參數(shù)用于在安裝時調(diào)用cmake腳本文件(也就是<abc>.cmake文件)
CODE參數(shù)用于執(zhí)行CMAKE指令,必須以雙引號括起來。比如:
INSTALL(CODE "MESSAGE(\"Sample install message.\")")
安裝還有幾個被標(biāo)記為過時的指令,比如INSTALL_FILES等,這些指令已經(jīng)不再推薦使
用,所以,這里就不再贅述了。
下面,我們就來改寫我們的工程文件,讓他來支持各種文件的安裝,并且,我們要使用
CMAKE_INSTALL_PREFIX指令。
5,修改Helloworld支持安裝
在本節(jié)開頭我們定義了本節(jié)的任務(wù)如下:
1,為工程添加一個子目錄src,用來存儲源代碼;
2,添加一個子目錄doc,用來存儲這個工程的文檔hello.txt
3,在工程目錄添加文本文件COPYRIGHT, README;
4,在工程目錄添加一個runhello.sh腳本,用來調(diào)用hello二進(jìn)制
4,將構(gòu)建后的目標(biāo)文件放入構(gòu)建目錄的bin子目錄;
5,最終安裝這些文件:將hello二進(jìn)制與runhello.sh安裝至/<prefix>/bin,將
doc目錄中的hello.txt以及COPYRIGHT/README安裝到
/<prefix>/share/doc/cmake/t2,將
首先我們先補(bǔ)上為添加的文件。
添加doc目錄及文件:
 cd /backup/cmake/t2
 mkdir doc
 vi doc/hello.txt
隨便填寫一些內(nèi)容并保存
在工程目錄添加runhello.sh腳本,內(nèi)容為:
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來完成),前者比較簡單,各位可以根據(jù)興趣自己完成,我們來嘗試
后者,順便演示以下DIRECTORY的安裝。
因?yàn)閔ello.txt要安裝到/<prefix>/share/doc/cmake/t2,所以我們不能直接安裝
整個doc目錄,這里采用的方式是安裝doc ” 目錄中的內(nèi)容,也就是使用doc/”
在工程文件中添加
INSTALL(DIRECTORY doc/ DESTINATION share/doc/cmake/t2)
6,嘗試我們修改的結(jié)果:
現(xiàn)在進(jìn)入build目錄進(jìn)行外部編譯,注意使用CMAKE_INSTALL_PREFIX參數(shù),這里我們
將它安裝到了/tmp/t2目錄:
cmake -DCMAKE_INSTALL_PREFIX=/tmp/t2/usr ..
然后運(yùn)行
make
make install
讓我們進(jìn)入/tmp/t2目錄看一下安裝結(jié)果:
./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
如果你要直接安裝到系統(tǒng),可以使用如下指令:
cmake -DCMAKE_INSTALL_PREFIX=/usr ..
7,一個疑問
如果我沒有定義CMAKE_INSTALL_PREFIX會安裝到什么地方?
你可以嘗試以下,cmake ..;make;make install,你會發(fā)現(xiàn)
CMAKE_INSTALL_PREFIX的默認(rèn)定義是/usr/local
8,小結(jié):
本小節(jié)主要描述了如何在工程中使用多目錄、各種安裝指令以及
CMAKE_INSTALL_PREFIX變量(你真夠牛的,這么點(diǎn)東西居然羅唆了這么多文字)
在下一小節(jié),我們將探討如何在cmake中構(gòu)建動態(tài)庫和靜態(tài)庫,以及如何使用外部頭文件
和外部共享庫,畢竟,這是程序編寫中最長使用的(對了,你知道用怎樣的gcc參數(shù)可以
直接構(gòu)建靜態(tài)庫和動態(tài)庫嗎?)
五,靜態(tài)庫與動態(tài)庫構(gòu)建
讀者云,太能羅唆了,一個Hello World就折騰了兩個大節(jié)。OK,從本節(jié)開始,我們不
再折騰Hello World了,我們來折騰Hello World的共享庫。
本節(jié)的任務(wù):
1,建立一個靜態(tài)庫和動態(tài)庫,提供HelloFunc函數(shù)供其他程序編程使用,HelloFunc
向終端輸出Hello World字符串。
2,安裝頭文件與共享庫。
一,準(zhǔn)備工作:
在/backup/cmake目錄建立t3目錄,用于存放本節(jié)涉及到的工程
二,建立共享庫
cd /backup/cmake/t3
mkdir lib
在t3目錄下建立CMakeLists.txt,內(nèi)容如下:
PROJECT(HELLOLIB)
ADD_SUBDIRECTORY(lib)
在lib目錄下建立兩個源文件hello.c與hello.h
hello.c內(nèi)容如下:
#include “hello.h”
void HelloFunc()
{
printf(“Hello World\n”);
}
hello.h內(nèi)容如下:
#ifndef HELLO_H
#define HELLO_H
#include <stdio.h>
void HelloFunc();
#endif
在lib目錄下建立CMakeLists.txt,內(nèi)容如下:
SET(LIBHELLO_SRC hello.c)
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
三,編譯共享庫:
仍然采用out-of-source編譯的方式,按照習(xí)慣,我們建立一個build目錄,在build
目錄中
cmake ..
make
這時,你就可以在lib目錄得到一個libhello.so,這就是我們期望的共享庫。
如果你要指定libhello.so生成的位置,可以通過在主工程文件CMakeLists.txt中修
改ADD_SUBDIRECTORY(lib)指令來指定一個編譯輸出位置或者
在lib/CMakeLists.txt中添加
SET(LIBRARY_OUTPUT_PATH <路徑>)來指定一個新的位置。
這兩者的區(qū)別我們上一節(jié)已經(jīng)提到了,所以,這里不再贅述,下面,我們解釋一下一個新的
指令A(yù)DD_LIBRARY
        ADD_LIBRARY(libname    [SHARED|STATIC|MODULE]
          [EXCLUDE_FROM_ALL]
                source1 source2 ... sourceN)
你不需要寫全libhello.so,只需要填寫hello即可,cmake系統(tǒng)會自動為你生成
libhello.X
類型有三種:
SHARED,動態(tài)庫
STATIC,靜態(tài)庫
MODULE,在使用dyld的系統(tǒng)有效,如果不支持dyld,則被當(dāng)作SHARED對待。
EXCLUDE_FROM_ALL參數(shù)的意思是這個庫不會被默認(rèn)構(gòu)建,除非有其他的組件依賴或者手
工構(gòu)建。
四,添加靜態(tài)庫:
同樣使用上面的指令,我們在支持動態(tài)庫的基礎(chǔ)上再為工程添加一個靜態(tài)庫,按照一般的習(xí)
慣,靜態(tài)庫名字跟動態(tài)庫名字應(yīng)該是一致的,只不過后綴是.a罷了。
下面我們用這個指令再來添加靜態(tài)庫:
ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC})
然后再在build目錄進(jìn)行外部編譯,我們會發(fā)現(xiàn),靜態(tài)庫根本沒有被構(gòu)建,仍然只生成了
一個動態(tài)庫。因?yàn)閔ello作為一個target是不能重名的,所以,靜態(tài)庫構(gòu)建指令無效。
如果我們把上面的hello修改為hello_static:
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})
就可以構(gòu)建一個libhello_static.a的靜態(tài)庫了。
這種結(jié)果顯示不是我們想要的,我們需要的是名字相同的靜態(tài)庫和動態(tài)庫,因?yàn)閠arget名
稱是唯一的,所以,我們肯定不能通過ADD_LIBRARY指令來實(shí)現(xiàn)了。這時候我們需要用到
另外一個指令:
SET_TARGET_PROPERTIES,其基本語法是:
        SET_TARGET_PROPERTIES(target1 target2 ...
                      PROPERTIES prop1 value1
                      prop2 value2 ...)
這條指令可以用來設(shè)置輸出的名稱,對于動態(tài)庫,還可以用來指定動態(tài)庫版本和API版本。
在本例中,我們需要作的是向lib/CMakeLists.txt中添加一條:
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")
這樣,我們就可以同時得到libhello.so/libhello.a兩個庫了。
與他對應(yīng)的指令是:
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.
讓我們來檢查一下最終的構(gòu)建結(jié)果,我們發(fā)現(xiàn),libhello.a已經(jīng)構(gòu)建完成,位于
build/lib目錄中,但是libhello.so去消失了。這個問題的原因是:cmake在構(gòu)建一
個新的target時,會嘗試清理掉其他使用這個名字的庫,因?yàn)?,在?gòu)建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)
這時候,我們再次進(jìn)行構(gòu)建,會發(fā)現(xiàn)build/lib目錄中同時生成了libhello.so和
libhello.a
五,動態(tài)庫版本號
按照規(guī)則,動態(tài)庫是應(yīng)該包含一個版本號的,我們可以看一下系統(tǒng)的動態(tài)庫,一般情況是
libhello.so.1.2
libhello.so ->libhello.so.1
libhello.so.1->libhello.so.1.2
為了實(shí)現(xiàn)動態(tài)庫版本號,我們?nèi)匀恍枰褂肧ET_TARGET_PROPERTIES指令。
具體使用方法如下:
SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1)
VERSION指代動態(tài)庫版本,SOVERSION指代API版本。
將上述指令加入lib/CMakeLists.txt中,重新構(gòu)建看看結(jié)果。
在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安裝到系統(tǒng)目
錄,才能真正讓其他人開發(fā)使用,在本例中我們將hello的共享庫安裝到<prefix>/lib
目錄,將hello.h安裝到<prefix>/include/hello目錄。
利用上一節(jié)了解到的INSTALL指令,我們向lib/CMakeLists.txt中添加如下指令:
INSTALL(TARGETS hello hello_static
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib)
INSTALL(FILES hello.h DESTINATION include/hello)
注意,靜態(tài)庫要使用ARCHIVE關(guān)鍵字
通過:
cmake -DCMAKE_INSTALL_PREFIX=/usr ..
make
make install
我們就可以將頭文件和共享庫安裝到系統(tǒng)目錄/usr/lib和/usr/include/hello中了。
七,小結(jié):
本小節(jié),我們談到了:
如何通過ADD_LIBRARY指令構(gòu)建動態(tài)庫和靜態(tài)庫。
如何通過SET_TARGET_PROPERTIES同時構(gòu)建同名的動態(tài)庫和靜態(tài)庫。
如何通過SET_TARGET_PROPERTIES控制動態(tài)庫版本
最終使用上一節(jié)談到的INSTALL指令來安裝頭文件和動態(tài)、靜態(tài)庫。
在下一節(jié),我們需要編寫另一個高級一點(diǎn)的Hello World來演示怎么使用我們已經(jīng)構(gòu)建的
構(gòu)建的共享庫libhello和外部頭文件。
六,如何使用外部共享庫和頭文件
抱歉,本節(jié)仍然繼續(xù)折騰Hello World.
上一節(jié)我們已經(jīng)完成了libhello動態(tài)庫的構(gòu)建以及安裝,本節(jié)我們的任務(wù)很簡單:
編寫一個程序使用我們上一節(jié)構(gòu)建的共享庫。
1,準(zhǔn)備工作:
請在/backup/cmake目錄建立t4目錄,本節(jié)所有資源將存儲在t4目錄。
2,重復(fù)以前的步驟,建立src目錄,編寫源文件main.c,內(nèi)容如下:
#include <hello.h>
int main()
{
HelloFunc();
return 0;
}
編寫工程主文件CMakeLists.txt
PROJECT(NEWHELLO)
ADD_SUBDIRECTORY(src)
編寫src/CMakeLists.txt
ADD_EXECUTABLE(main main.c)
上述工作已經(jīng)嚴(yán)格按照我們前面季節(jié)提到的內(nèi)容完成了。
3,外部構(gòu)建
按照習(xí)慣,仍然建立build目錄,使用cmake ..方式構(gòu)建。
過程:
cmake ..
make
構(gòu)建失敗,如果需要查看細(xì)節(jié),可以使用第一節(jié)提到的方法
make VERBOSE=1來構(gòu)建
錯誤輸出為是:
/backup/cmake/t4/src/main.c:1:19: error: hello.h: 沒有那個文件或目錄
4,引入頭文件搜索路徑。
hello.h位于/usr/include/hello目錄中,并沒有位于系統(tǒng)標(biāo)準(zhǔn)的頭文件路徑,
(有人會說了,白癡啊,你就不會include <hello/hello.h>,同志,要這么干,我這
一節(jié)就沒什么可寫了,只能選擇一個glib或者libX11來寫了,這些代碼寫出來很多同志
是看不懂的)
為了讓我們的工程能夠找到hello.h頭文件,我們需要引入一個新的指令
INCLUDE_DIRECTORIES,其完整語法為:
INCLUDE_DIRECTORIES([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)
這條指令可以用來向工程添加多個特定的頭文件搜索路徑,路徑之間用空格分割,如果路徑
中包含了空格,可以使用雙引號將它括起來,默認(rèn)的行為是追加到當(dāng)前的頭文件搜索路徑的
后面,你可以通過兩種方式來進(jìn)行控制搜索路徑添加的方式:
1,CMAKE_INCLUDE_DIRECTORIES_BEFORE,通過SET這個cmake變量為on,可以
將添加的頭文件搜索路徑放在已有路徑的前面。
2,通過AFTER或者BEFORE參數(shù),也可以控制是追加還是置前。
現(xiàn)在我們在src/CMakeLists.txt中添加一個頭文件搜索路徑,方式很簡單,加入:
INCLUDE_DIRECTORIES(/usr/include/hello)
進(jìn)入build目錄,重新進(jìn)行構(gòu)建,這是找不到hello.h的錯誤已經(jīng)消失,但是出現(xiàn)了一個
新的錯誤:
main.c:(.text+0x12): undefined reference to `HelloFunc'
因?yàn)槲覀儾]有l(wèi)ink到共享庫libhello上。
5,為target添加共享庫
我們現(xiàn)在需要完成的任務(wù)是將目標(biāo)文件鏈接到libhello,這里我們需要引入兩個新的指令
LINK_DIRECTORIES和TARGET_LINK_LIBRARIES
LINK_DIRECTORIES的全部語法是:
LINK_DIRECTORIES(directory1 directory2 ...)
這個指令非常簡單,添加非標(biāo)準(zhǔn)的共享庫搜索路徑,比如,在工程內(nèi)部同時存在共享庫和可
執(zhí)行二進(jìn)制,在編譯時就需要指定一下這些共享庫的路徑。這個例子中我們沒有用到這個指
令。
TARGET_LINK_LIBRARIES的全部語法是:
TARGET_LINK_LIBRARIES(target library1
                      <debug | optimized> library2
                      ...)
這個指令可以用來為target添加需要鏈接的共享庫,本例中是一個可執(zhí)行文件,但是同樣
可以用于為自己編寫的共享庫添加共享庫鏈接。
為了解決我們前面遇到的HelloFunc未定義錯誤,我們需要作的是向
src/CMakeLists.txt中添加如下指令:
TARGET_LINK_LIBRARIES(main hello)
也可以寫成
TARGET_LINK_LIBRARIES(main libhello.so)
這里的hello指的是我們上一節(jié)構(gòu)建的共享庫libhello.
進(jìn)入build目錄重新進(jìn)行構(gòu)建。
cmake ..
make
這是我們就得到了一個連接到libhello的可執(zhí)行程序main,位于build/src目錄,運(yùn)
行main的結(jié)果是輸出:
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確實(shí)鏈接了共享庫libhello,而且鏈接的是動態(tài)庫
libhello.so.1
那如何鏈接到靜態(tài)庫呢?
方法很簡單:
將TARGET_LINK_LIBRRARIES指令修改為:
TARGET_LINK_LIBRARIES(main libhello.a)
重新構(gòu)建后再來看一下main的鏈接情況
ldd src/main
        linux-gate.so.1 =>  (0xb7fa8000)
        libc.so.6 => /lib/libc.so.6 (0xb7e3a000)
        /lib/ld-linux.so.2 (0xb7fa9000)
說明,main確實(shí)鏈接到了靜態(tài)庫libhello.a
6,特殊的環(huán)境變量CMAKE_INCLUDE_PATH和CMAKE_LIBRARY_PATH
務(wù)必注意,這兩個是環(huán)境變量而不是cmake變量。
使用方法是要在bash中用export或者在csh中使用set命令設(shè)置或者
CMAKE_INCLUDE_PATH=/home/include cmake ..等方式。
這兩個變量主要是用來解決以前autotools工程中
--extra-include-dir等參數(shù)的支持的。
也就是,如果頭文件沒有存放在常規(guī)路徑(/usr/include, /usr/local/include等),
則可以通過這些變量就行彌補(bǔ)。
我們以本例中的hello.h為例,它存放在/usr/include/hello目錄,所以直接查找肯
定是找不到的。
前面我們直接使用了絕對路徑INCLUDE_DIRECTORIES(/usr/include/hello)告訴工
程這個頭文件目錄。
為了將程序更智能一點(diǎn),我們可以使用CMAKE_INCLUDE_PATH來進(jìn)行,使用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)
上述的一些指令我們在后面會介紹。
這里簡單說明一下,F(xiàn)IND_PATH用來在指定路徑中搜索文件名,比如:
FIND_PATH(myHeader NAMES hello.h PATHS /usr/include
/usr/include/hello)
這里我們沒有指定路徑,但是,cmake仍然可以幫我們找到hello.h存放的路徑,就是因
為我們設(shè)置了環(huán)境變量CMAKE_INCLUDE_PATH。
如果你不使用FIND_PATH,CMAKE_INCLUDE_PATH變量的設(shè)置是沒有作用的,你不能指
望它會直接為編譯器命令添加參數(shù)-I<CMAKE_INCLUDE_PATH>。
以此為例,CMAKE_LIBRARY_PATH可以用在FIND_LIBRARY中。
同樣,因?yàn)檫@些變量直接為FIND_指令所使用,所以所有使用FIND_指令的cmake模塊都
會受益。
7,小節(jié):
本節(jié)我們探討了:
如何通過INCLUDE_DIRECTORIES指令加入非標(biāo)準(zhǔn)的頭文件搜索路徑。
如何通過LINK_DIRECTORIES指令加入非標(biāo)準(zhǔn)的庫文件搜索路徑。
如果通過TARGET_LINK_LIBRARIES為庫或可執(zhí)行二進(jìn)制加入庫鏈接。
并解釋了如果鏈接到靜態(tài)庫。
到這里為止,您應(yīng)該基本可以使用cmake工作了,但是還有很多高級的話題沒有探討,比
如編譯條件檢查、編譯器定義、平臺判斷、如何跟pkgconfig配合使用等等。
“ 到這里,或許你可以理解前面講到的cmake的使用過程其實(shí)就是學(xué)習(xí)cmake語言并編寫
cmake ” “ 程序的過程,既然是cmake ” 語言,自然涉及到變量、語法等.
下一節(jié),我們將拋開程序的話題,看看常用的CMAKE變量以及一些基本的控制語法規(guī)則。
七,cmake常用變量和常用環(huán)境變量
一,cmake變量引用的方式:
前面我們已經(jīng)提到了,使用${}進(jìn)行變量的引用。在IF等語句中,是直接使用變量名而不
通過${}取值
二,cmake自定義變量的方式:
主要有隱式定義和顯式定義兩種,前面舉了一個隱式定義的例子,就是PROJECT指令,他
會隱式的定義<projectname>_BINARY_DIR和<projectname>_SOURCE_DIR兩個變
量。
顯式定義的例子我們前面也提到了,使用SET指令,就可以構(gòu)建一個自定義變量了。
比如:
SET(HELLO_SRC main.SOURCE_PATHc),就PROJECT_BINARY_DIR可以通過
${HELLO_SRC}來引用這個自定義變量了.
三,cmake常用變量:
1,CMAKE_BINARY_DIR
   PROJECT_BINARY_DIR
   <projectname>_BINARY_DIR
這三個變量指代的內(nèi)容是一致的,如果是in source編譯,指得就是工程頂層目錄,如果
是out-of-source編譯,指的是工程編譯發(fā)生的目錄。PROJECT_BINARY_DIR跟其他
指令稍有區(qū)別,現(xiàn)在,你可以理解為他們是一致的。
2,CMAKE_SOURCE_DIR
    PROJECT_SOURCE_DIR
    <projectname>_SOURCE_DIR
這三個變量指代的內(nèi)容是一致的,不論采用何種編譯方式,都是工程頂層目錄。
也就是在in source編譯時,他跟CMAKE_BINARY_DIR等變量一致。
PROJECT_SOURCE_DIR跟其他指令稍有區(qū)別,現(xiàn)在,你可以理解為他們是一致的。
3,CMAKE_CURRENT_SOURCE_DIR
指的是當(dāng)前處理的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 <新路徑>)并不會對這個變量造成影響,它僅僅
修改了最終目標(biāo)文件存放的路徑。
5,CMAKE_CURRENT_LIST_FILE
輸出調(diào)用這個變量的CMakeLists.txt的完整路徑
6,CMAKE_CURRENT_LIST_LINE
輸出這個變量所在的行
7,CMAKE_MODULE_PATH
這個變量用來定義自己的cmake模塊所在的路徑。如果你的工程比較復(fù)雜,有可能會自己
編寫一些cmake模塊,這些cmake模塊是隨你的工程發(fā)布的,為了讓cmake在處理
CMakeLists.txt時找到這些模塊,你需要通過SET指令,將自己的cmake模塊路徑設(shè)
置一下。
比如
SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
這時候你就可以通過INCLUDE指令來調(diào)用自己的模塊了。
8,EXECUTABLE_OUTPUT_PATH和LIBRARY_OUTPUT_PATH
分別用來重新定義最終結(jié)果的存放目錄,前面我們已經(jīng)提到了這兩個變量。
9,PROJECT_NAME
返回通過PROJECT指令定義的項(xiàng)目名稱。
四,cmake調(diào)用環(huán)境變量的方式
使用$ENV{NAME}指令就可以調(diào)用系統(tǒng)的環(huán)境變量了。
比如
MESSAGE(STATUS “HOME dir: $ENV{HOME}”)
設(shè)置環(huán)境變量的方式是:
SET(ENV{變量名} 值)
1,CMAKE_INCLUDE_CURRENT_DIR
自動添加CMAKE_CURRENT_BINARY_DIR和CMAKE_CURRENT_SOURCE_DIR到當(dāng)前處理
的CMakeLists.txt。相當(dāng)于在每個CMakeLists.txt加入:
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}
${CMAKE_CURRENT_SOURCE_DIR})
2,CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE
將工程提供的頭文件目錄始終至于系統(tǒng)頭文件目錄的前面,當(dāng)你定義的頭文件確實(shí)跟系統(tǒng)發(fā)
生沖突時可以提供一些幫助。
3,CMAKE_INCLUDE_PATH和CMAKE_LIBRARY_PATH我們在上一節(jié)已經(jīng)提及。
五,系統(tǒng)信息
1,CMAKE_MAJOR_VERSION,CMAKE主版本號,比如2.4.6中的2
2,CMAKE_MINOR_VERSION,CMAKE次版本號,比如2.4.6中的4
3,CMAKE_PATCH_VERSION,CMAKE補(bǔ)丁等級,比如2.4.6 中的6
4,CMAKE_SYSTEM,系統(tǒng)名稱,比如Linux-2.6.22
5,CMAKE_SYSTEM_NAME,不包含版本的系統(tǒng)名,比如Linux
6,CMAKE_SYSTEM_VERSION,系統(tǒng)版本,比如2.6.22
7,CMAKE_SYSTEM_PROCESSOR,處理器名稱,比如i686.
8,UNIX,在所有的類UNIX平臺為TRUE,包括OS X和cygwin
9,WIN32,在所有的win32平臺為TRUE,包括cygwin
六,主要的開關(guān)選項(xiàng):
1,CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS,用來控制IF ELSE語句的書寫方式,在
下一節(jié)語法部分會講到。
2,BUILD_SHARED_LIBS
這個開關(guān)用來控制默認(rèn)的庫編譯方式,如果不進(jìn)行設(shè)置,使用ADD_LIBRARY并沒有指定庫
類型的情況下,默認(rèn)編譯生成的庫都是靜態(tài)庫。
如果SET(BUILD_SHARED_LIBS ON)后,默認(rèn)生成的為動態(tài)庫。
3,CMAKE_C_FLAGS
設(shè)置C編譯選項(xiàng),也可以通過指令A(yù)DD_DEFINITIONS()添加。
4,CMAKE_CXX_FLAGS
設(shè)置C++編譯選項(xiàng),也可以通過指令A(yù)DD_DEFINITIONS()添加。
小結(jié):
本章介紹了一些較常用的cmake變量,這些變量僅僅是所有cmake變量的很少一部分,目
前cmake的英文文檔也是比較缺乏的,如果需要了解更多的cmake變量,更好的方式是閱
讀一些成功項(xiàng)目的cmake工程文件,比如KDE4的代碼。
八,cmake常用指令
前面我們講到了cmake “ 常用的變量,相信cmake ” 即編程的感覺會越來越明顯,無論如何,
我們?nèi)匀豢梢钥吹絚make比autotools要簡單很多。接下來我們就要集中的看一看
cmake所提供的常用指令。在前面的章節(jié)我們已經(jīng)討論了很多指令的用法,如
PROJECT,ADD_EXECUTABLE,INSTALL,ADD_SUBDIRECTORY,SUBDIRS,INCLUDE
_DIRECTORIES,LINK_DIRECTORIES,TARGET_LINK_LIBRARIES,SET等。
本節(jié)會引入更多的cmake指令,為了編寫的方便,我們將按照cmake man page的順序
來介紹各種指令,不再推薦使用的指令將不再介紹,INSTALL系列指令在安裝部分已經(jīng)做
了非常詳細(xì)的說明,本節(jié)也不在提及。(你可以將本章理解成選擇性翻譯,但是會加入更多
的個人理解)
一,基本指令
1,ADD_DEFINITIONS
向C/C++編譯器添加-D定義,比如:
ADD_DEFINITIONS(-DENABLE_DEBUG  -DABC),參數(shù)之間用空格分割。
如果你的代碼中定義了#ifdef ENABLE_DEBUG #endif,這個代碼塊就會生效。
如果要添加其他的編譯器開關(guān),可以通過CMAKE_C_FLAGS變量和CMAKE_CXX_FLAGS變
量設(shè)置。
2,ADD_DEPENDENCIES
定義target依賴的其他target,確保在編譯本target之前,其他的target已經(jīng)被構(gòu)
建。
ADD_DEPENDENCIES(target-name depend-target1
                 depend-target2 ...)
3,ADD_EXECUTABLE、ADD_LIBRARY、ADD_SUBDIRECTORY前面已經(jīng)介紹過了,這里
不再羅唆。
4,ADD_TEST與ENABLE_TESTING指令。
ENABLE_TESTING指令用來控制Makefile是否構(gòu)建test目標(biāo),涉及工程所有目錄。語
法很簡單,沒有任何參數(shù),ENABLE_TESTING(),一般情況這個指令放在工程的主
CMakeLists.txt中.
ADD_TEST指令的語法是:
ADD_TEST(testname Exename arg1 arg2 ...)
testname是自定義的test名稱,Exename可以是構(gòu)建的目標(biāo)文件也可以是外部腳本等
等。后面連接傳遞給可執(zhí)行文件的參數(shù)。如果沒有在同一個CMakeLists.txt中打開
ENABLE_TESTING()指令,任何ADD_TEST都是無效的。
比如我們前面的Helloworld例子,可以在工程主CMakeLists.txt中添加
ADD_TEST(mytest ${PROJECT_BINARY_DIR}/bin/main)
ENABLE_TESTING()
生成Makefile后,就可以運(yùn)行make test來執(zhí)行測試了。
5,AUX_SOURCE_DIRECTORY
基本語法是:
AUX_SOURCE_DIRECTORY(dir VARIABLE)
作用是發(fā)現(xiàn)一個目錄下所有的源代碼文件并將列表存儲在一個變量中,這個指令臨時被用來
自動構(gòu)建源文件列表。因?yàn)槟壳癱make還不能自動發(fā)現(xiàn)新添加的源文件。
比如
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,則出現(xiàn)嚴(yán)重錯誤,整個過程中止。
7,EXEC_PROGRAM
在CMakeLists.txt處理過程中執(zhí)行命令,并不會在生成的Makefile中執(zhí)行。具體語法
為:
EXEC_PROGRAM(Executable [directory in which to run]
                 [ARGS <arguments to executable>]
                 [OUTPUT_VARIABLE <var>]
                 [RETURN_VALUE <var>])
用于在指定的目錄運(yùn)行某個程序,通過ARGS添加參數(shù),如果要獲取輸出和返回值,可通過
OUTPUT_VARIABLE和RETURN_VALUE分別定義兩個變量.
這個指令可以幫助你在CMakeLists.txt處理過程中支持任何命令,比如根據(jù)系統(tǒng)情況去
修改代碼文件等等。
舉個簡單的例子,我們要在src目錄執(zhí)行l(wèi)s命令,并把結(jié)果和返回值存下來。
可以直接在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的過程中,就會執(zhí)行l(wèi)s命令,如果返回0,則說明成功執(zhí)行,
那么就輸出ls *.c的結(jié)果。關(guān)于IF語句,后面的控制指令會提到。
8,F(xiàn)ILE指令
文件操作指令,基本語法為:
        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文件,也用于載入預(yù)定義的cmake模塊.
        INCLUDE(file1 [OPTIONAL])
        INCLUDE(module [OPTIONAL])
OPTIONAL參數(shù)的作用是文件不存在也不會產(chǎn)生錯誤。
你可以指定載入一個文件,如果定義的是一個模塊,那么將在CMAKE_MODULE_PATH中搜
索這個模塊并載入。
載入的內(nèi)容將在處理到INCLUDE語句是直接執(zhí)行。
二,INSTALL指令
INSTALL系列指令已經(jīng)在前面的章節(jié)有非常詳細(xì)的說明,這里不在贅述,可參考前面的安
裝部分。
三,F(xiàn)IND_指令
FIND_系列指令主要包含一下指令:
FIND_FILE(<VAR> name1 path1 path2 ...)
VAR變量代表找到的文件全路徑,包含文件名
FIND_LIBRARY(<VAR> name1 path1 path2 ...)
VAR變量表示找到的庫全路徑,包含庫文件名
FIND_PATH(<VAR> name1 path1 path2 ...)
VAR變量代表包含這個文件的路徑。
FIND_PROGRAM(<VAR> name1 path1 path2 ...)
VAR變量代表包含這個程序的全路徑。
FIND_PACKAGE(<name> [major.minor] [QUIET] [NO_MODULE]
                 [[REQUIRED|COMPONENTS] [componets...]])
用來調(diào)用預(yù)定義在CMAKE_MODULE_PATH下的Find<name>.cmake模塊,你也可以自己
定義Find<name>模塊,通過SET(CMAKE_MODULE_PATH dir)將其放入工程的某個目錄
中供工程使用,我們在后面的章節(jié)會詳細(xì)介紹FIND_PACKAGE的使用方法和Find模塊的
編寫。
FIND_LIBRARY示例:
FIND_LIBRARY(libX X11 /usr/lib)
IF(NOT libX)
MESSAGE(FATAL_ERROR “l(fā)ibX 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,總體把握一個原則,凡是出現(xiàn)IF的地方一定要有對應(yīng)的
ENDIF.出現(xiàn)ELSEIF的地方,ENDIF是可選的。
表達(dá)式的使用方法如下:
IF(var),如果變量不是:空,0,N, NO, OFF, FALSE, NOTFOUND或
<var>_NOTFOUND時,表達(dá)式為真。
IF(NOT var ),與上述條件相反。
IF(var1 AND var2),當(dāng)兩個變量都為真是為真。
IF(var1 OR var2),當(dāng)兩個變量其中一個為真時為真。
IF(COMMAND cmd),當(dāng)給定的cmd確實(shí)是命令并可以調(diào)用是為真。
IF(EXISTS dir)或者IF(EXISTS file),當(dāng)目錄名或者文件名存在時為真。
IF(file1  IS_NEWER_THAN file2),當(dāng)file1比file2新,或者file1/file2其
中有一個不存在時為真,文件名請使用完整路徑。
IF(IS_DIRECTORY dirname),當(dāng)dirname是目錄時,為真。
IF(variable MATCHES regex)
IF(string MATCHES regex)
當(dāng)給定的變量或者字符串能夠匹配正則表達(dá)式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)
數(shù)字比較表達(dá)式
IF(variable STRLESS string)
IF(string STRLESS string)
IF(variable STRGREATER string)
IF(string STRGREATER string)
IF(variable STREQUAL string)
IF(string STREQUAL string)
按照字母序的排列進(jìn)行比較.
IF(DEFINED variable),如果變量被定義,為真。
一個小例子,用來判斷平臺差異:
IF(WIN32)
MESSAGE(STATUS “This is windows.”)
#作一些Windows相關(guān)的操作
ELSE(WIN32)
MESSAGE(STATUS “This is not windows”)
#作一些非Windows相關(guān)的操作
ENDIF(WIN32)
上述代碼用來控制在不同的平臺進(jìn)行不同的控制,但是,閱讀起來卻并不是那么舒服,
ELSE(WIN32)之類的語句很容易引起歧義。
“ ”   這就用到了我們在常用變量一節(jié)提到的CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS開
關(guān)。
可以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為步進(jìn)
舉例如下:
FOREACH(VAR RANGE 10)
MESSAGE(${VAR})
ENDFOREACH(VAR)
最終得到的輸出是:
0
1
2
3
4
5
6
7
8
9
10
3,范圍和步進(jìn)
FOREACH(loop_var RANGE start stop [step])
ENDFOREACH(loop_var)
從start開始到stop結(jié)束,以step為步進(jìn),
舉例如下
FOREACH(A RANGE 5 15 3)
MESSAGE(${A})
ENDFOREACH(A)
最終得到的結(jié)果是:
5
8
11
14
這個指令需要注意的是,知道遇到ENDFOREACH指令,整個語句塊才會得到真正的執(zhí)行。
小結(jié):
本小節(jié)基本涵蓋了常用的cmake指令,包括基本指令、查找指令、安裝指令以及控制語句
等,特別需要注意的是,在控制語句條件中使用變量,不能用${}引用,而是直接應(yīng)用變量
名。
掌握了以上的各種控制指令,你應(yīng)該完全可以通過cmake管理復(fù)雜的程序了,下一節(jié),我
們將介紹一個比較復(fù)雜的例子,通過他來演示本章的一些指令,并介紹模塊的概念。
九,復(fù)雜的例子:模塊的使用和自定義模塊
你現(xiàn)在還會覺得cmake簡單嗎?
本章我們將著重介紹系統(tǒng)預(yù)定義的Find模塊的使用以及自己編寫Find模塊,系統(tǒng)中提供
了其他各種模塊,一般情況需要使用INCLUDE指令顯式的調(diào)用,F(xiàn)IND_PACKAGE指令是一
個特例,可以直接調(diào)用預(yù)定義的模塊。
其實(shí)使用純粹依靠cmake本身提供的基本指令來管理工程是一件非常復(fù)雜的事情,所以,
cmake設(shè)計成了可擴(kuò)展的架構(gòu),可以通過編寫一些通用的模塊來擴(kuò)展cmake.
在本章,我們準(zhǔn)備首先介紹一下cmake提供的FindCURL模塊的使用。然后,基于我們前
面的libhello共享庫,編寫一個FindHello.cmake模塊。
一,使用FindCURL模塊
在/backup/cmake目錄建立t5目錄,用于存放我們的CURL的例子。
建立src目錄,并建立src/main.c,內(nèi)容如下:
#include <curl/curl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
FILE *fp;
int write_data(void *ptr, size_t size, size_t nmemb, void *stream)
{
int written = fwrite(ptr, size, nmemb, (FILE *)fp);
return written;
}
int main()
{
const char * path = “/tmp/curl-test”;
const char * mode = “w”;
fp = fopen(path,mode);
curl_global_init(CURL_GLOBAL_ALL);
CURLcode res;
CURL *curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_URL, “http://www.”);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
}
這段代碼的作用是通過curl取回www.的首頁并寫入/tmp/curl-test
文件中。
建立主工程文件CMakeLists.txt
PROJECT(CURLTEST)
ADD_SUBDIRECTORY(src)
建立src/CMakeLists.txt
ADD_EXECUTABLE(curltest main.c)
現(xiàn)在自然是沒辦法編譯的,我們需要添加curl的頭文件路徑和庫文件。
方法1:
直接通過INCLUDE_DIRECTORIES和TARGET_LINK_LIBRARIES指令添加:
我們可以直接在src/CMakeLists.txt中添加:
INCLUDE_DIRECTORIES(/usr/include)
TARGET_LINK_LIBRARIES(curltest curl)
然后建立build目錄進(jìn)行外部構(gòu)建即可。
現(xiàn)在我們要探討的是使用cmake提供的FindCURL模塊。
方法2,使用FindCURL模塊。
向src/CMakeLists.txt中添加:
FIND_PACKAGE(CURL)
IF(CURL_FOUND)
   INCLUDE_DIRECTORIES(${CURL_INCLUDE_DIR})
   TARGET_LINK_LIBRARIES(curltest ${CURL_LIBRARY})
ELSE(CURL_FOUND)
MESSAGE(FATAL_ERROR ”CURL library not found”)
ENDIF(CURL_FOUND)
對于系統(tǒng)預(yù)定義的Find<name>.cmake模塊,使用方法一般如上例所示:
每一個模塊都會定義以下幾個變量
<name>_FOUND
<name>_INCLUDE_DIR or <name>_INCLUDES
<name>_LIBRARY or <name>_LIBRARIES
你可以通過<name>_FOUND來判斷模塊是否被找到,如果沒有找到,按照工程的需要關(guān)閉
某些特性、給出提醒或者中止編譯,上面的例子就是報出致命錯誤并終止構(gòu)建。
如果<name>_FOUND為真,則將<name>_INCLUDE_DIR加入INCLUDE_DIRECTORIES,
將<name>_LIBRARY加入TARGET_LINK_LIBRARIES中。
我們再來看一個復(fù)雜的例子,通過<name>_FOUND來控制工程特性:
SET(mySources viewer.c)
SET(optionalSources)
SET(optionalLibs)
FIND_PACKAGE(JPEG)
IF(JPEG_FOUND)
   SET(optionalSources ${optionalSources} jpegview.c)
   INCLUDE_DIRECTORIES( ${JPEG_INCLUDE_DIR} )
   SET(optionalLibs ${optionalLibs} ${JPEG_LIBRARIES} )
   ADD_DEFINITIONS(-DENABLE_JPEG_SUPPORT)
ENDIF(JPEG_FOUND)
IF(PNG_FOUND)
   SET(optionalSources ${optionalSources} pngview.c)
   INCLUDE_DIRECTORIES( ${PNG_INCLUDE_DIR} )
   SET(optionalLibs ${optionalLibs} ${PNG_LIBRARIES} )
   ADD_DEFINITIONS(-DENABLE_PNG_SUPPORT)
ENDIF(PNG_FOUND)
ADD_EXECUTABLE(viewer ${mySources} ${optionalSources} )
TARGET_LINK_LIBRARIES(viewer ${optionalLibs}
通過判斷系統(tǒng)是否提供了JPEG庫來決定程序是否支持JPEG功能。
二,編寫屬于自己的FindHello模塊。
我們在此前的t3實(shí)例中,演示了構(gòu)建動態(tài)庫、靜態(tài)庫的過程并進(jìn)行了安裝。
接下來,我們在t6示例中演示如何自定義FindHELLO模塊并使用這個模塊構(gòu)建工程:
請在建立/backup/cmake/中建立t6目錄,并在其中建立cmake目錄用于存放我們自己
定義的FindHELLO.cmake模塊,同時建立src目錄,用于存放我們的源文件。
1,定義cmake/FindHELLO.cmake模塊
FIND_PATH(HELLO_INCLUDE_DIR hello.h /usr/include/hello
/usr/local/include/hello)
FIND_LIBRARY(HELLO_LIBRARY NAMES hello PATH /usr/lib
/usr/local/lib)
IF (HELLO_INCLUDE_DIR AND HELLO_LIBRARY)
   SET(HELLO_FOUND TRUE)
ENDIF (HELLO_INCLUDE_DIR AND HELLO_LIBRARY)
IF (HELLO_FOUND)
   IF (NOT HELLO_FIND_QUIETLY)
      MESSAGE(STATUS "Found Hello: ${HELLO_LIBRARY}")
   ENDIF (NOT HELLO_FIND_QUIETLY)
ELSE (HELLO_FOUND)
   IF (HELLO_FIND_REQUIRED)
      MESSAGE(FATAL_ERROR "Could not find hello library")
   ENDIF (HELLO_FIND_REQUIRED)
ENDIF (HELLO_FOUND)
針對上面的模塊讓我們再來回顧一下FIND_PACKAGE指令:
        FIND_PACKAGE(<name> [major.minor] [QUIET] [NO_MODULE]
                 [[REQUIRED|COMPONENTS] [componets...]])
前面的CURL例子中我們使用了最簡單的FIND_PACKAGE指令,其實(shí)他可以使用多種參數(shù),
QUIET參數(shù),對應(yīng)與我們編寫的FindHELLO   中的HELLO_FIND_QUIETLY,如果不指定
這個參數(shù),就會執(zhí)行:
MESSAGE(STATUS "Found Hello: ${HELLO_LIBRARY}")
REQUIRED參數(shù),其含義是指這個共享庫是否是工程必須的,如果使用了這個參數(shù),說明這
個鏈接庫是必備庫,如果找不到這個鏈接庫,則工程不能編譯。對應(yīng)于
FindHELLO.cmake   模塊中的HELLO_FIND_REQUIRED變量。
同樣,我們在上面的模塊中定義了HELLO_FOUND,
HELLO_INCLUDE_DIR,HELLO_LIBRARY變量供開發(fā)者在FIND_PACKAGE指令中使用。
OK,下面建立src/main.c,內(nèi)容為:
#include <hello.h>
int main()
{
HelloFunc();
return 0;
}
建立src/CMakeLists.txt文件,內(nèi)容如下:
FIND_PACKAGE(HELLO)
IF(HELLO_FOUND)
    ADD_EXECUTABLE(hello main.c)
    INCLUDE_DIRECTORIES(${HELLO_INCLUDE_DIR})
    TARGET_LINK_LIBRARIES(hello ${HELLO_LIBRARY})
ENDIF(HELLO_FOUND)
為了能夠讓工程找到FindHELLO.cmake模塊(存放在工程中的cmake目錄)
我們在主工程文件CMakeLists.txt中加入:
SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
三,使用自定義的FindHELLO模塊構(gòu)建工程
仍然采用外部編譯的方式,建立build目錄,進(jìn)入目錄運(yùn)行:
cmake ..
我們可以從輸出中看到:
Found Hello: /usr/lib/libhello.so
如果我們把上面的FIND_PACKAGE(HELLO)修改為FIND_PACKAGE(HELLO QUIET),則
不會看到上面的輸出。
接下來就可以使用make命令構(gòu)建工程,運(yùn)行:
./src/hello可以得到輸出
Hello World。
說明工程成功構(gòu)建。
四,如果沒有找到hello library呢?
我們可以嘗試將/usr/lib/libhello.x移動到/tmp目錄,這樣,按照FindHELLO模塊
的定義,就找不到hello library了,我們再來看一下構(gòu)建結(jié)果:
cmake ..
仍然可以成功進(jìn)行構(gòu)建,但是這時候是沒有辦法編譯的。
修改FIND_PACKAGE(HELLO)為FIND_PACKAGE(HELLO REQUIRED),將hello
library定義為工程必須的共享庫。
這時候再次運(yùn)行cmake ..
我們得到如下輸出:
CMake Error: Could not find hello library.
因?yàn)檎也坏絣ibhello.x,所以,整個Makefile生成過程被出錯中止。
小結(jié):
在本節(jié)中,我們學(xué)習(xí)了如何使用系統(tǒng)提供的Find<NAME>模塊并學(xué)習(xí)了自己編寫
Find<NAME>模塊以及如何在工程中使用這些模塊。
后面的章節(jié),我們會逐漸學(xué)習(xí)更多的cmake模塊使用方法以及用cmake來管理GTK和QT4
工程。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多