Modern Cmake

歷史背景

CMake是一個構建系統(tǒng)生成器(build-system generator)辕狰。常見的構建系統(tǒng)绢慢,有Visual Studio值朋,XCode腻贰,Make等等吁恍。CMake可以支持不同平臺下構建系統(tǒng)的生成。
思考:ninja是構建系統(tǒng)嗎

CMake的出現(xiàn)已經有接近20年的歷史播演,它的發(fā)展過程也初步經歷了三個階段冀瓦。

  • ~2000 (~v2.x) ,剛剛啟動写烤,過程式描述為主翼闽。
  • 2000~2014 (v3.0~) ,引入Target概念洲炊。
  • 2014~now (~v3.15)感局,有了Target和Property的定義,更現(xiàn)代化暂衡。

概 述

現(xiàn)代化的CMake是圍繞 Target 和 Property 來定義的蓝厌,并且竭力避免出現(xiàn)變量variable的定義。Variable橫行是典型CMake2.8時期的風格」磐剑現(xiàn)代版的CMake更像是在遵循OOP的規(guī)則,通過target來約束link读恃、compile等相關屬性的作用域隧膘。

Target 概念

舊版 CMake 2.0 主要是基于 directory 來構建,很多復用只能靠變量實現(xiàn)寺惫。Modern CMake 最大的改進是引入了 target疹吃,支持了對構建的閉包性和傳播性的控制
,從而實現(xiàn)了構建可以模塊化西雀。

在 Modern CMake 中強烈推薦拋棄舊的 directory 方式萨驶,使用 target 的方式構建整個工程。

Target 分類

Target 中最核心的兩個分類是:executable, library艇肴。

其中 executable 是可執(zhí)行程序腔呜,在不同的操作系統(tǒng)會有不同的格式,同樣一個工程內也可能需要生成多個可執(zhí)行程序再悼。 具體指令如下所示:

add_executable(<name> [WIN32] [MACOSX_BUNDLE]
              [EXCLUDE_FROM_ALL]
              [source1] [source2 ...])

library 代表鏈接庫核畴,可以分為 share, static, object, module, interface 五個種類。

  • share 表示共享庫冲九,在編譯構建過程中谤草,需要鏈接但不會添加到最后的可執(zhí)行文件中。共享庫在程序運行中可以被動態(tài)加載和替換,當被多個程序使用時還可以在內存中被共享丑孩。如果期望 library 可以被獨立的部署和替換的話冀宴,需要選擇這種方式。
  • static 表示靜態(tài)庫温学,會在編譯過程中被一起添加生成到可執(zhí)行文件中略贮。當靜態(tài)庫的實現(xiàn)發(fā)生變更時,必須要重新編譯整個系統(tǒng)才可以使用枫浙。使用靜態(tài)庫的一個好處是刨肃,生成的可執(zhí)行程序可以獨立的運行,不再需要依賴這個靜態(tài)庫箩帚。
  • module 也是共享庫的一種真友,CMake 中限制了 moudle 類型的 libray 不能被編譯時鏈接,只能通過 dlopen 在運行時動態(tài)加載使用紧帕。
  • object 類型的庫表示一組編譯后的文件询筏,并不會打包和鏈接琅轧。使用 object 類型的庫可以避免一些大的源文件被重復的編譯,提升編譯效率。
  • interface 類型的并不會編譯輸出文件夺鲜,代表一組接口文件,可以在編譯構建中被其他 target 使用恤左。使用 interface 類型的庫可以把多個模塊公共的接口頭文件作為一個單獨 target 來被引用蔓肯,構建更加高效。

定義庫具體指令如下:

add_library(<name> [STATIC | SHARED | MODULE |OBJECT |INTERFACE] ...)

Target 閉包性

為了實現(xiàn) target 閉包性丽柿,Modern CMake 實現(xiàn) target 與 構建和使用中所有依賴建立綁定關系恢准,從而可以拿來即用。正常情況下編譯一個 target(可執(zhí)行程序或者庫)需要依賴如下所示:

  • 源文件列表甫题,通過 target_sources 配置馁筐。
  • 頭文件列表,通過 target_include_directories 配置坠非。
  • 預編譯宏敏沉,通過 target_compile_definition 配置。
  • 編譯選項和特性炎码,通過 target_compile_options盟迟,target_compile_features 配置。
  • 鏈接選項潦闲,通過 target_link_options 配置队萤。

