CMake編程實踐(二) 常用語法

CMake常用語法

PROJECT 定義工程名稱

PROJECT(projectname [CXX] [C] [Java])
你可以用這個指令定義工程名稱,並可指定工程支持的語言,支持的語言列表是可以忽略的,
默認情況表示支持所有語言。這個指令隱式的定義了兩個 cmake 變量:
_BINARY_DIR 以及_SOURCE_DIR,例如上一節中使用到的
HELLO_BINARY_DIR 和 HELLO_SOURCE_DIR就是這樣隱式定義的。
同時 cmake 系統也幫助我們預定義了 PROJECT_BINARY_DIR 和 PROJECT_SOURCE_DIR
變量,他們的值分別跟 HELLO_BINARY_DIR 與 HELLO_SOURCE_DIR 一致。

SET 定義變量

SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])
現階段,你只需要瞭解 SET 指令可以用來顯式的定義變量即可。
比如我們用到的是 SET(SOURCE main.c),如果有多個源文件,也可以定義成:
SET(SOURCE main.c test1.c test2.c)。

MESSAGE 輸出信息

MESSAGE([SEND_ERROR | STATUS | FATAL_ERROR] "message to display"
...)

這個指令用於向終端輸出用戶定義的信息,包含了三種類型:SEND_ERROR,產生錯誤,生成過程被跳過。
SATUS,輸出前綴爲—的信息。FATAL_ERROR,立即終止所有 cmake 過程

ADD_EXECUTABLE 定義可執行文件

ADD_EXECUTABLE(Hello ${SOURCE})
定義了這個工程會生成一個文件名爲Hello的可執行文件,相關的源文件是SOURCE中
定義的源文件列表, 你也可以直接寫成ADD_EXECUTABLE(Hello main.cpp)。

INCLUDE_DIRECTORIES 添加頭文件路徑

INCLUDE_DIRECTORIES,其完整語法爲:

INCLUDE_DIRECTORIES([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)

這條指令可以用來向工程添加多個特定的頭文件搜索路徑,路徑之間用空格分割,如果路徑
中包含了空格,可以使用雙引號將它括起來,默認的行爲是追加到當前的頭文件搜索路徑的
後面,你可以通過兩種方式來進行控制搜索路徑添加的方式:
1,CMAKE_INCLUDE_DIRECTORIES_BEFORE,通過 SET 這個 cmake 變量爲 on,可以
將添加的頭文件搜索路徑放在已有路徑的前面。
2,通過 AFTER 或者 BEFORE 參數,也可以控制是追加還是置前。

LINK_DIRECTORIES 添加庫的路徑

LINK_DIRECTORIES(directory1 directory2 ...)

這個指令非常簡單,添加非標準的共享庫搜索路徑,比如,在工程內部同時存在共享庫和可
執行二進制,在編譯時就需要指定一下這些共享庫的路徑。

TARGET_LINK_LIBRARIES 爲target添加共享庫

TARGET_LINK_LIBRARIES 的全部語法是:

TARGET_LINK_LIBRARIES(target library1
                    <debug | optimized> library2
                    ...)

這個指令可以用來爲 target 添加需要鏈接的共享庫,可以用於爲自己編寫的共享庫添加共享庫鏈接。

共享庫的名稱寫法有多種,以下寫法均是可以的:

TARGET_LINK_LIBRARIES(project utils) # 連接libhello.so庫,默認優先鏈接動態庫
TARGET_LINK_LIBRARIES(project libutils.a) # 顯示指定鏈接靜態庫
TARGET_LINK_LIBRARIES(project libutils.so) # 顯示指定鏈接動態庫
TARGET_LINK_LIBRARIES(project -lutils)

e.g:

# 指定庫的位置爲項目根目錄下的lib目錄
LINK_DIRECTORIES(${PROJECT_SOURCE_DIR}/lib_utils/lib)
INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/lib_utils/include)
TARGET_LINK_LIBRARIES(${PROJECT_NAME} utils)

INSTALL 安裝指令

