一,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_DIR
PROJECT_BINARY_DIR
<projectname>_BINARY_DIR
這三個變量指代的內容是一致的,如果是 in source 編譯,指的就是工程頂層目錄,如果是 out-of-source 編譯,指的是工程編譯發生的目錄。PROJECT_BINARY_DIR 跟其他指令稍有區別,現在,你可以理解為他們是一致的。
2,CMAKE_SOURCE_DIR
PROJECT_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-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_BUILD_TYPE
生成 debug 版和 release 版的程序。
可以的取值是 Debug Release RelWithDebInfo 和 MinSizeRel。當這個變量值為 Debug 的時候,CMake 會使用變量CMAKE_CXX_FLAGS_DEBUG 和 CMAKE_C_FLAGS_DEBUG 中的字符串作為編譯選項生成 Makefile,當這個變量值為 Release 的時候,工程會使用變量CMAKE_CXX_FLAGS_RELEASE 和 CMAKE_C_FLAGS_RELEASE 選項生成 Makefile。
現假設項目中只有一個文件 main.cpp ,下面是一個可以選擇生成 debug 版和 release 版的程序的 CMakeList.txt :
1 PROJECT(main)
2 CMAKE_MINIMUM_REQUIRED(VERSION 2.6)
3 SET(CMAKE_SOURCE_DIR .)
4
5 SET(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb")
6 SET(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")
7
8 AUX_SOURCE_DIRECTORY(. DIR_SRCS)
9 ADD_EXECUTABLE(main ${DIR_SRCS})
第 5 和 6 行設置了兩個變量 CMAKE_CXX_FLAGS_DEBUG 和
CMAKE_CXX_FLAGS_RELEASE, 這兩個變量是分別用於 debug 和 release 的編譯選項。
編輯 CMakeList.txt 后需要執行 ccmake 命令生成 Makefile 。在進入項目的根目錄,輸入
"ccmake ." 進入一個圖形化界面,如下圖所示:
圖 5. ccmake 的界面
按照界面中的提示進行操作,按 "c" 進行 configure ,這時界面中顯示出了配置變量
CMAKE_BUILD_TYPE 的條目。如下圖所示:
圖 6. 執行了 configure 以后 ccmake 的界面
下面我們首先生成 Debug 版的 Makefile :將變量 CMAKE_BUILD_TYPE 設置為
Debug ,按 "c" 進行 configure ,按 "g" 生成 Makefile 並退出。這時執行命令
find * | xargs grep "O0" 后結果如下:
清單 8 find * | xargs grep "O0"的執行結果
CMakeFiles/main.dir/flags.make:CXX_FLAGS = -O0 -Wall -g -ggdb
CMakeFiles/main.dir/link.txt:/usr/bin/c++ -O0 -Wall -g -ggdb
CMakeFiles/main.dir/main.cpp.o -o main -rdynamic
CMakeLists.txt:SET(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb")
這個結果說明生成的 Makefile 中使用了變量 CMAKE_CXX_FLAGS_DEBUG 作為編譯時的
參數。
下面我們將生成 Release 版的 Makefile :再次執行命令 "ccmake ." 將變量
CMAKE_BUILD_TYPE 設置為 Release ,生成 Makefile 並退出。執行命令
find * | xargs grep "O0" 后結果如下:
清單 9 find * | xargs grep "O0"的執行結果
CMakeLists.txt:SET(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb")
而執行命令 find * | xargs grep "O3" 后結果如下:
清單 10. find * | xargs grep "O3"的執行結果
CMakeCache.txt:CMAKE_CXX_FLAGS_RELEASE:STRING=-O3 -DNDEBUG
CMakeCache.txt:CMAKE_C_FLAGS_RELEASE:STRING=-O3 -DNDEBUG
CMakeFiles/main.dir/flags.make:CXX_FLAGS = -O3 -Wall
CMakeFiles/main.dir/link.txt:/usr/bin/c++ -O3 -Wall
CMakeFiles/main.dir/main.cpp.o -o main -rdynamic
CMakeLists.txt:SET(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")
這兩個結果說明生成的 Makefile 中使用了變量 CMAKE_CXX_FLAGS_RELEASE 作為編譯
時的參數。
CMAKE_C_COMPILER
指定C編譯器,通常,CMake運行時能夠自動檢測C語言編譯器。進行嵌入式系統開發時,通常需要設置此變量,指定交叉編譯器。
CMAKE_CXX_COMPILER
指定C++編譯器
CMAKE_C_FLAGS
指定編譯C文件時編譯選項,比如-g指定產生調試信息。也可以通過add_definitions命令添加編譯選項。
EXECUTABLE_OUTPUT_PATH
指定可執行文件存放的路徑。
LIBRARY_OUTPUT_PATH
指定庫文件放置的路徑
四,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 ...)
讓一個頂層目標依賴於其他的頂層目標。一個頂層目標是由命令ADD_EXECUTABLE,ADD_LIBRARY,或者ADD_CUSTOM_TARGET產生的目標。為這些命令的輸出引入依賴性可以保證某個目標在其他的目標之前被構建。查看ADD_CUSTOM_TARGET和ADD_CUSTOM_COMMAND命令的DEPENDS選項,可以了解如何根據自定義規則引入文件級的依賴性。查看SET_SOURCE_FILES_PROPERTIES命令的OBJECT_DEPENDS選項,可以了解如何為目標文件引入文件級的依賴性。
3,ADD_EXECUTABLE、ADD_LIBRARY、ADD_SUBDIRECTORY
ADD_EXECUTABLE(可執行文件名 生成該可執行文件的源文件)
說明源文件需要編譯出的可執行文件名
例:
ADD_EXECUTABLE(hello ${SRC_LIST})
說明SRC_LIST變量中的源文件需要編譯出名為hello的可執行文件
ADD_LIBRARY(libname [SHARED|STATIC|MODULE] [EXCLUDE_FROM_ALL] source1 source2 ... sourceN)
生成動態靜態庫
例:
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
ADD_SUBDIRECTORY(src_dir [binary_dir] [EXCLUDE_FROM_ALL])
向當前工程添加存放源文件的子目錄,並可以指定中間二進制和目標二進制的存放位置
EXCLUDE_FROM_ALL含義:將這個目錄從編譯過程中排除
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 <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_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 [LIMIT numBytes] [OFFSET offset] [HEX])
file(STRINGS filename variable [LIMIT_COUNT num]
[LIMIT_INPUT numBytes] [LIMIT_OUTPUT numBytes]
[LENGTH_MINIMUM numBytes] [LENGTH_MAXIMUM numBytes]
[NEWLINE_CONSUME] [REGEX regex]
[NO_HEX_CONVERSION])
file(GLOB variable [RELATIVE path] [globbing expressions]...)
file(GLOB_RECURSE variable [RELATIVE path]
[FOLLOW_SYMLINKS] [globbing expressions]...)
file(RENAME <oldname> <newname>)
file(REMOVE [file1 ...])
file(REMOVE_RECURSE [file1 ...])
file(MAKE_DIRECTORY [directory1 directory2 ...])
file(RELATIVE_PATH variable directory file)
file(TO_CMAKE_PATH path result)
file(TO_NATIVE_PATH path result)
file(DOWNLOAD url file [TIMEOUT timeout] [STATUS status] [LOG log]
[EXPECTED_MD5 sum] [SHOW_PROGRESS])
WRITE選項將會寫一條消息到名為filename的文件中。如果文件已經存在,該命令會覆蓋已有的文件;如果文件不存在,它將創建該文件。
APPEND選項和WRITE選項一樣,將會寫一條消息到名為filename的文件中,只是該消息會附加到文件末尾。
READ選項將會讀一個文件中的內容並將其存儲在變量里。讀文件的位置從offset開始,最多讀numBytes個字節。如果指定了HEX參數,二進制代碼將會轉換為十六進制表達方式,並存儲在變量里。
STRINGS將會從一個文件中將一個ASCII字符串的list解析出來,然后存儲在variable變量中。文件中的二進制數據會被忽略。回車換行符會被忽略。它也可以用在Intel的Hex和Motorola的S-記錄文件;讀取它們時,它們會被自動轉換為二進制格式。可以使用NO_HEX_CONVERSION選項禁止這項功能。LIMIT_COUNT選項設定了返回的字符串的最大數量。LIMIT_INPUT設置了從輸入文件中讀取的最大字節數。LIMIT_OUTPUT設置了在輸出變量中存儲的最大字節數。LENGTH_MINIMUM設置了要返回的字符串的最小長度;小於該長度的字符串會被忽略。LENGTH_MAXIMUM設置了返回字符串的最大長度;更長的字符串會被分割成不長於最大長度的字符串。NEWLINE_CONSUME選項允許新行被包含到字符串中,而不是終止它們。REGEX選項指定了一個待返回的字符串必須滿足的正則表達式。典型的使用方式是:
file(STRINGS myfile.txt myfile)
該命令在變量myfile中存儲了一個list,該list中每個項是輸入文件中的一行文本。
GLOB選項將會為所有匹配查詢表達式的文件生成一個文件list,並將該list存儲進變量variable里。文件名查詢表達式與正則表達式類似,只不過更加簡單。如果為一個表達式指定了RELATIVE標志,返回的結果將會是相對於給定路徑的相對路徑。文件名查詢表達式的例子有:
*.cxx - 匹配所有擴展名為cxx的文件。
*.vt? - 匹配所有擴展名是vta,...,vtz的文件。
f[3-5].txt - 匹配文件f3.txt, f4.txt, f5.txt。
GLOB_RECURSE選項將會生成一個類似於通常的GLOB選項的list,只是它會尋訪所有那些匹配目錄的子路徑並同時匹配查詢表達式的文件。作為符號鏈接的子路徑只有在給定FOLLOW_SYMLINKS選項或者cmake策略CMP0009被設置為NEW時,才會被尋訪到。參見cmake --help-policy CMP0009 查詢跟多有用的信息。
使用遞歸查詢的例子有:
/dir/*.py - 匹配所有在/dir及其子目錄下的python文件。
MAKE_DIRECTORY選項將會創建指定的目錄,如果它們的父目錄不存在時,同樣也會創建。(類似於mkdir命令——譯注)
RENAME選項對同一個文件系統下的一個文件或目錄重命名。(類似於mv命令——譯注)
REMOVE選項將會刪除指定的文件,包括在子路徑下的文件。(類似於rm命令——譯注)
REMOVE_RECURSE選項會刪除給定的文件以及目錄,包括非空目錄。(類似於rm -r 命令——譯注)
RELATIVE_PATH選項會確定從direcroty參數到指定文件的相對路徑。
TO_CMAKE_PATH選項會把path轉換為一個以unix的 / 開頭的cmake風格的路徑。輸入可以是一個單一的路徑,也可以是一個系統路徑,比如"$ENV{PATH}"。注意,在調用TO_CMAKE_PATH的ENV周圍的雙引號只能有一個參數(Note the double quotes around the ENV call TO_CMAKE_PATH only takes one argument. 原文如此。quotes和后面的takes讓人后糾結,這句話翻譯可能有誤。歡迎指正——譯注)。
TO_NATIVE_PATH選項與TO_CMAKE_PATH選項很相似,但是它會把cmake風格的路徑轉換為本地路徑風格:windows下用\,而unix下用/。
DOWNLOAD 將給定的URL下載到指定的文件中。如果指定了LOG var選項,下載日志將會被輸出到var中。如果指定了STATUS var選項,下載操作的狀態會被輸出到var中。該狀態返回值是一個長度為2的list。list的第一個元素是操作的數字返回值,第二個返回值是錯誤的字符串值。錯誤信息如果是數字0,操作中沒有發生錯誤。如果指定了TIMEOUT time選項,在time秒之后,操作會超時退出;time應該是整數。如果指定了EXPECTED_MD5 sum選項,下載操作會認證下載的文件的實際MD5和是否與期望值匹配。如果不匹配,操作將返回一個錯誤。如果指定了SHOW_PROGRESS選項,進度信息會以狀態信息的形式被打印出來,直到操作完成。
file命令還提供了COPY和INSTALL兩種格式:
file(<COPY|INSTALL> files... DESTINATION <dir>
[FILE_PERMISSIONS permissions...]
[DIRECTORY_PERMISSIONS permissions...]
[NO_SOURCE_PERMISSIONS] [USE_SOURCE_PERMISSIONS]
[FILES_MATCHING]
[[PATTERN <pattern> | REGEX <regex>]
[EXCLUDE] [PERMISSIONS permissions...]] [...])
COPY版本把文件、目錄以及符號連接拷貝到一個目標文件夾。相對輸入路徑的評估是基於當前的源代碼目錄進行的,相對目標路徑的評估是基於當前的構建目錄進行的。復制過程將保留輸入文件的時間戳;並且如果目標路徑處存在同名同時間戳的文件,復制命令會把它優化掉。賦值過程將保留輸入文件的訪問權限,除非顯式指定權限或指定NO_SOURCE_PERMISSIONS選項(默認是USE_SOURCE_PERMISSIONS)。參見install(DIRECTORY)命令中關於權限(permissions),PATTERN,REGEX和EXCLUDE選項的文檔。
INSTALL版本與COPY版本只有十分微小的差別:它會打印狀態信息,並且默認使用NO_SOURCE_PERMISSIONS選項。install命令生成的安裝腳本使用這個版本(它會使用一些沒有在文檔中涉及的內部使用的選項。)
9,INCLUDE 指令,用來載入 CMakeLists.txt 文件,也用於載入預定義的 cmake 模塊.
INCLUDE(file1 [OPTIONAL])
INCLUDE(module [OPTIONAL])
OPTIONAL 參數的作用是文件不存在也不會產生錯誤。
你可以指定載入一個文件,如果定義的是一個模塊,那么將在 CMAKE_MODULE_PATH 中搜索這個模塊並載入。
載入的內容將在處理到 INCLUDE 語句時直接執行。
https://cmake.org/cmake/help/v3.0/command/include.html
二,INSTALL 指令
INSTALL 系列指令已經在前面的章節有非常詳細的說明,這里不在贅述,可參考前面的安裝部分。
三,FIND_系列指令
FIND_系列指令主要包含一下指令:
FIND_FILE(<VAR> name1 path1 path2 ...)
VAR 變量代表找到的文件全路徑,包含文件名
FIND_PATH(<VAR> name1 path1 path2 ...)
指明頭文件查找的路徑,原型如下:
該命令在參數 path* 指示的目錄中查找文件 name1 並將查找到的路徑保存在變量 VAR 中。
FIND_LIBRARY(<VAR> name1 path1 path2 ...)
用法同FIND_PATH,表明庫查找路徑。
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)將其放入工程的某個目錄中供工程使用。
這條命令執行后,CMake 會到變量 CMAKE_MODULE_PATH 指示的目錄中查找文件 Findname.cmake 並執行。
QUIET 參數:
對應於Find<name>.cmake模塊中的 NAME_FIND_QUIETLY,如果不指定這個參數,就會執行:
MESSAGE(STATUS "Found NAME: ${NAME_LIBRARY}")
指定了QUIET就不輸出指令。
REQUIRED 參數
其含義是指是否是工程必須的,如果使用了這個參數,說明是必須的,如果找不到,則工程不能編譯。對應於Find<name>.cmake模塊中的 NAME_FIND_REQUIRED 變量。
示例:
FIND_PACKAGE( libdb_cxx REQUIRED)
這條命令執行后,CMake 會到變量 CMAKE_MODULE_PATH 指示的目錄中查找文件 Findlibdb_cxx.cmake 並執行。
FIND_PACKAGE 的使用方法和 Find 模塊的編寫參見下面:編寫屬於自己的 FindHello 模塊。
FIND_LIBRARY 示例:
FIND_LIBRARY(libX X11 /usr/lib)
IF(NOT libX)
MESSAGE(FATAL_ERROR “libX not found”)
ENDIF(NOT libX)
四,控制指令:
1,IF 指令,基本語法為:
IF(expression_r_r)
# THEN section.
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
ELSE(expression_r_r)
# ELSE section.
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
ENDIF(expression_r_r)
另外一個指令是 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 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
5
6
7
8
9
10
3,范圍和步進
FOREACH(loop_var RANGE start stop [step])
ENDFOREACH(loop_var)
從 start 開始到 stop 結束,以 step 為步進,
舉例如下
FOREACH(A RANGE 5 15 3)
MESSAGE(${A})
ENDFOREACH(A)
最終得到的結果是:
5
8
11
14
這個指令需要注意的是,知道遇到 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.txt
PROJECT(CURLTEST)
ADD_SUBDIRECTORY(src)
建立 src/CMakeLists.txt
ADD_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()
{undefined
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),將 hello library 定義為工程必須的共享庫。
這時候再次運行 cmake ..
我們得到如下輸出:
CMake Error: Could not find hello library.
因為找不到 libhello.x,所以,整個 Makefile 生成過程被出錯中止。
小結:
在本節中,我們學習了如何使用系統提供的 Find<NAME>模塊並學習了自己編寫
Find<NAME>模塊以及如何在工程中使用這些模塊。
后面的章節,我們會逐漸學習更多的 cmake 模塊使用方法以及用 cmake 來管理 GTK 和 QT4工程。
http://blog.csdn.net/gubenpeiyuan/article/details/8667279