在 C/C++軟件系統(tǒng)中,一個 target 中大部分的頭文件是僅在模塊內使用矫钓,為內部接口要尔,僅有小一部分接口頭文件是外部使用舍杜,稱為對外接口。在軟件設計過程中赵辕,要從高內聚低耦合的角度出發(fā)既绩,去嚴格設計每個 target 的外部接口和內部接口。同樣構建過程中还惠,在鏈接不同 target 時也需要明確指明依賴的外部接口文件饲握,從而提高編譯構建的效率。

為了更好支持這個特性蚕键,Modern CMake 針對 target 引入兩個概念:user requriement(用戶依賴) 和 build requirement(編譯依賴)救欧。用戶依賴表示 target 使用方需要的依賴,而編譯依賴表示當前 target 編譯構建時需要依賴锣光。

Modern CMake 增加了三個關鍵字 INTERFACE笆怠、PUBLIC、PRIVATE 分布表示不同作用域誊爹, 下面以添加頭文件依賴命令為例說明:

target_include_directories(<target> [SYSTEM] [BEFORE]
  <INTERFACE|PUBLIC|PRIVATE> [items1...]
  [<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])

給 target 添加頭文件依賴路徑時:

  • INTERFACE : 表示添加的頭文件路徑僅 target 的使用方需要蹬刷,編譯當前 target 并不需要。
  • PRIVATE : 表示添加的頭文件路徑僅當前 target 編譯時使用频丘,其他 target 不需要办成。
  • PUBLIC : 表示編譯時和鏈接該 target 都需要使用。

在 Modern CMake 中強烈建議為 target 添加依賴接口時搂漠,從使用者角度考慮寫明 INTERFACE迂卢, PRIVATE, PUBLIC。

在 Modern CMake 中推薦使用 target_sources 來添加源文件依賴桐汤,保持每個接口的職責單一而克。

Target 傳播性

當構建工程中 包含比較多的 libary 時,編譯和管理這些 Libary 之間的依賴就變得尤為重要惊科。在 Modern CMake 中,當給 Libary 定義用戶依賴和編譯依賴后亮钦,通過在 target_link_libraries 中定義與其他組件間的依賴關系, 就可以自動傳遞和推演 target 之間的所有編譯依賴馆截。

組件間的依賴關系定義命令如下:

target_link_libraries(<target>
                      <PRIVATE|PUBLIC|INTERFACE> <item>...
                     [<PRIVATE|PUBLIC|INTERFACE> <item>...]...)
  • PRIVATE: 被依賴 libary 的 user requirement 的會變成當前 target 的 build requirement
  • PUBLIC:被依賴 libary 的 user requirement 的會變成當前 target 的 build requirement 和 user requirement.
  • INTERFACE:被依賴 libary 的 user requirement 的會變成當前 target 的 user requirement
    充分利用 Modern CMake 強大的依賴傳遞功能,合理設計每個 target 間的依賴關系蜂莉。

如果把一個Target想象成一個對象(Object)蜡娶,會發(fā)現(xiàn)兩者的組織方式非常相似:

構造函數(shù):

  • add_executable
  • add_library

成員函數(shù):

  • get_target_property()
  • set_target_properties()
  • get_property(TARGET)
  • set_property(TARGET)
  • target_compile_definitions()
  • target_compile_features()
  • target_compile_options()
  • target_include_directories()
  • target_link_libraries()
  • target_sources()

成員變量:

  • Target properties(太多)

知識點和principle

1、在現(xiàn)代IDE中的Multi-configuration

cmake -DCMAKE_BUILD_TYPE=Release .. // 在xcode或vs上不生效映穗,build type選擇后移至IDE中控制窖张,而非cmake階段。
cmake --build . --config release // Apple蚁滋、MSVC使用cmake命令行構建時release包時需要加上--config參數(shù)宿接,否則默認debug赘淮。
在現(xiàn)代IDE中,Build-type一般都不是在CMake config期間能確定的睦霎。如VS梢卸,XCode都支持Multi-configuration,具體使用Debug還是Release是在編譯時才確定副女,那如果Target的依賴路徑或者依賴庫需要區(qū)分Configuration來配置該怎么辦呢蛤高?在傳統(tǒng)CMake中是比較難辦的,target_link_libraries提供了一種手段碑幅,可以用debug和optimized來區(qū)分具體的庫名戴陡,而其他的編譯或鏈接設置則比較困難。在Modern CMake中沟涨,我們可以通過generator-expression來實現(xiàn)恤批。

generator-expression定義為$<...>的形式。該表達式的值有多種形式拷窜,而且支持嵌套使用:

