CMake入門教程

技術從未如此性感發表於2018-06-27

參考文獻:http://www.ibm.com/developerworks/cn/linux/l-cn-cmake/index.html

官方網址:http://www.cmake.org/

下載網址:http://www.cmake.org/download/


CMake 是一個跨平臺的,開源的構建系統(BuildSystem)。CMake 可以通過 CMakeLists.txt 檔案來產生特定平臺的標準的構建檔案,例如:為 Unix 平臺生成makefiles檔案(使用GCC編譯),為 Windows MSVC 生成 projects/workspaces(使用VS IDE編譯)或Makefile檔案(使用nmake編譯)。


CMake Hello World

首先編寫一個簡單的程式(hello.cpp):

#include <stdio.h>

int main()

{

   printf("Hello World");

   return 0;

}

編寫CMakeLists.txt,並與hello.cpp放在同一個目錄。

project(hello)

cmake_minimum_required(VERSION 2.8)

aux_source_directory(. DIR_SRCS)

add_executable(hello ${DIR_SRCS})

在 CMakeLists.txt 所在的目錄下建立一個build目錄,進入該目錄執行 CMake 命令生成構建檔案:

開啟Visual Studio 2008命令列提示視窗,它會執行載入一些VS的環境變數。

mkdir build

cd build

cmake -G "NMake Makefiles" ../

-- The C compiler identification is MSVC 15.0.30729.1

-- The CXX compiler identification is MSVC15.0.30729.1

….

-- Detecting CXX compiler ABI info

-- Detecting CXX compiler ABI info - done

-- Configuring done

-- Generating done

-- Build files have been written to:I:/cmake-hello/build

這裡使用的平臺為 Windows 並且安裝了 VS2008,CMake 為我們生成了 VS2008的構建檔案Makefile,我們可以使用nmake來構建應用程式,執行:

nmake

 

Microsoft (R) Program Maintenance UtilityVersion 9.00.30729.01

Copyright (C) Microsoft Corporation.  All rights reserved.

 

Scanning dependencies of target hello

[100%] Building CXX objectCMakeFiles/hello.dir/hello.cpp.obj

hello.cpp

Linking CXX executable hello.exe

[100%] Built target hello

編譯成功完成後,會當前目錄下生成hello應用程式。

CMake基本語法

編寫的CMakeLists.txt需要符合一定的語法規則,它主要由CMake命令組成。

1)註釋的語法:在CMake中註釋使用#字元開始到此行結束。

2)CMake命令:命令不區分大小寫(引數會區分大小寫),命令由命令、引數列表組成,引數之間使用空格進行分隔。使用一對雙引號包括的字串認為是一個引數。命令可以是一個內建命令(例如:project,add_executable等),也可以是一個使用者定義的巨集(macro)或者函式(function)。

3)資料型別:CMake的基本資料型別是字串,一組字串在一起稱為列表(list),例如:

# 通過 set 命令構建一個 listVAR

set(VAR a b c)

使用語法 ${VariableName} 來訪問名字為 VariableName 的變數的值(變數名區分大小寫)。需要注意的是,即使在字串中也可以使用 ${VariableName} 來訪問變數的值:

set(VAR a b c)

# 輸出 VAR = a;b;c

message("VAR = ${VAR}")

使用語法 $ENV{VariableName} 來訪問環境變數的值(ENV{VariableName} 則表示環境變數本身)

# 輸出環境變數 PATH 的值

message($ENV{PATH})

4)條件控制和迴圈結構

條件控制命令為 if 命令

if(expression)

    #...

elseif(expression2)

    #...

else()

    #...

endif()

對於 if(string) 來說:

如果 string 為(不區分大小寫)1、ON、YES、TRUE、Y、非 0 的數則表示真

如果 string 為(不區分大小寫)0、OFF、NO、FALSE、N、IGNORE、空字串、以 -NOTFOUND 結尾的字串則表示假

如果 string 不符合上面兩種情況,則 string 被認為是一個變數的名字。變數的值為第二條所述的各值則表示假,否則表示真。

# 此策略(Policy)在 CMake2.8.0 才被引入

# 因此這裡需要指定最低 CMake 版本為 2.8

