cmake用法總結

Git使用

clone帶子模塊項目

git clone project.git project2

cd project2

git submodule init

git submodule update

cd ..

 

引入

cmake是跨平臺的編譯工具

先看一個簡單案例

rule-1

# 主編譯規則目標是產生可執行文件testc

# 1) cmake基礎配置包括最低支持版本工程名等

cmake_minimum_required(VERSION 3.2)

project(testc) #可選工程名與編譯目標不是沒有直接對應關係

 

# 2) 執行子編譯規則,編譯子模塊

add_subdirectory(src/dll0) #參數爲子模塊編譯規則所在目錄

 

# 3) 本規則編譯器配置,主要是各類編譯選項

set(CMAKE_CXX_STANDARD 11)

add_compile_options(/MD)

 

# 4) 搜索與本編譯規則相關的源文件

include_directories(./src ./src/dll0)

aux_source_directory(. DIRSRCS) # 搜索指定目錄下所有源文件,.c/.cpp/.cxx

message(STATUS ${DIRSRCS})

 

# 5) 執行編譯及鏈接

add_executable(testc main.cpp src/func.cpp)

target_link_libraries(testc dll0)

# 子編譯規則目標是產生動態庫文件dll0

aux_source_directory(. DIRSRCS)

link_libraries(${all_libs}) // 指定鏈接依賴庫

add_library(dll0 SHARED ${DIRSRCS}) // 產生動態庫

install (TARGETS dll0 DESTINATION <path>) //將目標安裝到指定路徑下

上面例子中,工程的目錄如下:

testc/

CMakeLists.txt #主編譯規則

| main.cpp

| src/

| | func.cpp

| | func.h

| | dll0/

| | | CMakeLists.txt #子編譯規則

| | | func0.h

| | | func0.cpp

利用cmake工具編譯c/c++項目的關鍵就是編譯規則的指定,默認情況下一個CMakeLists.txt表示一條編譯規則,一條編譯規則的目標是產生一個或多個編譯目標(或可執行文件或動態庫或靜態庫)

 

利用cmake編譯c/c++項目,編譯過程基本可以歸納爲rule-1中的5個步驟。cmake本身需要學習的成本在於掌握cmake提供的內置變量及方法。

 

內置變量

變量名

說明

備註

CMAKE_CXX_STANDARD

設置CXX標準98,11,14等

對VC編譯器無效

CMAKE_BINARY_DIR

PROJECT_BINARY_DIR

<projectname>_BINARY_DIR

這三個變量指代的內容是一致的。in-source build表示工程頂層目錄;out-of-source build表示工作路徑,

 

CMAKE_SOURCE_DIR

PROJECT_SOURCE_DIR

<projectname>_SOURCE_DIR

這三個變量指代的內容是一致的,不論採用何種編譯方式,都是工程頂層目錄。

 

CMAKE_CURRENT_SOURCE_DIR

指的是當前處理的 CMakeLists.txt 所在的路徑, 比如rule-1中的 dll0/ 子目錄。

 

CMAKE_CURRRENT_BINARY_DIR

編譯目標的存放路徑

如果是 in-source 編譯,它跟 CMAKE_CURRENT_SOURCE_DIR 一致, 如果是 out-of-source 編譯,他指的是 target 編譯目錄。

使用我們上面提到的 ADD_SUBDIRECTORY(src bin)可以更改這個變量的值。

使用 SET(EXECUTABLE_OUTPUT_PATH <新路徑>)並不會對這個變量造成影響, 它僅僅修改了最終目標文件存放的路徑。

v3.11.2後無此變量

CMAKE_CURRENT_LIST_FILE

輸出調用這個變量的 CMakeLists.txt 的完整路徑

 

CMAKE_CURRENT_LIST_LINE

輸出這個變量所在的行

 

CMAKE_MODULE_PATH