條件表達式
$<condition:true_string> 當條件為1時开皿,表達式為true_string,否則為空
$<IF:condition,true_string,false_string> 當條件為1時,表達式為true_string篮昧,否則為false_string
變量表達式
$<TARGET_EXISTS:target> 當target存在為1赋荆,否則為0
$<CONFIG:cfg> 當config為cfg時為1,否則為0懊昨。這是非常高頻使用的一個表達式窄潭,可以通過它來區(qū)分Debug/Release等不同的config。如下例所示酵颁,通過嵌套使用上述兩個表達式嫉你,可以達到區(qū)分CONFIG來設置依賴庫路徑的目的。
target_link_directories(${PROJECT_NAME} PUBLIC                                                                                                                                                                      
  $<$<CONFIG:Debug>:${CONAN_LIB_DIRS_DEBUG}>                                                                                                                                                                        
  $<$<CONFIG:Release>:${CONAN_LIB_DIRS_RELEASE}>) 

思考:假設target A依賴于TARGET B C D E躏惋,希望A和C是debug幽污,B D E是release,是否支持

2簿姨、Forget those commands:
  • add_compile_options()
  • include_directories()
  • link_directories()
  • link_libraries()
3距误、install和find_package
  • install
install(TARGETS MyLib
        EXPORT MyLibTargets 
        LIBRARY DESTINATION lib  # 動態(tài)庫安裝路徑
        ARCHIVE DESTINATION lib  # 靜態(tài)庫安裝路徑
        RUNTIME DESTINATION bin  # 可執(zhí)行文件安裝路徑
        PUBLIC_HEADER DESTINATION include  # 頭文件安裝路徑
        )

LIBRARY, ARCHIVE, RUNTIME, PUBLIC_HEADER是可選的,可以根據(jù)需要進行選擇扁位。 准潭、
DESTINATION后面的路徑可以自行制定,根目錄默認為CMAKE_INSTALL_PREFIX,可以試用set方法進行指定域仇,如果使用默認值的話刑然,Unix系統(tǒng)的默認值為 /usr/local, Windows的默認值為 c:/Program Files/${PROJECT_NAME}。

  • find_package
SET(MyLib_DIR "${CMAKE_INSTALL_PREFIX}/lib/cmake/MyLib" CACHE FILEPATH "MyLib package." FORCE)
FIND_PACKAGE(MyLib REQUIRED CONFIG)

設置${PROJECT_NAME}_DIR暇务,F(xiàn)IND_PACKAGE使用CONFIG模式泼掠。
如果使用默認的FIND_PACKAGE module模式怔软,行為如何?

4武鲁、ExternalProjects

Create custom targets to build projects in external trees

INCLUDE(ExternalProject)

SET(JSONCPP_SOURCES_DIR ${THIRD_PARTY_DIR}/jsoncpp)
SET(JSONCPP_INSTALL_DIR ${CMAKE_CURRENT_BINARY_DIR}/third_party)
SET(JSONCPP_ROOT ${JSONCPP_INSTALL_DIR} CACHE FILEPATH "jsoncpp root directory." FORCE)
SET(JSONCPP_INCLUDE_DIR "${JSONCPP_INSTALL_DIR}/include" CACHE PATH "jsoncpp include directory." FORCE)

IF(WIN32)
  SET(JSONCPP_LIBRARIES "${JSONCPP_INSTALL_DIR}/lib/jsoncpp.lib" CACHE FILEPATH "jsoncpp library." FORCE)
ELSE(WIN32)
  SET(JSONCPP_LIBRARIES "${JSONCPP_INSTALL_DIR}/lib/libjsoncpp.a" CACHE FILEPATH "jsoncpp library." FORCE)
ENDIF(WIN32)

INCLUDE_DIRECTORIES(${JSONCPP_INCLUDE_DIR})

ExternalProject_Add(
    extern_jsoncpp
    ${EXTERNAL_PROJECT_LOG_ARGS}
    DEPENDS
    SOURCE_DIR       ${JSONCPP_SOURCES_DIR}
    UPDATE_COMMAND   ""
    DOWNLOAD_COMMAND ""
    ${EXTERNAL_PROJECT_CMAKE_ARGS}
    BUILD_BYPRODUCTS ${JSONCPP_LIBRARIES}
    CMAKE_ARGS      -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
    CMAKE_ARGS      -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}
    CMAKE_ARGS      -DCMAKE_AR=${CMAKE_AR}
    CMAKE_ARGS      -DCMAKE_RANLIB=${CMAKE_RANLIB}
    CMAKE_ARGS      -DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}
    CMAKE_ARGS      -DCMAKE_C_FLAGS=${CMAKE_C_FLAGS}
    CMAKE_ARGS      -DCMAKE_INSTALL_PREFIX=${JSONCPP_INSTALL_DIR}
                    -DBUILD_SHARED_LIBS=OFF
                    -DCMAKE_POSITION_INDEPENDENT_CODE=ON
                    -DCMAKE_MACOSX_RPATH=ON
    CMAKE_ARGS      -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
    CMAKE_CACHE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=${JSONCPP_INSTALL_DIR}
                     -DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=ON
                     -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE}
)