cmake_minimum_required(VERSION 2.8)

set(YES 0)

 

# 輸出 True

if(YES)

   message(True)

else()

   message(False)

endif()

 

# 輸出 False

if(${YES})

   message(True)

else()

   message(False)

endif()

表示式中可以包含操作符,操作符包括:

一元操作符,例如:EXISTS、COMMAND、DEFINED 等

二元操作符,例如:EQUAL、LESS、GREATER、STRLESS、STRGREATER 等

NOT(非操作符)

AND(與操作符)、OR(或操作符)

操作符優先順序:一元操作符 > 二元操作符 > NOT > AND、OR

常用操作符介紹:

if(NOT expression)

為真的前提是 expression 為假

if(expr1 AND expr2)

為真的前提是 expr1 和 expr2 都為真

if(expr1 OR expr2)

為真的前提是 expr1 或者 expr2 為真

if(COMMAND command-name)

為真的前提是存在 command-name 命令、巨集或函式且能夠被呼叫

if(EXISTS name)

為真的前提是存在 name 的檔案或者目錄(應該使用絕對路徑)

if(file1 IS_NEWER_THAN file2)

為真的前提是 file1 比 file2 新或者 file1、file2 中有一個檔案不存在(應該使用絕對路徑)

if(IS_DIRECTORY directory-name)

為真的前提是 directory-name 表示的是一個目錄(應該使用絕對路徑)

if(variable|string MATCHES regex)

為真的前提是變數值或者字串匹配 regex 正規表示式

if(variable|string LESS variable|string)

if(variable|string GREATER variable|string)

if(variable|string EQUAL variable|string)

為真的前提是變數值或者字串為有效的數字且滿足小於(大於、等於)的條件

if(variable|string STRLESS variable|string)

if(variable|string STRGREATERvariable|string)

if(variable|string STREQUALvariable|string)

為真的前提是變數值或者字串以字典序滿足小於(大於、等於)的條件

if(DEFINED variable)

為真的前提是 variable 表示的變數被定義了。

foreach 迴圈範例:

set(VAR a b c)

foreach(f ${VAR})

   message(${f})

endforeach()

while 迴圈範例:

set(VAR 5)

while(${VAR} GREATER 0)

   message(${VAR})

   math(EXPR VAR "${VAR} - 1")

endwhile()

5)函式和巨集定義

函式會為變數建立一個區域性作用域,而巨集則使用全域性作用域。範例:

# 定義一個巨集 hello

macro(hello MESSAGE)

   message(${MESSAGE})

endmacro()

# 呼叫巨集 hello

hello("hello world")

# 定義一個函式 hello

function(hello MESSAGE)

   message(${MESSAGE})

endfunction()

函式和巨集可以通過命令 return() 返回,但是函式和巨集的返回值必須通過引數傳遞出去。例如:

cmake_minimum_required(VERSION 2.8)

function(get_func RESULT)

    #RESULT 的值為實參的值,因此需要使用 ${RESULT}

    #這裡使用 PARENT_SCOPE 是因為函式會構建一個區域性作用域

   set(${RESULT} "Hello Function" PARENT_SCOPE)

endfunction()

 

macro(get_macro RESULT)

   set(${RESULT} "Hello Macro")

endmacro()

 

get_func(V1)

# 輸出 Hello Function

message(${V1})

 

get_macro(V2)

# 輸出 Hello Macro

message(${V2})

7)字串的一些問題

字串可跨行且支援轉移字元,例如:

set(VAR "hello

world")

# 輸出結果為:

# ${VAR} = hello

# world

message("\${VAR} = ${VAR}")

CMake常用命令

這裡介紹一下常用的命令(CMake 2.8 的命令可以在此查詢):

http://www.cmake.org/cmake/help/v2.8.8/cmake.html#section_Commands

1)project 命令

命令語法:project(<projectname> [languageName1 languageName2 … ] )

命令簡述:用於指定專案的名稱

使用範例:project(Main)

2)cmake_minimum_required命令

命令語法:cmake_minimum_required(VERSION major[.minor[.patch[.tweak]]][FATAL_ERROR])

