摘自:https://zhuanlan.zhihu.com/p/76975231
Modern CMake 簡介
CMake是一個構建系統生成器(build-system generator)。常見的構建系統,有Visual Studio,XCode,Make等等。CMake可以支援不同平臺下構建系統的生成。
CMake的出現已經有接近20年的歷史,它的發展過程也初步經歷了三個階段。
- ~2000 (~v2.x) ,剛剛啟動,過程式描述為主。
- 2000~2014 (v3.0~) ,引入Target概念。
- 2014~now (~v3.15),有了Target和Property的定義,更現代化。
概 述
現代化的CMake是圍繞 Target 和 Property 來定義的,並且竭力避免出現變數variable的定義。Variable橫行是典型CMake2.8時期的風格。現代版的CMake更像是在遵循OOP的規則,透過target來約束link、compile等相關屬性的作用域。如果把一個Target想象成一個物件(Object),會發現兩者的組織方式非常相似:
- 建構函式:
- add_executable
- add_library
- 成員函式:
- 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(太多)
在Target中有兩個概念非常重要:Build-Requirements 和 Usage-Requirements。這兩個概念對於理解為什麼現代CMake會如此設計提供了指導意義。
- Build-Requirements: 包含了所有構建Target必須的材料。如原始碼,include路徑,預編譯命令,連結依賴,編譯/連結選項,編譯/連結特性等。
- Usage-Requirements:包含了所有使用Target必須的材料。如原始碼,include路徑,預編譯命令,連結依賴,編譯/連結選項,編譯/連結特性等。這些往往是當另一個Target需要使用當前target時,必須包含的依賴。
傳統的CMake和現代化的CMake的主要區別(非語法層面)如下圖所示。Traditioncal CMake在設定build-requirements和usage-requirements上都依賴手動輸入命令,並且人工維持其作用域(變數的作用域以目錄為單位)。而Modern CMake在設定上述requirement均以target為單位,所以在傳遞target屬性到其依賴的下游鏈條中更自動也更智慧。
在Moden CMake中新增了不少關鍵字,其中最常見的是PUBLIC、PRIVATE、INTERFACE。
- PRIVATE/INTERFACE/PUBLIC:定義了Target屬性的傳遞範圍。
- PRIVATE: 表示Target的屬性只定義在當前Target中,任何依賴當前Target的Target不共享PRIVATE關鍵字下定義的屬性。
- INTERFACE:表示Target的屬性不適用於其自身,而只適用於依賴其的Target。
- PUBLIC:表示Target的屬性既是build-requirements也是usage-requirements。凡是依賴。凡是依賴於當前Target的Target都會共享本屬性。
解剖麻雀
我們來嘗試寫一個例項,看看在CMake v3.13及以後版本中的寫法如何。
HelloWorld
|___ CMakeLists.txt
|___ hello-exe
|______ CMakeLists.txt
|______ main.cpp
|___ hello-lib
|______ CMakeLists.txt
|______ hello.hpp
|______ hello.cpp
以這樣一個簡單的HelloWorld開啟有助於我們快速進入主題。這個專案結構很簡單,包含兩個子資料夾,hello-exe生成executable,hello-lib生成連結庫(動態)。
- 我們先看下頂層CMakeLists的內容:
# HelloWorld/CMakeLists.txt
cmake_minimum_required(VERSION 3.14)
project(HelloWorld VERSION 1.0.0)
add_subdirectory(hello-lib)
add_subdirectory(hello-exe)
這裡沒有什麼值得多討論的,與傳統CMake一樣的寫法,定義project名稱,版本號,新增子資料夾。
- 我們接著看hello-lib。首先看原始碼。
原始碼比較簡單,只是定義一個hello_printer類,並在其cpp中定義成員函式print。請注意標頭檔案中的預編譯命令。這在VS中是非常常用的預編譯命令,用於匯出動態庫的符號。而當該庫被其他Target呼叫時,需要使用dllimport匯入符號。注意這條預編譯命令剛好符合build-requirement和usage-requirement的定義。對於hello-lib而言,定義DLL_EXPORT從而將DLL_API定義為_declspec(dllexport)是build-requirement,而對於該Target的呼叫者,需要的是不定義DLL_EXPORT。因而需要在定義compile_definitions 時將Dll_EXPORT放在PRIVATE關鍵詞下。
當其他Target使用hello-lib的時候,還需要知道hello.hpp的路徑。傳統的CMake寫法是透過在呼叫者的CMakeLists.txt中新增includedirectory來實現。但這種寫法會依賴庫之間的相對路徑,一旦調整路徑,所有的CMakeLists都將需要更新。在Modern CMake中不必如此,你只需要透過target_include_directories指定hello.hpp的路徑,將之納入INTERFACE(當然PUBLIC)也行。則呼叫者就可以得到該include路徑。
CMakeLists.txt 全文如下:
set(target_name "hello-lib")
add_library(${target_name} SHARED
hello.cpp
hello.hpp
)
target_include_directories(${target_name} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_definitions(${target_name} PRIVATE DLL_EXPORT)
- 最後看下hello-exe。hello-exe中的CMakeLists.txt就可以比較簡單了:
add_executable(hello-exe main.cpp)
target_link_libraries(hello-exe PUBLIC hello-lib)
補充
Modern CMake中還有些有意思的知識點,這裡沒法一一覆蓋,只能稍稍展開。最有意思的點是generator-expression。在現代IDE中,Build-type一般都不是在CMake config期間能確定的。如VS,XCode都支援Multi-configuration,具體使用Debug還是Release是在編譯時才確定,那如果Target的依賴路徑或者依賴庫需要區分Configuration來配置該怎麼辦呢?在傳統CMake中是比較難辦的,target_link_libraries提供了一種手段,可以用debug和optimized來區分具體的庫名,而其他的編譯或連結設定則比較困難。在Modern CMake中,我們可以透過generator-expression來實現。
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。這是非常高頻使用的一個表示式,可以透過它來區分Debug/Release等不同的config。如下例所示,透過巢狀使用上述兩個表示式,可以達到區分CONFIG來設定依賴庫路徑的目的。
target_link_directories(${PROJECT_NAME} PUBLIC
$<$<CONFIG:Debug>:${CONAN_LIB_DIRS_DEBUG}>
$<$<CONFIG:Release>:${CONAN_LIB_DIRS_RELEASE}>)
- ... 太多了,不一一列舉。
以上是Modern CMake中常用的內容,還有些如IMPORTED,ALIAS暫時還沒用到,等用到再更新吧。
參考
- https://crascit.com/2016/01/31/enhanced-source-file-handling-with-target_sources/#comment-414
- onqtam/awesome-cmake
- https://www.youtube.com/watch?