ADD_LIBRARY(jsoncpp STATIC IMPORTED GLOBAL)
SET_PROPERTY(TARGET jsoncpp PROPERTY IMPORTED_LOCATION ${JSONCPP_LIBRARIES})
ADD_DEPENDENCIES(jsoncpp extern_jsoncpp)

ExternalProject_Add函數(shù)會創(chuàng)建target爽雄,除了編譯還會執(zhí)行download和update step。
思考:ExternalProject創(chuàng)建的target是否會集成其實際編譯工程內target的屬性沐鼠?

5挚瘟、cmake policies

CMake 在添加新特性后可能不會完全兼容舊的 CMake 版本,這導致了在新版本的 CMake 中使用舊的 CMakeLists 文件時可能會存在一些問題饲梭。策略的引入就是幫助用戶和開發(fā)者解決這些問題乘盖,它是 CMake 中用來改善向后兼容性和追蹤兼容性的一種機制。 CMake 中的所有策略都被賦予一個 CMPNNNN 格式的識別符憔涉,其中 NNNN 是一個整數(shù)值订框。策略通常既保留了用于保持舊版本兼容性的舊行為,又包含了讓用戶在新項目中優(yōu)先使用的正確的新行為兜叨。每個策略相關的文檔都會描述舊行為和新行為穿扳,以及引入該策略的原因。

  • 設置策略
    工程可以設置各種策略來選擇新的或舊的行為国旷。當 CMake 遇到會被特殊策略影響的用戶代碼時矛物,它會檢查工程是否設置了策略。如果沒有設置策略跪但,工程會使用舊行為履羞,并會給出警告要求項目作者設置工程的策略。
      有許多方法設置一個策略的行為屡久,最快速的方式是設置所有的策略版本與編寫項目的 CMake 版本一致忆首,設置策略的版本會獲取所有指定的版本或更早的版本中引入的策略。所有指定的版本之后引入的策略會標記為未設置被环,這是為了輸出這些新策略合適的警告信息糙及。設置策略版本的命令為:
cmake_policy (VERSION major.minor[.patch[.tweak]])
cmake_policy (SET CMPNNNN OLD) 
cmake_policy (SET CMPNNNN NEW)

使用 NEW 選項的 cmake_policy 命令明確告訴 CMake 使用策略的新行為。
A policy should almost never be set to OLD, except to silence warnings in an otherwise frozen or stable codebase, or temporarily as part of a larger migration path.

if (POLICY CMP0042)
  cmake_policy (SET CMP0042 NEW)
endif (POLICY CMP0042)

if (POLICY CMP0063)
  cmake_policy (SET CMP0063 NEW)
endif (POLICY CMP0063)

GLOG工程中的cmake
CMP0042
MACOSX_RPATH is enabled by default.
CMake 2.8.12 and newer has support for using @rpath in a target’s install name. This was enabled by setting the target property MACOSX_RPATH. The @rpath in an install name is a more flexible and powerful mechanism than @executable_path or @loader_path for locating shared libraries.
CMake 3.0 and later prefer this property to be ON by default. Projects wanting @rpath in a target’s install name may remove any setting of the INSTALL_NAME_DIR and CMAKE_INSTALL_NAME_DIR variables.
This policy was introduced in CMake version 3.0. CMake version 3.0.2 warns when the policy is not set and uses OLD behavior. Use the cmake_policy command to set it to OLD or NEW explicitly.
思考:cmake policy是否支持用戶自定義筛欢?

6浸锨、使用target_sources而非GLOB
add_library(evolution"")
target_sources(evolution
  PRIVATE
      ${CMAKE_CURRENT_LIST_DIR}/evolution.cpp
  PUBLIC
      ${CMAKE_CURRENT_LIST_DIR}/evolution.hpp
  )

VS

file(GLOB srcs "*.cpp")
add_library(${srcs })

OOP思想的modern cmake VS 面向過程
使用GLOB正則匹配后,增刪文件cmake系統(tǒng)無感知悴能,cmake官方強烈建議不使用GLOB的方式引入源文件揣钦。