命令簡述:用於指定需要的 CMake 的最低版本

使用範例:cmake_minimum_required(VERSION 2.8)

3)aux_source_directory命令

命令語法:aux_source_directory(<dir> <variable>)

命令簡述:用於將 dir 目錄下的所有原始檔的名字儲存在變數 variable 中

使用範例:aux_source_directory(. DIR_SRCS)

4)add_executable 命令

命令語法:add_executable(<name> [WIN32] [MACOSX_BUNDLE][EXCLUDE_FROM_ALL] source1 source2 … sourceN)

命令簡述:用於指定從一組原始檔 source1 source2 … sourceN 編譯出一個可執行檔案且命名為 name

使用範例:add_executable(Main ${DIR_SRCS})

5)add_library 命令

命令語法:add_library([STATIC | SHARED | MODULE] [EXCLUDE_FROM_ALL] source1source2 … sourceN)

命令簡述:用於指定從一組原始檔 source1 source2 … sourceN 編譯出一個庫檔案且命名為 name

使用範例:add_library(Lib ${DIR_SRCS})

6)add_dependencies 命令

命令語法:add_dependencies(target-name depend-target1 depend-target2 …)

命令簡述:用於指定某個目標(可執行檔案或者庫檔案)依賴於其他的目標。這裡的目標必須是 add_executable、add_library、add_custom_target 命令建立的目標

7)add_subdirectory 命令

命令語法:add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])

命令簡述:用於新增一個需要進行構建的子目錄

使用範例:add_subdirectory(Lib)

8)target_link_libraries命令

命令語法:target_link_libraries(<target> [item1 [item2 […]]][[debug|optimized|general] ] …)

命令簡述:用於指定 target 需要連結 item1 item2 …。這裡 target 必須已經被建立,連結的 item 可以是已經存在的 target(依賴關係會自動新增)

使用範例:target_link_libraries(Main Lib)

9)set 命令

命令語法:set(<variable> <value> [[CACHE <type><docstring> [FORCE]] | PARENT_SCOPE])

命令簡述:用於設定變數 variable 的值為 value。如果指定了 CACHE 變數將被放入 Cache(快取)中。

使用範例:set(ProjectName Main)

10)unset 命令

命令語法:unset(<variable> [CACHE])

命令簡述:用於移除變數 variable。如果指定了 CACHE 變數將被從 Cache 中移除。

使用範例:unset(VAR CACHE)

11)message 命令

命令語法:message([STATUS|WARNING|AUTHOR_WARNING|FATAL_ERROR|SEND_ERROR] “message todisplay”…)

命令簡述:用於輸出資訊

使用範例:message(“Hello World”)

12)include_directories 命令

命令語法:include_directories([AFTER|BEFORE] [SYSTEM] dir1 dir2 …)

命令簡述:用於設定目錄,這些設定的目錄將被編譯器用來查詢 include 檔案

使用範例:include_directories(${PROJECT_SOURCE_DIR}/lib)

13)find_path 命令

命令語法:find_path(<VAR> name1 [path1 path2 …])

命令簡述:用於查詢包含檔案 name1 的路徑,如果找到則將路徑儲存在 VAR 中(此路徑為一個絕對路徑),如果沒有找到則結果為 <VAR>-NOTFOUND。預設的情況下,VAR 會被儲存在 Cache 中,這時候我們需要清除 VAR 才可以進行下一次查詢(使用 unset 命令)。

使用範例:

find_path(LUA_INCLUDE_PATH lua.h${LUA_INCLUDE_FIND_PATH})

if(NOT LUA_INCLUDE_PATH)

   message(SEND_ERROR "Header file lua.h not found")

endif()

14)find_library 命令

命令語法:find_library(<VAR> name1 [path1 path2 …])

命令簡述:用於查詢庫檔案 name1 的路徑,如果找到則將路徑儲存在 VAR 中(此路徑為一個絕對路徑),如果沒有找到則結果為 <VAR>-NOTFOUND。一個類似的命令 link_directories 已經不太建議使用了

15)add_definitions 命令

命令語法:add_definitions(-DFOO -DBAR …)