這個變量用來定義自己的 cmake 模塊所在的路徑。如果工程比較複雜,有可能會自己編寫一些 cmake 模塊, 這些 cmake 模塊是隨你的工程發佈的, 爲了讓 cmake 在處理CMakeLists.txt 時找到這些模塊,你需要通過 SET 指令,將自己的 cmake 模塊路徑設置一下。

如:

根目錄下由文件夾

cmake

| modular1.cmake #cmake模塊以.cmake爲後綴

| modular2.cmake

SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)

這時候你就可以通過 INCLUDE 指令來調用自己的模塊了。

include(modular1)

設置後,cmake就會在include時自動在這個路徑中去找模塊

EXECUTABLE_OUTPUT_PATH / LIBRARY_OUTPUT_PATH

分別用來重新定義最終結果的存放目錄,前面我們已經提到了這兩個變量。

設置LIBRARY_OUTPUT_PATH後,便無需調用install()安裝庫文件了

注意,這兩個變量設置後,會影響編譯目標的存放路徑

PROJECT_NAME

返回通過 PROJECT 指令定義的項目名稱

 

環境變量相關

$ENV{NAME}

使用$ENV{NAME}指令可以調用系統的環境變量

比如

MESSAGE(STATUS “HOME dir: $ENV{HOME}”)

設置環境變量的方式是:

SET(ENV{變量名} 值)

 

CMAKE_INCLUDE_CURRENT_DIR

自動添加 CMAKE_CURRENT_BINARY_DIR 和 CMAKE_CURRENT_SOURCE_DIR 到當前處理

的 CMakeLists.txt。相當於在每個 CMakeLists.txt 加入:

INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}

${CMAKE_CURRENT_SOURCE_DIR})

啓用:

set(CMAKE_INCLUDE_CURRENT_DIR ON)

 

CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE

將工程提供的頭文件目錄始終至於系統頭文件目錄的前面,當你定義的頭文件確實跟系統發生衝突時可以提供一些幫助。

默認爲OFF, 啓用設置爲ON

 

CMAKE_INCLUDE_PATH / CMAKE_LIBRARY_PATH

 

 

 

內置指令

execute_process

CMake可以通過execute_process調用shell命令,其使用如下:

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])

按照上述格式,我寫的測試CMakeLists.txt如下:

cmake_minimum_required(VERSION 3.7)

message("Test cmake call shell command")

execute_process(COMMAND echo "Hello")

 

案例1:

想獲取shell指令的輸出,比如搜索當前目錄下所有.c文件,並放入變量OUT_VAR

execute_process(

    COMMAND ls

    COMMAND grep -n "\\.c"

    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}

    OUTPUT_VARIABLE OUT_VAR

    RESULT_VARIABLE RET_VAR)

 

案例2

我們考慮這樣一種場景:

現有目錄結構如下:

|-cmakeTest

   |-shell.sh

   |-CMakeLists.txt

   |-src

      |-main.c

我們需要把 src/main.c 提取到 cmakeTest 目錄下,對其進行編譯。我們的做法是這樣子的:shell.sh 腳本執行復制文件操作,CMakeLists.txt 調用 shell.sh 獲得 main.c 並完成構建。

 

main.c 是這樣的:

#include <stdio.h>

 

int main()

{

    printf("Hello CMake!\n");

    return 0;

}

 

shell.sh 是這樣的:

echo "Begin"

cp  ../$1/main.c  ../main.c

echo "End"

 

注意 cp 語句中,main.c 的路徑寫法,還有$1參數。一會兒解釋爲什麼是這樣的。

重點來了,CMakeLists.txt 是這樣的:

cmake_minimum_required (VERSION 2.6)

execute_process(COMMAND sh ../shell.sh src)

aux_source_directory(. SRC_LIST)

add_executable(main ${SRC_LIST})

 

CMakeLists.txt 通過 execute_process() 命令調用了 shell.sh 並傳入了參數 src 。這裏的 src 就是 shell.sh 裏面 cp 語句的$1,cp 語句展開後就是:

