linux CMakeLists.txt 語法
CMake入門教程
參考文獻:http://www.ibm.com/developerworks/cn/linux/l-cn-cmake/index.html
官方網址:http://www.cmake.org/
下載網址:http://www.cmake.org/download/
當前版本:3.1rc3
本文基於CMake 2.8版本編寫。
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 目錄下生成文件。
linux CMakeLists.txt 語法