命令簡述:用於新增編譯器命令列標誌(選項),通常的情況下我們使用其來新增前處理器定義

使用範例:add_definitions(-D_UNICODE -DUNICODE)

16)execute_process 命令

命令語法:

execute_process(COMMAND <cmd1>[args1...]]

                  [COMMAND <cmd2>[args2...] [...]]

                  [WORKING_DIRECTORY<directory>]

                  [TIMEOUT <seconds>]

                  [RESULT_VARIABLE<variable>]

                  [OUTPUT_VARIABLE<variable>]

                  [ERROR_VARIABLE<variable>]

                  [INPUT_FILE <file>]

                  [OUTPUT_FILE <file>]

                  [ERROR_FILE <file>]

                  [OUTPUT_QUIET]

                  [ERROR_QUIET]

                 [OUTPUT_STRIP_TRAILING_WHITESPACE]

                 [ERROR_STRIP_TRAILING_WHITESPACE])

命令簡述:用於執行一個或者多個外部命令。每一個命令的標準輸出通過管道轉為下一個命令的標準輸入。WORKING_DIRECTORY 用於指定外部命令的工作目錄,RESULT_VARIABLE 用於指定一個變數儲存外部命令執行的結果,這個結果可能是最後一個執行的外部命令的退出碼或者是一個描述錯誤條件的字串,OUTPUT_VARIABLE 或者 ERROR_VARIABLE 用於指定一個變數儲存標準輸出或者標準錯誤,OUTPUT_QUIET 或者 ERROR_QUIET 用於忽略標準輸出和標準錯誤。

使用範例:execute_process(COMMAND ls)

18)file 命令

命令簡述:此命令提供了豐富的檔案和目錄的相關操作(這裡僅說一下比較常用的)

使用範例:

# 目錄的遍歷

# GLOB 用於產生一個檔案(目錄)路徑列表並儲存在variable 中

# 檔案路徑列表中的每個檔案的檔名都能匹配globbing expressions(非正規表示式,但是類似)

# 如果指定了 RELATIVE 路徑,那麼返回的檔案路徑列表中的路徑為相對於 RELATIVE 的路徑

# file(GLOB variable [RELATIVE path][globbing expressions]...)

 

# 獲取當前目錄下的所有的檔案(目錄)的路徑並儲存到 ALL_FILE_PATH 變數中

