CMake編譯工具與項(xiàng)目構(gòu)建
點(diǎn)擊上方“小白學(xué)視覺”,選擇加"星標(biāo)"或“置頂”
重磅干貨,第一時(shí)間送達(dá)
文章導(dǎo)讀
本文從C/C++代碼的編譯過程入手,弄清楚Make與Makefile,CMake與CMakeLists的關(guān)系,最后從CMakeLists的語法規(guī)則入手給出示例帶大家熟悉如何編寫一份簡單的編譯腳本。
編譯過程實(shí)際上就是將一種語言(通常為高級(jí)語言)翻譯為成另一種語言(通常為低級(jí)語言)。C/C++程序編譯的主要工作流程為:源代碼? → 預(yù)處理器? → 編譯器? → 匯編器 → 鏈接器? → 可執(zhí)行程序
(1)預(yù)處理
C/C++中,在編譯器對(duì)源程序進(jìn)行編譯之前,首先要對(duì)程序文本進(jìn)行預(yù)處理。預(yù)處理器提供了一組預(yù)編譯處理指令和預(yù)處理操作符,形式上以#開頭,實(shí)際上并不屬于C/C++中的語句,不能被編譯程序翻譯,需要在真正編譯之前做一個(gè)預(yù)處理,最后輸出一個(gè)“.i”文件。
(2)編譯
編譯就是把C/C++代碼轉(zhuǎn)換成匯編代碼。編譯程序需要通過詞法分析和語法分析,在確認(rèn)所有的指令都符合語法規(guī)則沒有語法錯(cuò)誤之后,把代碼轉(zhuǎn)換成匯編語言,生成匯編代碼。
(3)匯編
匯編就是將上一步輸出的匯編代碼翻譯成目標(biāo)機(jī)器指令的過程,生成的目標(biāo)文件中是與源程序等效的機(jī)器語言。
(4)鏈接
鏈接就是將匯編生成的目標(biāo)文件、系統(tǒng)庫的目標(biāo)文件、庫文件鏈接起來,生成可以在某一特定平臺(tái)運(yùn)行的可執(zhí)行程序。

當(dāng)源文件比較多時(shí),一般不適合直接通過gcc來編譯代碼,這時(shí)就需要一個(gè)自動(dòng)化的編譯工具。
Make(GNU Make)是一個(gè)自動(dòng)化軟件,用于將源代碼文件編譯為可執(zhí)行的二進(jìn)制文件從而完成自動(dòng)化編譯。Make工具編譯的時(shí)候需要Makefile文件提供編譯規(guī)則,Makefile定義了一系列的編譯規(guī)則,包括編譯的先后順序,哪些文件需要重新編譯等操作。
利用Make工具可以自動(dòng)完成編譯工作,如果修改了某幾個(gè)源文件,則只重新編譯這幾個(gè)源文件。如果某個(gè)頭文件被修改了,則重新編譯所有包含該頭文件的源文件。利用這種自動(dòng)編譯極大地提高了開發(fā)效率,避免了不必要的重新編譯。
CMake是更加抽象的跨平臺(tái)的項(xiàng)目管理工具,它能夠輸出各種Makefile文件或工程文件。例如,在windows下它能生成visual studio的工程,在linux下它會(huì)生成Makefile文件。也就是說,cmake能夠按照同一個(gè)抽象規(guī)則為各個(gè)編譯器生成工程文件,從而忽略不同平臺(tái)的差異,抽象成為一個(gè)一致的環(huán)境。?
而CMake命令的執(zhí)行所按照的規(guī)則也就是由CMakeLists.txt文件編寫的。?

對(duì)于一個(gè)龐大的工程,編寫Makefile相當(dāng)復(fù)雜,有了CMake工具之后就可以讀入所有源文件,自動(dòng)生成Makefile。那么使用CMake編寫一個(gè)跨平臺(tái)工程的基本流程如下:
編寫源文件
編寫CMakeLists.txt
由CMake根據(jù)CMakeLists.txt生成Makefile
由Make根據(jù)Makefile,調(diào)用gcc生成可執(zhí)行文件

