【cmake系列使用教程】java
cmake使用教程(一)-起步linux
cmake使用教程(四)-文件生成器github
cmake使用教程(八)-macro和functionbash
這個系列的文章翻譯自官方cmake教程:cmake tutorial。app
示例程序地址:github.com/rangaofei/t…
不會僅僅停留在官方教程。本人做爲一個安卓開發者,實在是沒有linux c程序開發經驗,望大佬們海涵。教程是在macos下完成,大部分linux我也測試過,有特殊說明的我會標註出來。本教程基於cmake-3.10.2,同時認爲你已經安裝好cmake。
「蛋疼的不止語法,還有文檔」
本節講的命令格式遵循以下語法:
格式 | 註釋 |
---|---|
<command> |
必須填寫的 |
[command] |
可寫也可不寫的 |
a\|b |
a或者b都可以 |
cmake能識別CMakeLists.txt和*.cmake格式的文件。cmake可以以三種方式 來組織文件:
類別 | 文件格式 |
---|---|
Dierctory (文件夾) |
CMakeLists.txt |
Script (腳本) |
<script>.cmake |
Module (模塊) |
<module>.cmake |
本系列主要以CMakeLists.txt的語法爲主要講解內容,至於編寫這蛋疼的腳本和模塊假如我還寫的下去的話我就寫。
當CMake處理一個項目時,入口點是一個名爲CMakeLists.txt
的源文件,這個必定是根目錄下的CMakeLists.txt。這個文件包含整個工程的構建規範,當咱們有多個子文件夾須要編譯時,使用add_subdirectory(<dir_name>)
命令來爲構建添加子目錄。添加的每一個子目錄也必須包含一個CMakeLists.txt
文件做爲該子目錄的入口點。每一個子目錄的CMakeLists.txt文件被處理時,CMake在構建樹中生成相應的目錄做爲默認的工做和輸出目錄。記住這一點很是關鍵,這樣咱們就可使用外部構建了,而沒必要每次都使用蛋疼的內部構建,而後刪除一堆文件才能重新構建。
一個單獨的<script>.cmake
源文件可使用cmake命令行工具cmake -P <script>.cmake
選項來執行腳本。腳本模式只是在給定的文件中運行命令,而且不生成構建系統。它不容許CMake命令定義或執行構建目標。
在Directory或Script中,CMake代碼可使用include()命令來加載.cmake。cmake內置了許多模塊用來幫助咱們構建工程,前邊文章中提到的CheckFunctionExists
。也能夠提供本身的模塊,並在CMAKE_MODULE_PATH
變量中指定它們的位置。
先看一下定義的方式
名稱 | 表達式 | 我認爲的 | 例子 |
---|---|---|---|
space | <match '[ \t]+'> |
任意個空格或者tab | a b |
newline | <match '\n'> |
換行符 | a\nb |
line_comment | '#' <any text not starting in a bracket_argument and not containing a newline> |
以'#"開頭,不在'[]'塊中,不包含換行符 | #bus |
separation | space\|newline |
空格或者換行 | a b=a\nb |
lineending | linecomment?newline |
從註釋開頭到換行符都不執行 | |
command_invocation | space* identifier space* '(' arguments ')' |
||
quoted_argument | "quoted_element* " |
用引號包裹的參數 | "a" |
文檔看起來很蛋疼,我直接寫一個最簡單的
add_executable(hello world.c foo.c) #這是一個註釋
複製代碼
也等於
add_executable(hello
world.c
foo.c)
#這是一個註釋
複製代碼
就是這麼easy。注意參數這一塊,能夠用引號包裹起來,這表明一個參數,假如一行不能寫完,則用\\
符號來表示鏈接成一行,也能夠不用引號,可是假如參數帶有分隔符,則會被認爲是多個參數。
定義變量經常使用的函數是set(KEY VALUE)
,取消定義變量是unset(KEY)
。 它們的值始終是string類型的,有些命令可能將字符串解釋爲其餘類型的值。變量名是區分大小寫的,可能包含任何文本,可是咱們建議只使用字母數字字符加上_和-這樣的名稱。
變量的做用域和java基本一致,很少作講解。
變量引用的形式爲${variable_name}
,並在引用的參數或未引用的參數中進行判斷。變量引用被變量的值替換,或者若是變量沒有設置,則由空字符串替換。變量引用能夠嵌套,並從內部進行替換,例如${outer_${inner_variable}veriable}
。 環境變量引用的形式爲$ENV{VAR},並在相同的上下文中做爲正常變量引用。
這算是進入正文了。扯淡的介紹就很少講了,直接乾貨。
可執行文件和庫是使用add_executable()和add_library()命令定義的。生成的二進制文件有合適的前綴、後綴和針對平臺的擴展。二進制目標之間的依賴關係使用target_link_libraries()命令表示。
add_library(archive archive.cpp zip.cpp lzma.cpp)
add_executable(zipapp zipapp.cpp)
target_link_libraries(zipapp archive)
複製代碼
在構建c程序的時候,由於要生成可執行文件,add_executable
是必須的;構建安卓動態庫的時候,add_library
是必須的,由於jni須要調用動態庫。
前邊提到過cmake的構建命令cmake .
,也就是在當前目錄構建工程,這樣會生成一系列的緩存文件在當前目錄,假如咱們須要從新構建須要刪除這些文件,其中CMakeCache.txt是必須刪除的,不然不會構建最新的程序。 看一下咱們在工程根目錄下執行cmake
後是什麼狀況:
~/Desktop/Tutorial/Step1/ tree -L 1
.
├── CMakeCache.txt
├── CMakeFiles
├── CMakeLists.txt
├── Makefile
├── Tutorial
├── TutorialConfig.h
├── TutorialConfig.h.in
├── cmake_install.cmake
└── tutorial.cxx
複製代碼
生成的文件與源文件交叉在一塊兒,至關混亂。咱們能夠採用外部構建來分隔源文件與生成的文件,當咱們須要清空緩存從新構建項目時,就能夠刪除這個文件夾下的全部內容,從新運行構建命令,保持源文件的整潔,從而更容易管理項目。
首先新建一個文件夾build
。這個文件夾就是咱們用來存放生成的文件的目錄,而後進入該目錄,執行構建命令。
mkdir build # 建立build目錄
cd build # 進入build目錄
cmake .. # 由於程序入口構建文件在項目根目錄下,採用相對路徑上級目錄來使用根目錄下的構建文件
複製代碼
此時能夠看到生成的文件所有在build文件夾下了,構建徹底沒問題。
~/Desktop/Tutorial/Step1/ tree -L 2
.
├── CMakeLists.txt
├── TutorialConfig.h.in
├── build
│ ├── CMakeCache.txt
│ ├── CMakeFiles
│ ├── Makefile
│ ├── TutorialConfig.h
│ └── cmake_install.cmake
└── tutorial.cxx
複製代碼
之後的項目講解中將所有使用外部構建。
下面是我專門爲講解一些基本指令編寫的代碼
cmake_minimum_required (VERSION 2.6)
project (Tutorial)
message(STATUS ${PROJECT_NAME})
message(STATUS ${PROJECT_SOURCE_DIR})
message(STATUS ${PROJECT_BINARY_DIR})
message(STATUS ${Tutorial_SOURCE_DIR})
message(STATUS ${Tutorial_BINARY_DIR})
# The version number.
set (Tutorial_VERSION_MAJOR 1)
set (Tutorial_VERSION_MINOR 0)
# configure a header file to pass some of the CMake settings
# to the source code
configure_file (
"${PROJECT_SOURCE_DIR}/TutorialConfig.h.in"
"${PROJECT_BINARY_DIR}/TutorialConfig.h"
)
# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
include_directories("${PROJECT_BINARY_DIR}")
# add the executable
add_executable(Tutorial tutorial.cxx)
複製代碼
message([<mode>] "message to display" ...)
複製代碼
mode有如下幾個模式
模式 | 做用 |
---|---|
(none) | Important information |
STATUS | Incidental information |
WARNING | CMake Warning, continue processing |
AUTHOR_WARNING | CMake Warning (dev), continue processing |
SEND_ERROR | CMake Error, continue processing,but skip generation |
FATAL_ERROR | CMake Error, stop processing and generation |
DEPRECATION | CMake Deprecation Error or Warning if variable CMAKE_ERROR_DEPRECATED or CMAKE_WARN_DEPRECATED is enabled, respectively, else no message. |
STATUS咱們常常用到。
cmake_minimum_required (VERSION 2.6)
複製代碼
爲一個項目設置cmake的最低要求版本,並更新策略設置以匹配給定的版本(策略設置我永遠也不會講了)。不管是構建項目仍是構建庫,都須要這個命令。 它的語法是這樣的
cmake_minimum_required(VERSION major.minor[.patch[.tweak]]
[FATAL_ERROR])
複製代碼
版本號必須指定主次代號,後邊的可選,請忽略可選項[FATAL_ERROR]
,徹底沒用。
假如你指定的版本號大於你安裝的cmake版本,將會中止構建並拋出一個錯誤:
CMake Error at CMakeLists.txt:1 (cmake_minimum_required):
CMake 3.11 or higher is required. You are running version 3.10.2
-- Configuring incomplete, errors occurred!
複製代碼
cmake_minimum_required
必須在項目根目錄下的最開始調用,也就是project()
以前。在function()中調用該指令也能夠,做用域將侷限在函數內,可是必須以不影響全局使用爲前提
project (Tutorial)
複製代碼
指定項目的名稱爲Tutorial
,構建項目必須使用這個命令,構建庫能夠不指定。文檔以下:
project(<PROJECT-NAME> [LANGUAGES] [<language-name>...])
project(<PROJECT-NAME>
[VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
[DESCRIPTION <project-description-string>]
[LANGUAGES <language-name>...])
複製代碼
設置項目名稱並將該名稱存儲在PROJECT_NAME
變量中。同時也指定了四個變量:
PROJECT_SOURCE_DIR, <PROJECT-NAME>_SOURCE_DIR
PROJECT_BINARY_DIR, <PROJECT-NAME>_BINARY_DIR
複製代碼
可是咱們通常只使用前一個,這樣更容易更改。在上邊的代碼中咱們用message輸出了這些變量的信息,執行構建命令後日志輸出:
-- Tutorial
-- /Users/saka/Desktop/Tutorial/Step1
-- /Users/saka/Desktop/Tutorial/Step1/build
-- /Users/saka/Desktop/Tutorial/Step1
-- /Users/saka/Desktop/Tutorial/Step1/build
複製代碼
能夠看到這幾個變量確實輸出了正確的值。
咱們也能夠在指定項目名稱時直接指定版本號,假如沒有指定,則版本號爲空。 版本號存儲在下邊幾個變量中:
PROJECT_VERSION, <PROJECT-NAME>_VERSION
PROJECT_VERSION_MAJOR, <PROJECT-NAME>_VERSION_MAJOR
PROJECT_VERSION_MINOR, <PROJECT-NAME>_VERSION_MINOR
PROJECT_VERSION_PATCH, <PROJECT-NAME>_VERSION_PATCH
PROJECT_VERSION_TWEAK, <PROJECT-NAME>_VERSION_TWEAK
複製代碼
一般咱們推薦使用前一個。如今測試一下,在CMakeLists.txt文件中修改代碼:
project (Tutorial
VERSION 1.2.3
DESCRIPTION "this is description"
LANGUAGES CXX)
message(STATUS ${PROJECT_VERSION})
message(STATUS ${PROJECT_VERSION_MAJOR})
message(STATUS ${PROJECT_VERSION_MINOR})
message(STATUS ${PROJECT_VERSION_PATCH})
message(STATUS ${PROJECT_VERSION_TWEAK})
message(STATUS ${PROJECT_DESCRIPTION})
複製代碼
輸出日誌以下:
-- 1.2.3
-- 1
-- 2
-- 3
--
-- this is description
複製代碼
在這設置版本號和用set設置版本號效果同樣,取最後一次設置的值。因爲咱們沒有指定tweak版本,因此爲空,同時看到description被存儲到PROJECT_DESCRIPTION
這個變量中了。
能夠經過設置LANGUAGES
來指定編程語言是C、CXX(即c++)或者Fortran等,若是沒有設置此項,默認啓用C和CXX。設置爲NONE,或者只寫LANGUAGES
關鍵字而不寫具體源語言,能夠跳過啓用任何語言。通常都是用cmake來編譯c或者c++程序,因此用默認的就能夠了。
configure_file
該命令的做用是複製文件到另外一個地方並修改文件內容。語法以下:
configure_file(<input> <output>
[COPYONLY] [ESCAPE_QUOTES] [@ONLY]
[NEWLINE_STYLE [UNIX|DOS|WIN32|LF|CRLF] ])
複製代碼
將文件複製到
input文件的定義形式爲:
#cmakedefine VAR ..
複製代碼
通過configure後生成的文件內容被替換爲:
#define VAR ... //替換成功
/* #undef VAR */ //未定義的變量
複製代碼
生成的文件將會保留在'#'與'cmakedefine'之間的空格和製表符。
此處有一點需說明,如今clion默認使用cmake來構建程序,可是在clion中不支持
cmakedefine
關鍵字,因此能夠直接使在input文件中填寫#define VAR ...
來編寫宏定義,生成的結果與上邊徹底同樣。clion有一個問題,就是直接用cmakedefine
定義宏的時候假如#與cmakedefine之間有空格則不會替換cmakedefine
爲define
,後邊的變量會替換,可是不能編譯成功,因此假如在clion中使用,要注意這幾點,直接使用#define
或者#cmakedefine
,儘可能不要加空格。
介紹其中的選項: input和output假如不指定絕對路徑,則會被默認設置爲CMAKE_CURRENT_SOURCE_DIR
和CMAKE_CURRENT_BINARY_DIR
,也就是項目根目錄和構建的目錄;
COPYONLY
則只是複製文件,不替換任何東西,不能和NEWLINE_STYLE <style>
一塊兒使用。
ESCAPE_QUOTES
禁止爲"
轉義。這個很蛋疼,不加這個命令的話假如變量中有a\"b
,則在生成的文件中會直接使用轉義後的字符a"b
,加上指令後則按原來的文字顯示a\"b
;
@ONLY
只容許替換@VAR@
包裹的變量${VAR}
則不會被替換;
NEWLINE_STYLE <style>
設置換行符格式
如今舉個例子: foo.h.in文件以下
#cmakedefine FOO_ENABLE
#cmakedefine FOO_STRING "@FOO_STRING@"
複製代碼
CMakeLists.txt中添加代碼來設置一個開關,下邊會執行if中的語句:
option(FOO_ENABLE "Enable Foo" ON)
if(FOO_ENABLE)
set(FOO_STRING "foo")
endif()
configure_file(foo.h.in foo.h @ONLY)
複製代碼
生成的文件foo.h
#define FOO_ENABLE
#define FOO_STRING "foo"
複製代碼
假如設置爲off,option(FOO_ENABLE "Enable Foo" ON)
,則不會執行if中的語句,生成的文件以下:
/* #undef FOO_ENABLE */
/* #undef FOO_STRING */
複製代碼
include_directories
這一行這句話的意思將當前的二進制目錄添加到編譯器搜索include目錄中,這樣就能夠直接使用上一步生成的頭文件了。
include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 ...])
複製代碼
將給定的目錄添加到編譯器用來搜索包含文件的目錄。相對路徑爲相對於當前根目錄。
括號中的目錄被添加到當前CMakeLists文件的INCLUDE_DIRECTORIES
目錄屬性中。它們也被添加到當前CMakeLists文件中的每一個目標的INCLUDE_DIRECTORIES目標屬性中。。
默認狀況下,指定的目錄被追加到當前的include目錄列表中。經過將CMAKE_INCLUDE_DIRECTORIES_BEFORE
設置爲ON,能夠更改此默認行爲。經過明確使用AFTER或BEFORE,您能夠選擇添加和預先設置。
若是給出SYSTEM選項,那麼編譯器會被告知這些目錄在某些平臺上是指系統包含的目錄。
這翻譯的真是教我頭大。