file(GLOB ALL_FILE_PATH ./*)

# 獲取當前目錄下的 .h 檔案的檔名並儲存到ALL_H_FILE 變數中

# 這裡的變數CMAKE_CURRENT_LIST_DIR 表示正在處理的 CMakeLists.txt 檔案的所在的目錄的絕對路徑(2.8.3 以及以後版本才支援)

file(GLOB ALL_H_FILE RELATIVE${CMAKE_CURRENT_LIST_DIR} ${CMAKE_CURRENT_LIST_DIR}/*.h)

CMake常用變數

UNIX 如果為真,表示為 UNIX-like 的系統,包括 AppleOS X 和 CygWin

WIN32 如果為真,表示為 Windows 系統,包括 CygWin

APPLE 如果為真,表示為 Apple 系統

CMAKE_SIZEOF_VOID_P 表示 void* 的大小(例如為 4 或者 8),可以使用其來判斷當前構建為 32 位還是 64 位

CMAKE_CURRENT_LIST_DIR 表示正在處理的CMakeLists.txt 檔案的所在的目錄的絕對路徑(2.8.3 以及以後版本才支援)

CMAKE_ARCHIVE_OUTPUT_DIRECTORY 用於設定 ARCHIVE 目標的輸出路徑

CMAKE_LIBRARY_OUTPUT_DIRECTORY 用於設定 LIBRARY 目標的輸出路徑

CMAKE_RUNTIME_OUTPUT_DIRECTORY 用於設定 RUNTIME 目標的輸出路徑

構建型別

CMake 為我們提供了四種構建型別:

Debug

Release

MinSizeRel

RelWithDebInfo

如果使用 CMake 為 Windows MSVC 生成 projects/workspaces 那麼我們將得到上述的 4 種解決方案配置。

 

如果使用 CMake 生成 Makefile 時,我們需要做一些不同的工作。CMake 中存在一個變數 CMAKE_BUILD_TYPE 用於指定構建型別,此變數只用於基於 make 的生成器。我們可以這樣指定構建型別:

$ CMake -DCMAKE_BUILD_TYPE=Debug .

這裡的 CMAKE_BUILD_TYPE 的值為上述的 4 種構建型別中的一種。

編譯和連結標誌

C 編譯標誌相關變數:

 

CMAKE_C_FLAGS

CMAKE_C_FLAGS_[DEBUG|RELEASE|MINSIZEREL|RELWITHDEBINFO]

C++ 編譯標誌相關變數:

 

CMAKE_CXX_FLAGS

CMAKE_CXX_FLAGS_[DEBUG|RELEASE|MINSIZEREL|RELWITHDEBINFO]

CMAKE_C_FLAGS 或CMAKE_CXX_FLAGS 可以指定編譯標誌

CMAKE_C_FLAGS_[DEBUG|RELEASE|MINSIZEREL|RELWITHDEBINFO]或 CMAKE_CXX_FLAGS_[DEBUG|RELEASE|MINSIZEREL|RELWITHDEBINFO] 則指定特定構建型別的編譯標誌,這些編譯標誌將被加入到 CMAKE_C_FLAGS 或 CMAKE_CXX_FLAGS 中去,例如,如果構建型別為 DEBUG,那麼 CMAKE_CXX_FLAGS_DEBUG 將被加入到 CMAKE_CXX_FLAGS中去

 

連結標誌相關變數:

CMAKE_EXE_LINKER_FLAGS

CMAKE_EXE_LINKER_FLAGS_[DEBUG|RELEASE|MINSIZEREL|RELWITHDEBINFO]

CMAKE_MODULE_LINKER_FLAGS

CMAKE_MODULE_LINKER_FLAGS_[DEBUG|RELEASE|MINSIZEREL|RELWITHDEBINFO]

CMAKE_SHARED_LINKER_FLAGS

CMAKE_SHARED_LINKER_FLAGS_[DEBUG|RELEASE|MINSIZEREL|RELWITHDEBINFO]

它們類似於編譯標誌相關變數

生成Debug和Release版本

在 Visual Studio 中我們可以生成 debug 版和 release 版的程式,使用 CMake 我們也可以達到上述效果。debug 版的專案生成的可執行檔案需要有除錯資訊並且不需要進行優化,而 release 版的不需要除錯資訊但需要優化。這些特性在 gcc/g++ 中是通過編譯時的引數來決定的,如果將優化程度調到最高需要設定引數-O3,最低是 -O0 即不做優化;新增除錯資訊的引數是 -g -ggdb ,如果不新增這個引數,除錯資訊就不會被包含在生成的二進位制檔案中。

CMake 中有一個變數CMAKE_BUILD_TYPE ,可以的取值是 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。

示例:

PROJECT(main)

CMAKE_MINIMUM_REQUIRED(VERSION 2.6)

SET(CMAKE_SOURCE_DIR .)

 

SET(CMAKE_CXX_FLAGS_DEBUG"$ENV{CXXFLAGS} -O0 -Wall -g -ggdb")

SET(CMAKE_CXX_FLAGS_RELEASE"$ENV{CXXFLAGS} -O3 -Wall")

 

AUX_SOURCE_DIRECTORY(. DIR_SRCS)

ADD_EXECUTABLE(main ${DIR_SRCS})

第 5 和 6 行設定了兩個變數 CMAKE_CXX_FLAGS_DEBUG 和CMAKE_CXX_FLAGS_RELEASE, 這兩個變數是分別用於 debug 和 release 的編譯選項。編輯 CMakeList.txt 後需要執行 ccmake 命令生成 Makefile 。在進入專案的根目錄,輸入 "ccmake ." 進入一個圖形化介面。

編譯32位和64位程式

對於 Windows MSVC,我們可以設定 CMake Generator 來確定生成 Win32 還是 Win64 工程檔案,例如:

 

# 用於生成 Visual Studio 10Win64 工程檔案

CMake -G "Visual Studio 10 Win64"

# 用於生成 Visual Studio 10Win32 工程檔案

CMake -G "Visual Studio 10"

我們可以通過 CMake --help 來檢視當前平臺可用的 Generator。

CMake .. -DUSE_32BITS=1

if(USE_32BITS)

 message(STATUS "Using 32bits")

 set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}-m32")

 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}-m32")

else()

endif(USE_32BITS)

對於 UNIX 和類 UNIX 平臺,我們可以通過編譯器標誌(選項)來控制進行 32 位還是 64 位構建。

GCC命令列引數

32位版:加上 -m32 引數,生成32位的程式碼。

64位版:加上 -m64 引數,生成64位的程式碼。

debug版:加上 -g 引數,生成除錯資訊。

release版:加上 -static 引數,進行靜態連結,使程式不再依賴動態庫。加上 -O3 引數,進行最快速度優化。加上-DNDEBUG引數,定義NDEBUG巨集,遮蔽斷言。

當沒有-m32或-m64引數時,一般情況下會生成跟作業系統位數一致的程式碼,但某些編譯器存在例外,例如——

32位Linux下的GCC,預設是編譯為32位程式碼。

64位Linux下的GCC,預設是編譯為64位程式碼。

Window系統下的MinGW,總是編譯為32位程式碼。因為MinGW只支援32位程式碼。

Window系統下的MinGW-w64(例如安裝了TDM-GCC,選擇MinGW-w64),預設是編譯為64位程式碼,包括在32位的Windows系統下。

Makefile檔案中的示例:

# [args] 生成模式. 0代表debug模式, 1代表release模式. makeRELEASE=1.

ifeq ($(RELEASE),0)

   CFLAGS += -g

else

    #release

   CFLAGS += -static -O3 -DNDEBUG

   LFLAGS += -static

endif

# [args] 程式位數. 32代表32位程式, 64代表64位程式, 其他預設. makeBITS=32.

ifeq ($(BITS),32)

   CFLAGS += -m32

   LFLAGS += -m32

else

    ifeq($(BITS),64)

       CFLAGS += -m64

       LFLAGS += -m64

   else

   endif

endif

多原始檔目錄的處理方式

我們在每一個原始碼目錄中都會放置一個 CMakeLists.txt 檔案。我們現在假定有這麼一個工程:

HelloWorld

|

+------- Main.cpp

|

+------- CMakeLists.txt

|

+------- Lib

        |

        +------- Lib.cpp

        |

        +------- Lib.h

        |

        +------- CMakeLists.txt

這裡 Lib 目錄下的檔案將被編譯為一個庫。首先,我們看一下 Lib 目錄下的 CMakeLists.txt 檔案:

aux_source_directory(. DIR_SRCS)

add_library(Lib ${DIR_SRCS})

然後,看一下 HelloWorld 目錄下的 CMakeLists.txt 檔案:

project(Main)

cmake_minimum_required(VERSION 2.8)

add_subdirectory(Lib)

aux_source_directory(. DIR_SRCS)

add_executable(Main ${DIR_SRCS})

target_link_libraries(Main Lib)

這裡使用了 add_subdirectory 指定了需要進行構建的子目錄,並且使用了 target_link_libraries 命令,表示 Main 可執行檔案需要連結 Lib庫。我們執行 CMake . 命令,首先會執行 HelloWorld 目錄下的 CMakeLists.txt 中的命令,當執行到 add_subdirectory(Lib) 命令的時候會進入 Lib 子目錄並執行其中的CMakeLists.txt 檔案。

外部構建(out of source builds)

我們在 CMakeLists.txt 所在目錄下執行 CMake . 會生成大量的檔案,這些檔案和我們的原始檔混在一起不好管理,我們採用外部構建的方式來解決這個問題。以上面的 Hello World 工程來做解釋:

在 HelloWorld 目錄下建立一個build 目錄(build目錄可以建立在如何地方)

進入 build 目錄並進行外部構建 CMake ..(語法為 CMake <CMakeLists.txt 的路徑>,這裡使用 CMake.. 表明了 CMakeLists.txt 在 Build 目錄的父目錄中)。這樣 CMake 將在 Build 目錄下生成檔案。