cp ../src/main.c ../main.c

 

至於這奇怪的 ../ 路徑,是因爲 execute_process() 命令開起了一個子線程來執行 shell 命令,這個子線程的工作目錄和當前工作目錄一致。我會採用外部構建方式執行 CMake,也就是新建 build 目錄,進入 build 目錄執行 CMake,於是子線程的工作目錄也就是當前工作目錄——build,所以需要../才能夠正確找到需要的文件。

 

完整的演示如下:

$ mkdir build

$ cd build

$ cmake ..

-- The C compiler identification is GNU 4.8.4

-- The CXX compiler identification is GNU 4.8.4

-- Check for working C compiler: /usr/bin/cc

-- Check for working C compiler: /usr/bin/cc -- works

-- Detecting C compiler ABI info

-- Detecting C compiler ABI info - done

-- Check for working CXX compiler: /usr/bin/c++

-- Check for working CXX compiler: /usr/bin/c++ -- works

-- Detecting CXX compiler ABI info

-- Detecting CXX compiler ABI info - done

Begin

End

-- Configuring done

-- Generating done

-- Build files have been written to: /????/CMakeTest/build

$ make

Scanning dependencies of target main

[100%] Building C object CMakeFiles/main.dir/main.c.o

Linking C executable main

[100%] Built target main

$ ./main

Hello CMake!

 

cmake加載鏈接庫

cl編譯器與gcc加載動態庫區別:

windows系統與linux系統中動態庫形式分別爲dll和so。當程序以靜態方式加載動態庫時,需要在程序產生的鏈接階段將動態庫的信息包括函數地址,導出符號等寫入到程序中,程序運行時才能根據這些信息找到動態庫並從中得到函數的地址。如,linux下gcc –lmylib這句指令將執行找尋libmylib.so動態庫,並將其中鏈接信息寫入鏈接目標。而windows中則可在代碼中加入#pragma comment(“libmylib.lib”)編譯指令加載動態庫的導入庫。(注:windows中動態庫的導入信息和執行代碼是分離的,所以產生一個動態庫既有dll文件又有lib文件)

 

find_library

find_library指令用於查找庫,如windows下實際尋找.lib(可以是靜態庫也可以是動態庫的導入庫),linux下則查詢.so和.a。查找成功後會將庫文件名放在VAR變量中,表示找到。

這個指令用在用戶知道庫文件路徑時使用。

 

find_package

find_package與find_library類似,也是尋找庫,不過他的目的是尋找某個已經安裝的庫的所有依賴庫文件,比如find_package(boost)。當調用此指令時,cmake先找尋用戶cmake模塊中Findxxx.cmake執行,xxx與find_package()指令中指定的名字須相同(可忽略大小寫),若無則再查詢cmake自帶的庫查找腳本。

當程序中需要編譯多個組件時,也可以將組件的編譯腳本寫到Findxxx.cmake中,並通過調用find_package(xxx)編譯安裝。

注:cmake會默認在CMAKE_MODULE_PATH中的路徑下找Findxxx.cmake,所以需要將本地路徑加入進去

set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake)

 

cmake語法檢測

cmake提供一些宏可以區分編譯的平臺和編譯器,比如MSVC宏若爲TRUE則表示默認編譯器爲cl。但是單純知道編譯器有時候也不夠,比如gcc是比較主流的編譯器,但每個版本對c++的標註你支持不同,爲了使cmake能夠區分具體編譯器的版本,或對某種語法的支持,可以採取編譯代表語法的代碼片段的方式。

如:通過check_cxx_source_compiles(“int main(){_sprintf_s(\“hello\”)}”)來檢測編譯器是否支持_sprintf_s()函數

 

list 和 set

set(var a b c d e)創建了一個這樣的列表:a;b;c;d;e。 set(var “a b c d e”)創建了一個字符串或只有一個元素的列表。