INSTALL 指令用於定義安裝規則,安裝的內容可以包括目標二進制、動態庫、靜態庫以及文件、目錄、腳本等。
提到INSTALL就不得不提一下CMAKE_INSTALL_PREFIX變量,類似於 configure 腳本的 –prefix,用於指定安裝的位置,默認位置是/usr/local/,不同的系統默認位置可能不一樣,常見的使用方法看
起來是這個樣子:

cmake -DCMAKE_INSTALL_PREFIX=/usr .

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 目錄安裝到 /share/myproj,將 scripts/中的內容安裝到
/share/myproj
不包含目錄名爲 CVS 的目錄,對於 scripts/*文件指定權限爲 OWNER_EXECUTE
OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ.

安裝時 CMAKE 腳本的執行:

INSTALL([[SCRIPT <file>] [CODE <code>]] [...])

SCRIPT 參數用於在安裝時調用 cmake 腳本文件(也就是.cmake 文件)
CODE 參數用於執行 CMAKE 指令,必須以雙引號括起來。比如:

INSTALL(CODE "MESSAGE(\"Sample install message.\")")

配置CMAKE環境變量

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 等),
則可以通過這些變量就行彌補。

爲了將程序更智能一點,我們可以使用 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 模塊都
會受益。

ADD_DEFINITIONS

向 C/C++編譯器添加-D 定義,比如:
ADD_DEFINITIONS(-DENABLE_DEBUG -DENABLE_CONFIG_FILE),參數之間用空格分割。
如果你的代碼中定義了#ifdef ENABLE_DEBUG #endif,這個代碼塊就會生效。
如果要添加其他的編譯器開關,可以通過 CMAKE_C_FLAGS 變量和 CMAKE_CXX_FLAGS 變
量設置。

ADD_DEPENDENCIES

定義 target 依賴的其他 target,確保在編譯本 target 之前,其他的 target 已經被構建。

ADD_DEPENDENCIES(target-name depend-target1depend-target2 ...)

ADD_EXECUTABLE

ADD_EXECUTABLE、ADD_SUBDIRECTORY 前面已經介紹過了,這裏不再羅唆。

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 參數的意思是這個庫不會被默認構建,除非有其他的組件依賴或者手工構建。

SET_TARGET_PROPERTIES

其基本語法是:

SET_TARGET_PROPERTIES(target1 target2 ...
    PROPERTIES prop1 value1
    prop2 value2 ...)

在目標上設置屬性。該命令的語法是列出要更改的所有目標,然後提供下一步要設置的值。對於動態庫,可以用來指定動態庫版本和API版本以及設置庫名稱等屬性。設置以後使用get_property() 或者 get_target_property() 獲取到設置的屬性。
關於目標屬性,請查看官方給出的定義:
https://cmake.org/cmake/help/latest/manual/cmake-properties.7.html#target-properties

e.g:

SET_TARGET_PROPERTIES(utilsbox_shared PROPERTIES OUTPUT_NAME "utilsbox")

GET_TARGET_PROPERTY

與SET_TARGET_PROPERTIES對應的就是GET_TARGET_PROPERTY

GET_TARGET_PROPERTY(VAR target property)

具體用法如下例,我們向 lib/CMakeListst.txt 中添加:
GET_TARGET_PROPERTY(OUTPUT_VALUE utilsbox_shared OUTPUT_NAME)
MESSAGE(STATUS “This is the utilsbox_shared OUTPUT_NAME:”${OUTPUT_VALUE})
如果沒有這個屬性定義,則返回 NOTFOUND.

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 都是無效的。

比如我們前面的Hello例子,可以在工程主 CMakeLists.txt 中添加

# 這裏相當於定義一個指定的測試程序,以便make test調用
ADD_TEST(mytest ${PROJECT_BINARY_DIR}/bin/Hello)
ENABLE_TESTING()

生成 Makefile 後,就可以運行 make test 來執行測試了。

AUX_SOURCE_DIRECTORY

基本語法是:
AUX_SOURCE_DIRECTORY(dir VARIABLE)
作用是發現一個目錄下所有的源代碼文件並將列表存儲在一個變量中,這個指令臨時被用來
自動構建源文件列表。因爲目前 cmake 還不能自動發現新添加的源文件。
比如

AUX_SOURCE_DIRECTORY(. SRC_LIST)
ADD_EXECUTABLE(main ${SRC_LIST})

CMAKE_MINIMUM_REQUIRED

其語法爲 CMAKE_MINIMUM_REQUIRED(VERSION versionNumber [FATAL_ERROR])
比如 CMAKE_MINIMUM_REQUIRED(VERSION 2.5 FATAL_ERROR)
如果 cmake 版本小與 2.5,則出現嚴重錯誤,整個過程中止。

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 語句,後面的控制指令會提到。
或者:

# 在 CMakeLists.txt 處理過程中執行命令
# EXEC_PROGRAM(Executable [directory in which to run][ARGS <arguments to executable>][OUTPUT_VARIABLE <var>][RETURN_VALUE <var>])
# e.g 獲取當前時間
EXEC_PROGRAM(date ARGS "+'%Y/%m/%d %H:%M:%S'" 
                    OUTPUT_VARIABLE CMD_OUTPUT
                    RETURN_VALUE CMD_RVALUE)
# CMD_RVALUE 如果返回0怎表示腳本執行成功

IF(NOT CMD_RVALUE)
MESSAGE(STATUS "date result: " ${CMD_OUTPUT})
ENDIF(NOT CMD_RVALUE)

FILE 指令

文件操作指令,基本語法爲:

FILE(WRITE filename "message to write"... )
FILE(APPEND filename "message to write"... )
FILE(READ filename variable)
FILE(GLOB variable [RELATIVE path] [globbing
expressions]...)
FILE(GLOB_RECURSE variable [RELATIVE path]
[globbing expressions]...)
FILE(REMOVE [directory]...)
FILE(REMOVE_RECURSE [directory]...)
FILE(MAKE_DIRECTORY [directory]...)
FILE(RELATIVE_PATH variable directory file)
FILE(TO_CMAKE_PATH path result)
FILE(TO_NATIVE_PATH path result)

這裏的語法都比較簡單,不在展開介紹了

INCLUDE 指令

用來載入 CMakeLists.txt 文件,也用於載入預定義的 cmake 模塊.
INCLUDE(file1 [OPTIONAL])
INCLUDE(module [OPTIONAL])
OPTIONAL 參數的作用是文件不存在也不會產生錯誤。
你可以指定載入一個文件,如果定義的是一個模塊,那麼將在 CMAKE_MODULE_PATH 中搜
索這個模塊並載入。
載入的內容將在處理到 INCLUDE 語句是直接執行

在CMakeLists.txt 中添加

# 定義自己的cmake模塊所在的路徑
SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
# 加載運行自己的cmake模塊
INCLUDE("CMakeListsDemo")
./cmake
└── CMakeListsDemo.cmake

cat CMakeListsDemo.cmake

CMAKE_MINIMUM_REQUIRED(VERSION 3.0)

MESSAGE(STATUS "This is a CMakeListsDemo")

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 模塊的
編寫。

e.g:

FIND_FILE(CONFIG_FILE config ${PROJECT_SOURCE_DIR})
IF(NOT CONFIG_FILE)
MESSAGE(STATUS "config not found")
ENDIF(NOT CONFIG_FILE)

FIND_LIBRARY 示例:

FIND_LIBRARY(libX X11 /usr/lib)
IF(NOT libX)
MESSAGE(FATAL_ERROR “libX not found”)
ENDIF(NOT libX)

math 數學運算

EXPR計算數學表達式然後通過output變量返回計算結果,該命令支持的運算符包括:+ - * / % ^ ~ << >> ;它們的含義與C語言中的完全一致。
math 數學表達式

math(EXPR <output variable> <math expression>) 

e.g:

math(EXPR VAR "${VAR} - 1") 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章