cmake使用教程(六)-蛋疼的語法

【cmake系列使用教程】java

cmake使用教程(一)-起步linux

cmake使用教程(二)-添加庫c++

cmake使用教程(三)-安裝、測試、系統自檢git

cmake使用教程(四)-文件生成器github

cmake使用教程(五)-cpack生成安裝包macos

cmake使用教程(六)-蛋疼的語法編程

cmake使用教程(七)-流程和循環緩存

cmake使用教程(八)-macro和functionbash

這個系列的文章翻譯自官方cmake教程:cmake tutorialapp

示例程序地址:github.com/rangaofei/t…

不會僅僅停留在官方教程。本人做爲一個安卓開發者,實在是沒有linux c程序開發經驗,望大佬們海涵。教程是在macos下完成,大部分linux我也測試過,有特殊說明的我會標註出來。本教程基於cmake-3.10.2,同時認爲你已經安裝好cmake。

「蛋疼的不止語法,還有文檔」

cmake文件格式

本節講的命令格式遵循以下語法:

格式 註釋
<command> 必須填寫的
[command] 可寫也可不寫的
a\|b a或者b都可以

cmake能識別CMakeLists.txt和*.cmake格式的文件。cmake可以以三種方式 來組織文件:

類別 文件格式
Dierctory(文件夾) CMakeLists.txt
Script(腳本) <script>.cmake
Module(模塊) <module>.cmake

本系列主要以CMakeLists.txt的語法爲主要講解內容,至於編寫這蛋疼的腳本和模塊假如我還寫的下去的話我就寫。

1. Directory

當CMake處理一個項目時,入口點是一個名爲CMakeLists.txt的源文件,這個必定是根目錄下的CMakeLists.txt。這個文件包含整個工程的構建規範,當咱們有多個子文件夾須要編譯時,使用add_subdirectory(<dir_name>)命令來爲構建添加子目錄。添加的每一個子目錄也必須包含一個CMakeLists.txt文件做爲該子目錄的入口點。每一個子目錄的CMakeLists.txt文件被處理時,CMake在構建樹中生成相應的目錄做爲默認的工做和輸出目錄。記住這一點很是關鍵,這樣咱們就可使用外部構建了,而沒必要每次都使用蛋疼的內部構建,而後刪除一堆文件才能重新構建。

2. Script

一個單獨的<script>.cmake源文件可使用cmake命令行工具cmake -P <script>.cmake選項來執行腳本。腳本模式只是在給定的文件中運行命令,而且不生成構建系統。它不容許CMake命令定義或執行構建目標。

3. Module

在Directory或Script中,CMake代碼可使用include()命令來加載.cmake。cmake內置了許多模塊用來幫助咱們構建工程,前邊文章中提到的CheckFunctionExists。也能夠提供本身的模塊,並在CMAKE_MODULE_PATH變量中指定它們的位置。

cmake基本編寫格式

先看一下定義的方式

名稱 表達式 我認爲的 例子
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},並在相同的上下文中做爲正常變量引用。

cmake構建系統

這算是進入正文了。扯淡的介紹就很少講了,直接乾貨。

可執行文件和庫是使用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用法 message相似於一個向控制檯輸出日誌的工具,可是功能又稍微強大一些,在一些模式下可以終止程序構建。
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] ])
複製代碼

文件複製到文件中,並在輸入文件內容中替換@VAR@${VAR}的變量值。每一個變量引用將被替換爲變量的當前值,若是變量的值未被定義,則爲空字符串。(VAR必須與cmakelist.txt中的變量保持一直,不然會生成註釋)

input文件的定義形式爲:

#cmakedefine VAR ..
複製代碼

通過configure後生成的文件內容被替換爲:

#define VAR ... //替換成功
/* #undef VAR */ //未定義的變量
複製代碼

生成的文件將會保留在'#'與'cmakedefine'之間的空格和製表符。

此處有一點需說明,如今clion默認使用cmake來構建程序,可是在clion中不支持cmakedefine關鍵字,因此能夠直接使在input文件中填寫#define VAR ...來編寫宏定義,生成的結果與上邊徹底同樣。clion有一個問題,就是直接用cmakedefine定義宏的時候假如#與cmakedefine之間有空格則不會替換cmakedefinedefine,後邊的變量會替換,可是不能編譯成功,因此假如在clion中使用,要注意這幾點,直接使用#define或者#cmakedefine,儘可能不要加空格。

介紹其中的選項: input和output假如不指定絕對路徑,則會被默認設置爲CMAKE_CURRENT_SOURCE_DIRCMAKE_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選項,那麼編譯器會被告知這些目錄在某些平臺上是指系統包含的目錄。

這翻譯的真是教我頭大。

相關文章
相關標籤/搜索