注意當

execute_process(COMMAND gcc ${LFLAG} ${OBJS} -o libst.so)編譯時,${OBJS}必須是列表而非字符串或一個元素!

 

條件if

if(expression)

  # then section.

  COMMAND1(ARGS ...)

  COMMAND2(ARGS ...)

  ...

elseif(expression2)

  # elseif section.

  COMMAND1(ARGS ...)

  COMMAND2(ARGS ...)

  ...

else()

  # else section.

  COMMAND1(ARGS ...)

  COMMAND2(ARGS ...)

  ...

endif()

例子:

if(" ${CMAKE_SOURCE_DIR}" STREQUAL " ${CMAKE_BINARY_DIR}")

  message(FATAL_ERROR "

FATAL: In-source builds are not allowed.

       You should create a separate directory for build files.

")

endif()

STREQUAL 是 CMAKE 的關鍵字,用於字符串比較,相同返回 true

${CMAKE_SOURCE_DIR} 是 CMAKE 的自保留變量(拿來用就可以,含義已經確定),文件路徑

${CMAKE_BINARY_DIR}是輸出路徑

 

關係操作符

NOT

非,NOT E1

AND

與,E1 AND E2

OR

或,E1 OR E2

EXIST

~ E,存在 name 的文件或者目錄(應該使用絕對路徑),真

COMMAND

~ E,存在 command-name 命令、宏或函數且能夠被調用,真

DEFINED 

~ E,變量被定義了,真

EQUAL

E1 ~ E2,變量值或者字符串匹配 regex 正則表達式

LESS

GREATER

STRLESS

E1 ~ E2,變量值或者字符串爲有效的數字且滿足小於(大於、等於)的條件

STRGREATER 

STREQUAL

 

install

 

使用場景

CMake來編譯Qt程序

Qt自帶的qmake會出現許多問題(比如文件修改之後有時候qmake不會偵測到不會重新編譯,需要手動去編譯等),於是開始嘗試使用CMake來編寫Qt程序。

    CMake對於一些有名的庫都有自帶文件夾中Modules裏。cmake文件查詢的支持,比如你需要編寫Qt程序,你就可以去cmake_dir/Moudles/查找 FindQt4.cmake這個文件,裏面詳細講述瞭如果你需要用到Qt庫,你需要包含的變量和文件,比如他舉出了 QT_USE_FILE 這個變量,你直接include在CMake腳本之後,你就不需要手動的include_diectories等等,同時它也會生成QT_LIBRARIES這個變量讓你來target_link,因此省去了很多自己需要逐步查詢qmake所在路徑和Qt庫所在路徑的問題。

 

比較簡單的用法,

find_package(Qt4 4.4.3 REQUIRED QtCore QtGui QtXml)

include(${QT_USE_FILE})

add_executable(myexe main.cpp)

target_link_libraries(myexe ${QT_LIBRARIES})

find_package來查詢你需要用到的Qt版本庫,之後REQUIRED表示你需要用到Qt中的哪些子庫,之後include它生成的文件,link它給你生成的庫文件變量,你的Qt簡單的Demo就成功了,是不是很簡單。

   同時我再來講一下moc的簡單用法,Qt的機制它會查詢Q_OBJECT這個宏如果你的文件有這個宏,它的qmake會自動去moc一把生成moc_xxx.cpp文件,然後會內部幫你include他們,所在在IDE端Qt Creator,我們根本察覺不到這個機制在裏面,所以IDE用多了有時候確實察覺不到這些比較底層的機制,用手寫部署確實有其好處。迴歸正題,在CMake中,你如何去實現由qmake幫你做的這些步驟呢?答案有很多,我這裏列舉一個比較簡單的用法,就是給target設置屬性,

set_target_properties(${target_name} PROPERTIES ${properties_name} ${properties_value})

CMake給Qt提供了AUTOMOC這個屬性,可以自動的爲給定target的項目的所有需要moc的文件自動moc,所以這個時候我們只需要加一把set_target_properties(myexe PROPERTIES AUTOMOC ON),這個時候,CMake就會去學qmake的那套邏輯來進行自動moc和編譯了。

 

案例

爲什麼要用cmake來構建qt5的項目呢?qt不是有qmake嗎?這樣,豈不是多此一舉?

其實,應用cmake來構建項目還是非常有必要的,特別是當你的項目涉及到很多第三方庫的時候,cmake的優勢非常突出。

舉個簡單的例子:

假如我要開發一個基於pcl 1.8.0,vtk 7.0,opencv3.2.0, eigen3, Sophus ……等其他的第三方庫的qt5的項目

而不僅僅是隻用qt一家的庫。

qmake只針對qt自身的庫有優勢,如果你的項目中需要依賴很多的第三方庫,而你又覺得手動配置第三方庫的.pro文件挺麻煩的,費力不討好。

就拿pcl來說吧,其實安裝一點都不難,非常簡單,即使是源碼安裝也很容易,頂多是cmake配置項需要花一點時間,而頭疼的是當你需要用qmake構建項目的時候,需要配置很多頭文件和庫文件.

 

這裏,我就僅僅以一個很簡單的實例,來教大家如何使用cmake構建和管理項目:

首先,我們還是要使用qt creater創建中規中矩的qt5的項目:helloworld

項目中的文件列表如下:項目雖小,五臟俱全,該有的文件都有了(.h .cpp .qrc .ui .pro .png)

我把.png文件和.qrc文件放在了一個新建的resources資源文件夾中

├── helloworld.pro

├── helloworld.pro.user

├── main.cpp

├── resources

│   ├── ico.png

│   └── resources.qrc

├── widget.cpp

├── widget.h

└── widget.ui

 

該項目中唯一需要添加代碼的地方是widget.cpp文件,因爲我們需要添加一個圖標

#include "widget.h"

#include "ui_widget.h"

 

Widget::Widget(QWidget *parent) :

    QWidget(parent),

    ui(new Ui::Widget)

{

    ui->setupUi(this);

    //窗體標題

    this->setWindowTitle("Qt5.1 窗體應用");

    //窗體 ICO 圖片,如圖不起別名,後綴直接寫圖片全名。

    this->setWindowIcon(QIcon(":/new/prefix1/ico.png"));

}

 

Widget::~Widget()

{

    delete ui;

}

 

接着在在項目文件夾中手動創建一個CMakeLists.txt文件。內容如下:

cmake_minimum_required(VERSION 2.8.11 FATAL_ERROR)

project(helloworld)

 

set(CMAKE_INCLUDE_CURRENT_DIR ON)

set(CMAKE_AUTOMOC ON)

set(CMAKE_AUTOUIC ON)

set(CMAKE_AUTORCC ON)

set(RESOURCE_DIR resources/resources.qrc)

find_package(Qt5 REQUIRED Widgets)

qt5_wrap_cpp( MOC widget.h)

qt5_wrap_ui( UIC widget.ui)

qt5_add_resources(RCC resources.qrc)

 

add_executable(helloworld main.cpp widget.cpp widget.h widget.ui ${RESOURCE_DIR})

target_link_libraries(helloworld Qt5::Widgets)

 

最後的項目組成如下:

├── CMakeLists.txt

├── CMakeLists.txt.user

├── helloworld.pro

├── helloworld.pro.user

├── main.cpp

├── resources

│   ├── ico.png

│   └── resources.qrc

├── widget.cpp

├── widget.h

└── widget.ui

最後,你可以關閉之前創建的helloworld項目,直接打開CMakeLists.txt文件,一開始會彈出一個configure窗口,直接configure就可以實現項目配置,即使沒有彈出這個窗口也沒關係,直接快捷鍵保存該文件,系統就會直接configure和generate. 最後選中項目右鍵點擊運行項目即可。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章