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 模塊路徑設置一下。 如: 根目錄下由文件夾
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. 最後選中項目右鍵點擊運行項目即可。