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<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的經驗和積累,若是html
你現錯誤或者添加了新內容後,請務必CC給我一份,讓咱們共同把cmake掌握的更好。java
一,初識cmake
Cmake再也不使你在構建項目時鬱悶地想自殺了.linux
--一位KDE開發者c++
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必然會成爲正則表達式
一個主流的構建體系。express
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功能。windows
3,問題,難道就沒有問題?
1,cmake很簡單,但絕對沒有聽起來或者想象中那麼簡單。
2,cmake編寫的過程其實是編程的過程,跟之前使用autotools同樣,不過你須要編
寫的是CMakeLists.txt(每一個目錄一個),使用的是」cmake語言和語法」。
3,cmake跟已有體系的配合並非特別理想,好比pkgconfig,您在實際使用中會有所xcode
體會,雖然有一些擴展可使用,但並不理想。bash
4,我的的建議:
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
還須要安裝嗎?
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 <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)
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變量:
<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"
...)
這個指令用於向終端輸出用戶定義的信息,包含了三種類型:
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以及變量調用的方法,同時說起了兩個隱式變量
<projectname>_SOURCE_DIR及<projectname>_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)
在第一節咱們提到了<projectname>_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 <dir>]
[PERMISSIONS permissions...]
[CONFIGURATIONS
[Debug|Release|...]]
[COMPONENT <component>]
[OPTIONAL]
] [...])
參數中的TARGETS後面跟的就是咱們經過ADD_EXECUTABLE或者ADD_LIBRARY定義的
目標文件,多是可執行二進制、動態庫、靜態庫。
目標類型也就相對應的有三種,ARCHIVE特指靜態庫,LIBRARY特指動態庫,RUNTIME
特指可執行目標二進制。
DESTINATION定義了安裝的路徑,若是路徑以/開頭,那麼指的是絕對路徑,這時候
CMAKE_INSTALL_PREFIX其實就無效了。若是你但願使用CMAKE_INSTALL_PREFIX來
定義安裝路徑,就要寫成相對路徑,即不要以/開頭,那麼安裝後的路徑就是
${CMAKE_INSTALL_PREFIX}/<DESTINATION定義的路徑>
舉個簡單的例子:
INSTALL(TARGETS myrun mylib mystaticlib
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION libstatic
)
上面的例子會將:
可執行二進制myrun安裝到${CMAKE_INSTALL_PREFIX}/bin目錄
動態庫libmylib安裝到${CMAKE_INSTALL_PREFIX}/lib目錄
靜態庫libmystaticlib安裝到${CMAKE_INSTALL_PREFIX}/libstatic目錄
特別注意的是你不須要關心TARGETS具體生成的路徑,只須要寫上TARGETS名稱就能夠
了。
普通文件的安裝:
INSTALL(FILES files... DESTINATION <dir>
[PERMISSIONS permissions...]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[RENAME <name>] [OPTIONAL])
可用於安裝通常文件,並能夠指定訪問權限,文件名是此指令所在路徑下的相對路徑。若是
默認不定義權限PERMISSIONS,安裝後的權限爲:
OWNER_WRITE, OWNER_READ, GROUP_READ,和WORLD_READ,即644權限。
非目標文件的可執行程序安裝(好比腳本之類):
INSTALL(PROGRAMS files... DESTINATION <dir>
[PERMISSIONS permissions...]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[RENAME <name>] [OPTIONAL])
跟上面的FILES指令使用方法同樣,惟一的不一樣是安裝後權限爲:
OWNER_EXECUTE, GROUP_EXECUTE, 和WORLD_EXECUTE,即755權限
目錄的安裝:
INSTALL(DIRECTORY dirs... DESTINATION <dir>
[FILE_PERMISSIONS permissions...]
[DIRECTORY_PERMISSIONS permissions...]
[USE_SOURCE_PERMISSIONS]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[[PATTERN <pattern> | REGEX <regex>]
[EXCLUDE] [PERMISSIONS permissions...]] [...])
這裏主要介紹其中的DIRECTORY、PATTERN以及PERMISSIONS參數。
DIRECTORY後面鏈接的是所在Source目錄的相對路徑,但務必注意:
abc和abc/有很大的區別。
若是目錄名不以/結尾,那麼這個目錄將被安裝爲目標路徑下的abc,若是目錄名以/結尾,
表明將這個目錄中的內容安裝到目標路徑,但不包括這個目錄自己。
PATTERN用於使用正則表達式進行過濾,PERMISSIONS用於指定PATTERN過濾後的文件
權限。
咱們來看一個例子:
INSTALL(DIRECTORY icons scripts/ DESTINATION share/myproj
PATTERN "CVS" EXCLUDE
PATTERN "scripts/*"
PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ
GROUP_EXECUTE GROUP_READ)
這條指令的執行結果是:
將icons目錄安裝到 <prefix>/share/myproj,將scripts/中的內容安裝到
<prefix>/share/myproj
不包含目錄名爲CVS的目錄,對於scripts/*文件指定權限爲 OWNER_EXECUTE
OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ.
安裝時CMAKE腳本的執行:
INSTALL([[SCRIPT <file>] [CODE <code>]] [...])
SCRIPT參數用於在安裝時調用cmake腳本文件(也就是<abc>.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安裝至/<prefix>/bin,將
doc目錄中的hello.txt以及COPYRIGHT/README安裝到
/<prefix>/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要安裝到/<prefix>/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 <stdio.h>
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的共享庫安裝到<prefix>/lib
目錄,將hello.h安裝到<prefix>/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 <hello.h>
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 <hello/hello.h>,同志,要這麼幹,我這
一節就沒什麼可寫了,只能選擇一個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
<debug | optimized> 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_INCLUDE_PATH>。
以此爲例,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指令,他會隱式的定義<projectname>_BINARY_DIR和<projectname>_SOURCE_DIR兩個變量。顯式定義的例子咱們前面也提到了,使用SET指令,就能夠構建一個自定義變量了。好比:SET(HELLO_SRC main.SOURCE_PATHc),就PROJECT_BINARY_DIR能夠經過${HELLO_SRC}來引用這個自定義變量了.三,cmake經常使用變量:1,CMAKE_BINARY_DIRPROJECT_BINARY_DIR<projectname>_BINARY_DIR這三個變量指代的內容是一致的,若是是in source編譯,指得就是工程頂層目錄,若是是out-of-source編譯,指的是工程編譯發生的目錄。PROJECT_BINARY_DIR跟其餘指令稍有區別,如今,你能夠理解爲他們是一致的。2,CMAKE_SOURCE_DIRPROJECT_SOURCE_DIR<projectname>_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-ofsource編譯,他指的是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中的22,CMAKE_MINOR_VERSION,CMAKE次版本號,好比2.4.6中的43,CMAKE_PATCH_VERSION,CMAKE補丁等級,好比2.4.6 中的64,CMAKE_SYSTEM,系統名稱,好比Linux-2.6.225,CMAKE_SYSTEM_NAME,不包含版本的系統名,好比Linux6,CMAKE_SYSTEM_VERSION,系統版本,好比2.6.227,CMAKE_SYSTEM_PROCESSOR,處理器名稱,好比i686.8,UNIX,在全部的類UNIX平臺爲TRUE,包括OS X和cygwin9,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-target1depend-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指令來處理這個LIST6,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 <arguments to executable>][OUTPUT_VARIABLE <var>][RETURN_VALUE <var>])用於在指定的目錄運行某個程序,經過ARGS添加參數,若是要獲取輸出和返回值,可經過OUTPUT_VARIABLE和RETURN_VALUE分別定義兩個變量.這個指令能夠幫助你在CMakeLists.txt處理過程當中支持任何命令,好比根據系統狀況去修改代碼文件等等。舉個簡單的例子,咱們要在src目錄執行ls命令,並把結果和返回值存下來。能夠直接在src/CMakeLists.txt中添加:EXEC_PROGRAM(ls ARGS "*.c" OUTPUT_VARIABLE LS_OUTPUT RETURN_VALUELS_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] [globbingexpressions]...)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(<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...]])用來調用預約義在CMAKE_MODULE_PATH下的Find<name>.cmake模塊,你也能夠本身定義Find<name>模塊,經過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或<var>_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 WIN32ELSEIF(UNIX)#do something related to UNIXELSEIF(APPLE)#do something related to APPLEENDIF(WIN32)2,WHILEWHILE指令的語法是:WHILE(condition)COMMAND1(ARGS ...)COMMAND2(ARGS ...)...ENDWHILE(condition)其真假判斷條件能夠參考IF指令。3,FOREACHFOREACH指令的使用方法有三種形式: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)最終獲得的輸出是:0123456789103,範圍和步進FOREACH(loop_var RANGE start stop [step])ENDFOREACH(loop_var)從start開始到stop結束,以step爲步進,舉例以下FOREACH(A RANGE 5 15 3)MESSAGE(${A})ENDFOREACH(A)最終獲得的結果是:581114這個指令須要注意的是,知道遇到ENDFOREACH指令,整個語句塊纔會獲得真正的執行。小結:本小節基本涵蓋了經常使用的cmake指令,包括基本指令、查找指令、安裝指令以及控制語句等,特別須要注意的是,在控制語句條件中使用變量,不能用${}引用,而是直接應用變量名。掌握了以上的各類控制指令,你應該徹底能夠經過cmake管理複雜的程序了,下一節,我們將介紹一個比較複雜的例子,經過他來演示本章的一些指令,並介紹模塊的概念。九,複雜的例子:模塊的使用和自定義模塊你如今還會以爲cmake簡單嗎?本章咱們將着重介紹系統預約義的Find模塊的使用以及本身編寫Find模塊,系統中提供了其餘各類模塊,通常狀況須要使用INCLUDE指令顯式的調用,FIND_PACKAGE指令是一個特例,能夠直接調用預約義的模塊。其實使用純粹依靠cmake自己提供的基本指令來管理工程是一件很是複雜的事情,因此,cmake設計成了可擴展的架構,能夠經過編寫一些通用的模塊來擴展cmake.在本章,咱們準備首先介紹一下cmake提供的FindCURL模塊的使用。而後,基於咱們前面的libhello共享庫,編寫一個FindHello.cmake模塊。一,使用FindCURL模塊在/backup/cmake目錄創建t5目錄,用於存放咱們的CURL的例子。創建src目錄,並創建src/main.c,內容以下:#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.linux-ren.org」);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.linux-ren.org的首頁並寫入/tmp/curl-test文件中。創建主工程文件CMakeLists.txtPROJECT(CURLTEST)ADD_SUBDIRECTORY(src)創建src/CMakeLists.txtADD_EXECUTABLE(curltest main.c)如今天然是沒辦法編譯的,咱們須要添加curl的頭文件路徑和庫文件。方法1:直接經過INCLUDE_DIRECTORIES和TARGET_LINK_LIBRARIES指令添加:咱們能夠直接在src/CMakeLists.txt中添加:INCLUDE_DIRECTORIES(/usr/include)TARGET_LINK_LIBRARIES(curltest curl)而後創建build目錄進行外部構建便可。如今咱們要探討的是使用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)對於系統預約義的Find<name>.cmake模塊,使用方法通常如上例所示:每個模塊都會定義如下幾個變量• <name>_FOUND• <name>_INCLUDE_DIR or <name>_INCLUDES• <name>_LIBRARY or <name>_LIBRARIES你能夠經過<name>_FOUND來判斷模塊是否被找到,若是沒有找到,按照工程的須要關閉某些特性、給出提醒或者停止編譯,上面的例子就是報出致命錯誤並終止構建。若是<name>_FOUND爲真,則將<name>_INCLUDE_DIR加入INCLUDE_DIRECTORIES,將<name>_LIBRARY加入TARGET_LINK_LIBRARIES中。咱們再來看一個複雜的例子,經過<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}經過判斷系統是否提供了JPEG庫來決定程序是否支持JPEG功能。二,編寫屬於本身的FindHello模塊。咱們在此前的t3實例中,演示了構建動態庫、靜態庫的過程並進行了安裝。接下來,咱們在t6示例中演示如何自定義FindHELLO模塊並使用這個模塊構建工程:請在創建/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指令,其實他可使用多種參數,QUIET參數,對應與咱們編寫的FindHELLO中的 HELLO_FIND_QUIETLY,若是不指定這個參數,就會執行:MESSAGE(STATUS "Found Hello: ${HELLO_LIBRARY}")REQUIRED參數,其含義是指這個共享庫是不是工程必須的,若是使用了這個參數,說明這個連接庫是必備庫,若是找不到這個連接庫,則工程不能編譯。對應於FindHELLO.cmake模塊中的 HELLO_FIND_REQUIRED變量。一樣,咱們在上面的模塊中定義了HELLO_FOUND,HELLO_INCLUDE_DIR,HELLO_LIBRARY變量供開發者在FIND_PACKAGE指令中使用。OK,下面創建src/main.c,內容爲:#include <hello.h>int main(){HelloFunc();return 0;}創建src/CMakeLists.txt文件,內容以下: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模塊構建工程仍然採用外部編譯的方式,創建build目錄,進入目錄運行:cmake ..咱們能夠從輸出中看到:Found Hello: /usr/lib/libhello.so若是咱們把上面的FIND_PACKAGE(HELLO)修改成FIND_PACKAGE(HELLO QUIET),則不會看到上面的輸出。接下來就可使用make命令構建工程,運行:./src/hello能夠獲得輸出Hello World。說明工程成功構建。四,若是沒有找到hello library呢?咱們能夠嘗試將/usr/lib/libhello.x移動到/tmp目錄,這樣,按照FindHELLO模塊的定義,就找不到hello library了,咱們再來看一下構建結果:cmake ..仍然能夠成功進行構建,可是這時候是沒有辦法編譯的。修改FIND_PACKAGE(HELLO)爲FIND_PACKAGE(HELLO REQUIRED),將hellolibrary定義爲工程必須的共享庫。這時候再次運行cmake ..咱們獲得以下輸出:CMake Error: Could not find hello library.由於找不到libhello.x,因此,整個Makefile生成過程被出錯停止。小結:在本節中,咱們學習瞭如何使用系統提供的Find<NAME>模塊並學習了本身編寫Find<NAME>模塊以及如何在工程中使用這些模塊。後面的章節,咱們會逐漸學習更多的cmake模塊使用方法以及用cmake來管理GTK和QT4工程。