提問

1雳灾、如果未指定<PRIVATE|PUBLIC|INTERFACE>漠酿,默認行為是?

target_link_libraries(hello-world PUBLIC hello)
target_include_directories(hello-world PUBLIC hello)

2谎亩、如何隔離某個target的編譯參數(shù)炒嘲?
3宇姚、一個target是否可以既是static,又是shared夫凸?
4浑劳、cmake是否同時支持多種generator輸出

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市夭拌,隨后出現(xiàn)的幾起案子魔熏,更是在濱河造成了極大的恐慌,老刑警劉巖鸽扁,帶你破解...
    沈念sama閱讀 222,627評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蒜绽,死亡現(xiàn)場離奇詭異,居然都是意外死亡桶现,警方通過查閱死者的電腦和手機躲雅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來骡和,“玉大人相赁,你說我怎么就攤上這事∥坑冢” “怎么了钮科?”我有些...
    開封第一講書人閱讀 169,346評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長东囚。 經常有香客問我跺嗽,道長,這世上最難降的妖魔是什么页藻? 我笑而不...
    開封第一講書人閱讀 60,097評論 1 300
  • 正文 為了忘掉前任桨嫁,我火速辦了婚禮,結果婚禮上份帐,老公的妹妹穿的比我還像新娘璃吧。我一直安慰自己,他們只是感情好废境,可當我...
    茶點故事閱讀 69,100評論 6 398
  • 文/花漫 我一把揭開白布畜挨。 她就那樣靜靜地躺著,像睡著了一般噩凹。 火紅的嫁衣襯著肌膚如雪巴元。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,696評論 1 312
  • 那天驮宴,我揣著相機與錄音逮刨,去河邊找鬼。 笑死堵泽,一個胖子當著我的面吹牛修己,可吹牛的內容都是我干的恢总。 我是一名探鬼主播,決...
    沈念sama閱讀 41,165評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼睬愤,長吁一口氣:“原來是場噩夢啊……” “哼片仿!你這毒婦竟也來了?” 一聲冷哼從身側響起尤辱,我...
    開封第一講書人閱讀 40,108評論 0 277
  • 序言:老撾萬榮一對情侶失蹤砂豌,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后光督,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體奸鸯,經...
    沈念sama閱讀 46,646評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,709評論 3 342
  • 正文 我和宋清朗相戀三年可帽,在試婚紗的時候發(fā)現(xiàn)自己被綠了娄涩。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,861評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡映跟,死狀恐怖蓄拣,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情努隙,我是刑警寧澤球恤,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站荸镊,受9級特大地震影響咽斧,放射性物質發(fā)生泄漏。R本人自食惡果不足惜躬存,卻給世界環(huán)境...
    茶點故事閱讀 42,196評論 3 336
  • 文/蒙蒙 一张惹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧岭洲,春花似錦宛逗、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,698評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至告私,卻和暖如春屎暇,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背驻粟。 一陣腳步聲響...
    開封第一講書人閱讀 33,804評論 1 274
  • 我被黑心中介騙來泰國打工根悼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 49,287評論 3 379
  • 正文 我出身青樓番挺,卻偏偏與公主長得像,于是被迫代替她去往敵國和親屯掖。 傳聞我的和親對象是個殘疾皇子玄柏,可洞房花燭夜當晚...
    茶點故事閱讀 45,860評論 2 361

推薦閱讀更多精彩內容

  • CMake 是一個開源的跨平臺自動化建構系統(tǒng),是目前最主流的 C/C++語言構建工具贴铜。CMake3.0 之后引入很...
    尉剛強閱讀 10,897評論 3 11
  • CMake is a great tool for managing a C++ system’s build. ...
    XBruce閱讀 595評論 0 1
  • 前言 相信每個人都寫過CMakeLists粪摘,然而,“一千個讀者心中有一千個哈姆雷特”绍坝,一千個程序員也能寫出一千種C...
    奇林的徒步學園閱讀 2,625評論 0 1
  • 前言 相信每個人都寫過CMakeLists徘意,然而,“一千個讀者心中有一千個哈姆雷特”轩褐,一千個程序員也能寫出一千種C...
    金戈大王閱讀 5,858評論 0 2
  • 最近在負責一個大型工程的CMake編譯系統(tǒng)管理椎咧,整理一些工作過程中積累下來的知識片段和技巧。CMake是一個跨平臺...
    啊呀喲嘿閱讀 8,856評論 0 2