CMakeLists.txt的編寫主要包含以下步驟:
cmake_minimum_required(VERSION 2.8.0):用于指定cmake所需最低版本;
project(Project) :用于指定項(xiàng)目名稱;
include_directories() :用于包含頭文件目錄;
aux_source_directory(src dir_srcs):用于包含源文件目錄;
set(TEST_MATH) :用于設(shè)置環(huán)境變量,編譯用到的源文件全部都要放到這里;
add_executable(${PROJECT_NAME} ${TEST_MATH}):用于添加要編譯的可執(zhí)行文件;
target_link_libraries(${PROJECT_NAME} m):用于添加可執(zhí)行文件所需要的庫;
CMake語法中預(yù)設(shè)了一些常用變量:
CMAKE_MAJOR_VERSION:cmake 主版本號(hào);
CMAKE_MINOR_VERSION:cmake 次版本號(hào);
CMAKE_C_FLAGS:設(shè)置 C 編譯選項(xiàng);
CMAKE_CXX_FLAGS:設(shè)置 C++ 編譯選項(xiàng);
PROJECT_SOURCE_DIR:工程的根目錄;
PROJECT_BINARY_DIR:運(yùn)行 cmake 命令的目錄;
CMAKE_CURRENT_SOURCE_DIR:當(dāng)前CMakeLists.txt 所在路徑;
CMAKE_CURRENT_BINARY_DIR:目標(biāo)文件編譯目錄;
EXECUTABLE_OUTPUT_PATH:重新定義目標(biāo)二進(jìn)制可執(zhí)行文件的存放位置
LIBRARY_OUTPUT_PATH:重新定義目標(biāo)鏈接庫文件的存放位置
示例代碼如下(cmake語法大小寫不敏感)
cmake_minimum_required(VERSION?2.8.12)option(ARM?"Activate?the?ARM?cross-compile"?OFF)if(ARM)????message(STATUS?"ARM?Cross-Compile")????set(CMAKE_SYSTEM_NAME?Linux)????set(CMAKE_C_COMPILER?/usr/local/gcc-arm-9.2-2019.12-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-gcc)????set(CMAKE_CXX_COMPILER?/usr/local/gcc-arm-9.2-2019.12-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-g++)????set(CMAKE_BUILD_TYPE?"Release")else()????message(STATUS?"Default?Compile")endif()project("Demo")
上述代碼中通過cmake_minimum_required指定cmake的最小版本,if-else語句進(jìn)行條件判斷,因?yàn)閏make可以跨平臺(tái)編譯,上述通過宏的方式選擇arm下的交叉編譯器或者win下的默認(rèn)編譯器。
project()在上面提到是指定當(dāng)前項(xiàng)目的名稱。而message()類似于printf用于答應(yīng)信息。set()用于顯示的定義變量,比如定義CMAKE_C_COMPILER變量保存arm平臺(tái)C編譯器的路徑,定義CMAKE_BUILD_TYPE變量指定編譯的是Release版本程序。
add_compile_options(-D_DEBUG)add_compile_options(-D_INTERNALDEBUG)?add_compile_options(-std=c++11)add_compile_options(-O0)add_compile_options(-g)add_compile_options(-Wall)
add_compile_options主要用來設(shè)置編譯選項(xiàng),比如例子代碼中-std=c++11指定編譯c++代碼時(shí)加上c++11支持選項(xiàng);-g允許發(fā)出gcc能提供的所有有用的警告到生成的二進(jìn)制文件中;-O0是調(diào)節(jié)編譯優(yōu)化程度,調(diào)到最高需要設(shè)置 -O3 ,最低的是 -O0 即不做優(yōu)化;
# Find requirementsfind_package(PCL REQUIRED)if(NOT PCL_FOUND)message("Not found PCL")endif()include_directories(${PCL_INCLUDE_DIRS})link_directories(${PCL_LIBRARY_DIRS})add_definitions(${PCL_DEFINITIONS})
當(dāng)在工程中使用第三方庫時(shí),需要知道在哪里找頭文件;在哪里找?guī)煳募绘溄訋斓拿Q是什么等信息。使用find_package()命令會(huì)在模塊路徑中尋找Find.cmake,其中記錄了庫的相關(guān)信息。如實(shí)例代碼中,當(dāng)找到第三方庫的配置路徑后,就可以通過include_directories包含其頭文件;通過link_directories鏈接其庫文件;add_definitions的作用是控制庫的源代碼開關(guān),保證在更改第三方庫代碼時(shí),不對(duì)其源碼進(jìn)行破壞,并可以添加自己的功能。
file(GLOB_RECURSE?SRC_LIST????${CMAKE_CURRENT_SOURCE_DIR}/modules/src/*.cpp)aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/modules/common?SRC_COMMON_LIST)add_library(perception STATIC${SRC_LIST}${SRC_COMMON_LIST})
file()通過自定義搜索規(guī)則將文件中的內(nèi)容存儲(chǔ)到變量中,其中參數(shù)GLOB 會(huì)產(chǎn)生一個(gè)由所有匹配globbing表達(dá)式的文件組成的列表將其保存到變量SRC_LIST。或者使用參數(shù)GLOB_RECURSE遍歷匹配目錄的所有文件以及子目錄下面的文件將其保存到變量SRC_LIST。
aux_source_directory()的作用與file()類似,查找指定目錄下的所有源文件,然后將結(jié)果存進(jìn)指定變量名。如上述代碼將common路徑下的文件保存到變量SRC_COMMON_LIST中。
add_library用于將變量中保存的源文件打成庫的形式,可以通過參數(shù)選擇靜態(tài)庫或動(dòng)態(tài)庫。如:
add_library(perception STATIC?${SRC_LIST})
add_library(perception SHARED?${SRC_LIST})
link_directories(????${CMAKE_CURRENT_SOURCE_DIR}/modules/lib/)add_executable(main?${SRC_FILES})target_link_libraries(main????libdetect.a????libsegment.a????libtracker.a)
最后就是生成可執(zhí)行文件并鏈接庫文件的環(huán)節(jié),add_executable使用指定的源文件來生成目標(biāo)可執(zhí)行文件。
目標(biāo)可執(zhí)行文件分為三類:
普通可執(zhí)行目標(biāo)文件
導(dǎo)入可執(zhí)行目標(biāo)文件
別名可執(zhí)行目標(biāo)文件
其中l(wèi)ink_libraries和target_link_libraries這兩個(gè)命令長得挺相似,功能卻不同:
link_libraries是指定要鏈接的庫文件路徑。自己生成的庫文件可以用該指令指定目錄的路徑以便工程能夠找到。
target_link_libraries是將目標(biāo)文件與庫文件進(jìn)行鏈接,可以指定動(dòng)態(tài)庫/靜態(tài)庫,如果只提供庫名稱,系統(tǒng)會(huì)根據(jù)鏈接庫目錄搜索xxx.so 或者 xxx.a 文件;或者指定給出全路徑。
交流群
歡迎加入公眾號(hào)讀者群一起和同行交流,目前有SLAM、三維視覺、傳感器、自動(dòng)駕駛、計(jì)算攝影、檢測、分割、識(shí)別、醫(yī)學(xué)影像、GAN、算法競賽等微信群(以后會(huì)逐漸細(xì)分),請掃描下面微信號(hào)加群,備注:”昵稱+學(xué)校/公司+研究方向“,例如:”張三?+?上海交大?+?視覺SLAM“。請按照格式備注,否則不予通過。添加成功后會(huì)根據(jù)研究方向邀請進(jìn)入相關(guān)微信群。請勿在群內(nèi)發(fā)送廣告,否則會(huì)請出群,謝謝理解~

