cmake-buildsystem(7)

簡介

基於 CMake 的建置系統組織為一組高階邏輯目標。每個目標對應於一個執行檔或函式庫,或是一個包含自訂命令的自訂目標。目標之間的相依性在建置系統中表達,以決定建置順序以及響應變更的重新生成規則。

二進制目標

執行檔和函式庫使用 add_executable()add_library() 命令定義。產生的二進制檔案具有適合目標平台的 PREFIXSUFFIX 和副檔名。二進制目標之間的相依性使用 target_link_libraries() 命令表達

add_library(archive archive.cpp zip.cpp lzma.cpp)
add_executable(zipapp zipapp.cpp)
target_link_libraries(zipapp archive)

archive 定義為 STATIC 函式庫 -- 一個包含從 archive.cppzip.cpplzma.cpp 編譯的物件的封存檔。zipapp 定義為透過編譯和連結 zipapp.cpp 所形成的執行檔。當連結 zipapp 執行檔時,會連結 archive 靜態函式庫。

執行檔

執行檔是透過將物件檔案連結在一起而建立的二進制檔案,其中一個物件檔案包含程式進入點,例如 main

add_executable() 命令定義一個執行檔目標

add_executable(mytool mytool.cpp)

CMake 產生建置規則,將原始碼檔案編譯成物件檔案,並將它們連結成一個執行檔。

執行檔的連結相依性可以使用 target_link_libraries() 命令指定。連結器從從執行檔自身的原始碼檔案編譯的物件檔案開始,然後透過搜尋連結的函式庫來解析剩餘的符號相依性。

諸如 add_custom_command() 之類的命令,會產生在建置時執行的規則,可以透明地使用 EXECUTABLE 目標作為 COMMAND 執行檔。建置系統規則將確保執行檔在嘗試執行命令之前被建置。

靜態函式庫

靜態函式庫是物件檔案的封存檔。它們是由封存器而不是連結器產生的。執行檔共享函式庫模組函式庫 可以連結到靜態函式庫作為相依性。連結器根據需要從靜態函式庫中選擇物件檔案的子集,以解析符號並將其連結到消耗二進制檔案中。每個連結到靜態函式庫的二進制檔案都會獲得其自己的符號副本,而靜態函式庫本身在執行時期不需要。

add_library() 命令在以 STATIC 函式庫類型呼叫時,定義一個靜態函式庫目標

add_library(archive STATIC archive.cpp zip.cpp lzma.cpp)

或者,當 BUILD_SHARED_LIBS 變數為 false 時,不使用類型

add_library(archive archive.cpp zip.cpp lzma.cpp)

CMake 產生建置規則,將原始碼檔案編譯成物件檔案,並將它們封存到一個靜態函式庫中。

靜態函式庫的連結相依性可以使用 target_link_libraries() 命令指定。由於靜態函式庫是封存檔而不是連結的二進制檔案,因此它們的連結相依性的物件檔案不包含在函式庫本身中(除了指定為直接連結相依性的 物件函式庫 之外)。相反,CMake 會記錄靜態函式庫的連結相依性,以便在連結消耗二進制檔案時可傳遞地使用。

共享函式庫

共享函式庫是透過將物件檔案連結在一起而建立的二進制檔案。執行檔、其他共享函式庫和 模組函式庫 可以連結到共享函式庫作為相依性。連結器在消耗二進制檔案中記錄對共享函式庫的參考。在執行時期,動態載入器在磁碟上搜尋參考的共享函式庫並載入其符號。

add_library() 命令在以 SHARED 函式庫類型呼叫時,定義一個共享函式庫目標

add_library(archive SHARED archive.cpp zip.cpp lzma.cpp)

或者,當 BUILD_SHARED_LIBS 變數為 true 時,不使用類型

add_library(archive archive.cpp zip.cpp lzma.cpp)

CMake 產生建置規則,將原始碼檔案編譯成物件檔案,並將它們連結成一個共享函式庫。

共享函式庫的連結相依性可以使用 target_link_libraries() 命令指定。連結器從從共享函式庫自身的原始碼檔案編譯的物件檔案開始,然後透過搜尋連結的函式庫來解析剩餘的符號相依性。

注意

CMake 期望共享函式庫至少匯出一個符號。如果一個函式庫沒有匯出任何非託管符號,例如 Windows 資源 DLL 或 C++/CLI DLL,請將其設為 模組函式庫

Apple Frameworks

共享函式庫靜態函式庫 可以使用 FRAMEWORK 目標屬性標記,以建立 macOS 或 iOS Framework。具有 FRAMEWORK 目標屬性的函式庫也應設定 FRAMEWORK_VERSION 目標屬性。按照 macOS 慣例,此屬性通常設定為值 "A"。MACOSX_FRAMEWORK_IDENTIFIER 設定 CFBundleIdentifier 鍵,並且它唯一地識別 bundle。

add_library(MyFramework SHARED MyFramework.cpp)
set_target_properties(MyFramework PROPERTIES
  FRAMEWORK TRUE
  FRAMEWORK_VERSION A # Version "A" is macOS convention
  MACOSX_FRAMEWORK_IDENTIFIER org.cmake.MyFramework
)

模組函式庫

模組函式庫是透過將物件檔案連結在一起而建立的二進制檔案。與 共享函式庫 不同,模組函式庫可能不會被其他二進制檔案作為相依性連結 -- 不要在 target_link_libraries() 命令的右手邊命名它們。相反,模組函式庫是應用程式可以在執行時期按需動態載入的插件,例如,透過 dlopen

add_library() 命令在以 MODULE 函式庫類型呼叫時,定義一個模組函式庫目標

add_library(archivePlugin MODULE 7z.cpp)

CMake 產生建置規則,將原始碼檔案編譯成物件檔案,並將它們連結成一個模組函式庫。

模組函式庫的連結相依性可以使用 target_link_libraries() 命令指定。連結器從從模組函式庫自身的原始碼檔案編譯的物件檔案開始,然後透過搜尋連結的函式庫來解析剩餘的符號相依性。

物件函式庫

物件函式庫是透過編譯原始碼檔案而建立的物件檔案的集合,沒有任何封存或連結。物件檔案可以在連結 執行檔共享函式庫模組函式庫 時使用,或者在封存 靜態函式庫 時使用。

add_library() 命令在以 OBJECT 函式庫類型呼叫時,定義一個物件函式庫目標

add_library(archiveObjs OBJECT archive.cpp zip.cpp lzma.cpp)

CMake 產生建置規則,將原始碼檔案編譯成物件檔案。

其他目標可以透過使用 產生器表達式 語法 $<TARGET_OBJECTS:name> 將物件檔案指定為原始碼輸入

add_library(archiveExtras STATIC $<TARGET_OBJECTS:archiveObjs> extras.cpp)

add_executable(test_exe $<TARGET_OBJECTS:archiveObjs> test.cpp)

消耗目標使用來自它們自身原始碼和來自命名的物件函式庫的物件檔案進行連結(或封存)。

或者,物件函式庫可以指定為其他目標的連結相依性

add_library(archiveExtras STATIC extras.cpp)
target_link_libraries(archiveExtras PUBLIC archiveObjs)

add_executable(test_exe test.cpp)
target_link_libraries(test_exe archiveObjs)

消耗目標使用來自它們自身原始碼和來自透過 target_link_libraries() 指定為直接連結相依性的物件函式庫的物件檔案進行連結(或封存)。請參閱 連結物件函式庫

物件函式庫不能在 add_custom_command(TARGET) 命令簽章的使用中作為 TARGET 使用。但是,物件列表可以被 add_custom_command(OUTPUT)file(GENERATE) 透過使用 $<TARGET_OBJECTS:objlib> 來使用。

建置規範和使用需求

目標根據它們自己的 建置規範 以及從它們的連結相依性傳播的 使用需求 進行建置。兩者都可以使用目標特定的 命令 來指定。

例如

add_library(archive SHARED archive.cpp zip.cpp)

if (LZMA_FOUND)
  # Add a source implementing support for lzma.
  target_sources(archive PRIVATE lzma.cpp)

  # Compile the 'archive' library sources with '-DBUILDING_WITH_LZMA'.
  target_compile_definitions(archive PRIVATE BUILDING_WITH_LZMA)
endif()

target_compile_definitions(archive INTERFACE USING_ARCHIVE_LIB)

add_executable(consumer consumer.cpp)

# Link 'consumer' to 'archive'.  This also consumes its usage requirements,
# so 'consumer.cpp' is compiled with '-DUSING_ARCHIVE_LIB'.
target_link_libraries(consumer archive)

目標命令

目標特定命令填充 二進制目標建置規範二進制目標介面函式庫匯入目標使用需求

調用必須指定範圍關鍵字,每個關鍵字都會影響其後參數的能見度。範圍如下:

PUBLIC

同時填充用於 建置 的屬性和用於 使用 目標的屬性。

PRIVATE

僅填充用於 建置 目標的屬性。

INTERFACE

僅填充用於 使用 目標的屬性。

命令如下:

target_compile_definitions()

填充 COMPILE_DEFINITIONS 建置規範和 INTERFACE_COMPILE_DEFINITIONS 使用需求屬性。

例如,呼叫

target_compile_definitions(archive
  PRIVATE   BUILDING_WITH_LZMA
  INTERFACE USING_ARCHIVE_LIB
)

BUILDING_WITH_LZMA 附加到目標的 COMPILE_DEFINITIONS 屬性,並將 USING_ARCHIVE_LIB 附加到目標的 INTERFACE_COMPILE_DEFINITIONS 屬性。

target_compile_options()

填充 COMPILE_OPTIONS 建置規範和 INTERFACE_COMPILE_OPTIONS 使用需求屬性。

target_compile_features()

在版本 3.1 中新增。

填充 COMPILE_FEATURES 建置規範和 INTERFACE_COMPILE_FEATURES 使用需求屬性。

target_include_directories()

填充 INCLUDE_DIRECTORIES 建置規範和 INTERFACE_INCLUDE_DIRECTORIES 使用需求屬性。使用 SYSTEM 選項,它還填充 INTERFACE_SYSTEM_INCLUDE_DIRECTORIES 使用需求。

為了方便起見,可以啟用 CMAKE_INCLUDE_CURRENT_DIR 變數,以將原始碼目錄和對應的建置目錄作為所有目標上的 INCLUDE_DIRECTORIES 添加。同樣地,可以啟用 CMAKE_INCLUDE_CURRENT_DIR_IN_INTERFACE 變數,以將它們作為所有目標上的 INTERFACE_INCLUDE_DIRECTORIES 添加。

target_sources()

在版本 3.1 中新增。

填充 SOURCES 建置規範和 INTERFACE_SOURCES 使用需求屬性。

它還支援指定 檔案集,它可以添加 C++ 模組原始碼和未在 SOURCESINTERFACE_SOURCES 屬性中列出的標頭。檔案集也可以使用包含標頭的包含目錄來填充 INCLUDE_DIRECTORIES 建置規範和 INTERFACE_INCLUDE_DIRECTORIES 使用需求屬性。

target_precompile_headers()

在版本 3.16 中新增。

填充 PRECOMPILE_HEADERS 建置規範和 INTERFACE_PRECOMPILE_HEADERS 使用需求屬性。

target_link_libraries()

填充 LINK_LIBRARIES 建置規範和 INTERFACE_LINK_LIBRARIES 使用需求屬性。

這是連結相依性及其 使用需求 被可傳遞地傳播以影響目標的編譯和連結的主要機制。

target_link_directories()

在版本 3.13 中新增。

填充 LINK_DIRECTORIES 建置規範和 INTERFACE_LINK_DIRECTORIES 使用需求屬性。

target_link_options()

在版本 3.13 中新增。

填充 LINK_OPTIONS 建置規範和 INTERFACE_LINK_OPTIONS 使用需求屬性。

目標建置規範

二進制目標 的建置規範由目標屬性表示。對於以下每個 編譯連結 屬性,目標的編譯和連結都受到其自身值以及對應的 使用需求 屬性(以 INTERFACE_ 前綴命名)的影響,這些屬性從連結相依性的可傳遞閉包中收集。

目標編譯屬性

這些代表用於編譯目標的 建置規範

COMPILE_DEFINITIONS

用於編譯目標中原始碼的編譯定義列表。這些以未指定的順序透過 -D 標誌或等效標誌傳遞給編譯器。

DEFINE_SYMBOL 目標屬性也用作編譯定義,作為 SHAREDMODULE 函式庫目標的特殊便利情況。

COMPILE_OPTIONS

用於編譯目標中原始碼的編譯選項列表。這些以出現的順序作為標誌傳遞給編譯器。

編譯選項會自動為 shell 逸出。

某些編譯選項最好透過專用設定來指定,例如 POSITION_INDEPENDENT_CODE 目標屬性。

COMPILE_FEATURES

在版本 3.1 中新增。

編譯目標中原始碼所需的 編譯功能 列表。通常,這些確保目標的原始碼使用足夠的語言標準級別進行編譯。

INCLUDE_DIRECTORIES

用於編譯目標中原始碼的包含目錄列表。這些以出現的順序透過 -I-isystem 標誌或等效標誌傳遞給編譯器。

為了方便起見,可以啟用 CMAKE_INCLUDE_CURRENT_DIR 變數,以將原始碼目錄和對應的建置目錄作為所有目標上的 INCLUDE_DIRECTORIES 新增。

SOURCES

與目標相關聯的原始碼檔案列表。 這包括在透過 add_executable()add_library()add_custom_target() 命令建立目標時指定的原始碼。 它也包括透過 target_sources() 命令新增的原始碼,但不包括 檔案集合

PRECOMPILE_HEADERS

在版本 3.16 中新增。

要在目標中編譯原始碼時預先編譯和包含的標頭檔列表。

AUTOMOC_MACRO_NAMES

在版本 3.10 中新增。

AUTOMOC 使用的巨集名稱列表,用於判斷目標中的 C++ 原始碼是否需要由 moc 處理。

AUTOUIC_OPTIONS

在版本 3.0 中新增。

AUTOUIC 在為目標調用 uic 時使用的選項列表。

目標使用需求

目標的使用需求是傳播給消費者的設定,消費者透過 target_link_libraries() 連結到目標,以便正確地與其編譯和連結。 它們由可遞移的編譯連結屬性表示。

請注意,使用需求並非旨在讓下游僅為了方便而使用特定的 COMPILE_OPTIONSCOMPILE_DEFINITIONS 等等。 屬性的內容必須是需求,而不僅僅是建議。

請參閱 cmake-packages(7) 手冊的建立可重新定位的套件章節,以了解在建立用於重新發佈的套件時,指定使用需求時必須格外注意的事項。

目標的使用需求可以遞移地傳播給依賴項。 target_link_libraries() 命令具有 PRIVATEINTERFACEPUBLIC 關鍵字,以控制傳播。

add_library(archive archive.cpp)
target_compile_definitions(archive INTERFACE USING_ARCHIVE_LIB)

add_library(serialization serialization.cpp)
target_compile_definitions(serialization INTERFACE USING_SERIALIZATION_LIB)

add_library(archiveExtras extras.cpp)
target_link_libraries(archiveExtras PUBLIC archive)
target_link_libraries(archiveExtras PRIVATE serialization)
# archiveExtras is compiled with -DUSING_ARCHIVE_LIB
# and -DUSING_SERIALIZATION_LIB

add_executable(consumer consumer.cpp)
# consumer is compiled with -DUSING_ARCHIVE_LIB
target_link_libraries(consumer archiveExtras)

由於 archivearchiveExtrasPUBLIC 依賴項,因此它的使用需求也會傳播到 consumer

由於 serializationarchiveExtrasPRIVATE 依賴項,因此它的使用需求不會傳播到 consumer

一般來說,如果依賴項僅由程式庫的實作使用,而不在標頭檔中使用,則應在使用 PRIVATE 關鍵字的 target_link_libraries() 中指定依賴項。 如果依賴項也在程式庫的標頭檔中使用(例如用於類別繼承),則應將其指定為 PUBLIC 依賴項。 僅由標頭檔使用,而程式庫的實作不使用的依賴項應指定為 INTERFACE 依賴項。 可以使用每個關鍵字的多個用法調用 target_link_libraries() 命令

target_link_libraries(archiveExtras
  PUBLIC archive
  PRIVATE serialization
)

使用需求透過從依賴項讀取目標屬性的 INTERFACE_ 變體,並將值附加到運算元的非 INTERFACE_ 變體來傳播。 例如,將讀取依賴項的 INTERFACE_INCLUDE_DIRECTORIES,並將其附加到運算元的 INCLUDE_DIRECTORIES。 如果順序相關且已維護,且 target_link_libraries() 呼叫產生的順序不允許正確編譯,則直接使用適當的命令設定屬性可能會更新順序。

例如,如果目標的連結庫必須以 lib1 lib2 lib3 的順序指定,但包含目錄必須以 lib3 lib1 lib2 的順序指定

target_link_libraries(myExe lib1 lib2 lib3)
target_include_directories(myExe
  PRIVATE $<TARGET_PROPERTY:lib3,INTERFACE_INCLUDE_DIRECTORIES>)

請注意,在為將使用 install(EXPORT) 命令匯出的安裝目標指定使用需求時,必須格外小心。 請參閱建立套件以取得更多資訊。

可遞移的編譯屬性

這些代表消費者編譯的使用需求

INTERFACE_COMPILE_DEFINITIONS

用於編譯目標消費者中原始碼的編譯定義列表。 通常,這些由目標的標頭檔使用。

INTERFACE_COMPILE_OPTIONS

用於編譯目標消費者中原始碼的編譯選項列表。

INTERFACE_COMPILE_FEATURES

在版本 3.1 中新增。

編譯目標消費者中原始碼所需的編譯功能列表。 通常,這些功能可確保在使用足夠的語言標準層級編譯消費者時處理目標的標頭檔。

INTERFACE_INCLUDE_DIRECTORIES

用於編譯目標消費者中原始碼的包含目錄列表。 通常,這些是目標標頭檔的位置。

INTERFACE_SYSTEM_INCLUDE_DIRECTORIES

當指定為包含目錄時(例如,透過 INCLUDE_DIRECTORIESINTERFACE_INCLUDE_DIRECTORIES),應在編譯目標消費者中的原始碼時視為「系統」包含目錄的目錄列表。

INTERFACE_SOURCES

要與目標消費者關聯的原始碼檔案列表。

INTERFACE_PRECOMPILE_HEADERS

在版本 3.16 中新增。

要在編譯目標消費者中的原始碼時預先編譯和包含的標頭檔列表。

INTERFACE_AUTOMOC_MACRO_NAMES

在版本 3.27 中新增。

AUTOMOC 使用的巨集名稱列表,用於判斷目標消費者中的 C++ 原始碼是否需要由 moc 處理。

INTERFACE_AUTOUIC_OPTIONS

在版本 3.0 中新增。

AUTOUIC 在為目標消費者調用 uic 時使用的選項列表。

自訂可遞移屬性

在版本 3.30 中新增。

TARGET_PROPERTY 產生器表達式會評估上述建置規格使用需求屬性作為內建的可遞移屬性。 它也支援由目標及其連結依賴項上的 TRANSITIVE_COMPILE_PROPERTIESTRANSITIVE_LINK_PROPERTIES 屬性定義的自訂可遞移屬性。

例如

add_library(example INTERFACE)
set_target_properties(example PROPERTIES
  TRANSITIVE_COMPILE_PROPERTIES "CUSTOM_C"
  TRANSITIVE_LINK_PROPERTIES    "CUSTOM_L"

  INTERFACE_CUSTOM_C "EXAMPLE_CUSTOM_C"
  INTERFACE_CUSTOM_L "EXAMPLE_CUSTOM_L"
  )

add_library(mylib STATIC mylib.c)
target_link_libraries(mylib PRIVATE example)
set_target_properties(mylib PROPERTIES
  CUSTOM_C           "MYLIB_PRIVATE_CUSTOM_C"
  CUSTOM_L           "MYLIB_PRIVATE_CUSTOM_L"
  INTERFACE_CUSTOM_C "MYLIB_IFACE_CUSTOM_C"
  INTERFACE_CUSTOM_L "MYLIB_IFACE_CUSTOM_L"
  )

add_executable(myexe myexe.c)
target_link_libraries(myexe PRIVATE mylib)
set_target_properties(myexe PROPERTIES
  CUSTOM_C "MYEXE_CUSTOM_C"
  CUSTOM_L "MYEXE_CUSTOM_L"
  )

add_custom_target(print ALL VERBATIM
  COMMAND ${CMAKE_COMMAND} -E echo
    # Prints "MYLIB_PRIVATE_CUSTOM_C;EXAMPLE_CUSTOM_C"
    "$<TARGET_PROPERTY:mylib,CUSTOM_C>"

    # Prints "MYLIB_PRIVATE_CUSTOM_L;EXAMPLE_CUSTOM_L"
    "$<TARGET_PROPERTY:mylib,CUSTOM_L>"

    # Prints "MYEXE_CUSTOM_C"
    "$<TARGET_PROPERTY:myexe,CUSTOM_C>"

    # Prints "MYEXE_CUSTOM_L;MYLIB_IFACE_CUSTOM_L;EXAMPLE_CUSTOM_L"
    "$<TARGET_PROPERTY:myexe,CUSTOM_L>"
  )

相容的介面屬性

某些目標屬性需要在目標與每個依賴項的介面之間相容。 例如,POSITION_INDEPENDENT_CODE 目標屬性可以指定一個布林值,指示目標是否應編譯為位置無關程式碼,這會產生平台特定的後果。 目標也可以指定使用需求 INTERFACE_POSITION_INDEPENDENT_CODE,以傳達消費者必須編譯為位置無關程式碼。

add_executable(exe1 exe1.cpp)
set_property(TARGET exe1 PROPERTY POSITION_INDEPENDENT_CODE ON)

add_library(lib1 SHARED lib1.cpp)
set_property(TARGET lib1 PROPERTY INTERFACE_POSITION_INDEPENDENT_CODE ON)

add_executable(exe2 exe2.cpp)
target_link_libraries(exe2 lib1)

在這裡,exe1exe2 都將編譯為位置無關程式碼。 lib1 也將編譯為位置無關程式碼,因為這是 SHARED 程式庫的預設設定。 如果依賴項具有衝突、不相容的需求,cmake(1) 會發出診斷訊息

add_library(lib1 SHARED lib1.cpp)
set_property(TARGET lib1 PROPERTY INTERFACE_POSITION_INDEPENDENT_CODE ON)

add_library(lib2 SHARED lib2.cpp)
set_property(TARGET lib2 PROPERTY INTERFACE_POSITION_INDEPENDENT_CODE OFF)

add_executable(exe1 exe1.cpp)
target_link_libraries(exe1 lib1)
set_property(TARGET exe1 PROPERTY POSITION_INDEPENDENT_CODE OFF)

add_executable(exe2 exe2.cpp)
target_link_libraries(exe2 lib1 lib2)

lib1 需求 INTERFACE_POSITION_INDEPENDENT_CODEexe1 目標的 POSITION_INDEPENDENT_CODE 屬性「不相容」。 程式庫要求消費者建置為位置無關程式碼,而可執行檔指定不建置為位置無關程式碼,因此會發出診斷訊息。

lib1lib2 需求「不相容」。 其中一個要求消費者建置為位置無關程式碼,而另一個要求消費者不建置為位置無關程式碼。 由於 exe2 連結到兩者,且它們之間存在衝突,因此會發出 CMake 錯誤訊息

CMake Error: The INTERFACE_POSITION_INDEPENDENT_CODE property of "lib2" does
not agree with the value of POSITION_INDEPENDENT_CODE already determined
for "exe2".

為了「相容」,POSITION_INDEPENDENT_CODE 屬性(如果已設定)在布林意義上,必須與設定該屬性的所有遞移指定依賴項的 INTERFACE_POSITION_INDEPENDENT_CODE 屬性相同。

可以透過在 COMPATIBLE_INTERFACE_BOOL 目標屬性的內容中指定屬性,將「相容的介面需求」的此屬性擴展到其他屬性。 每個指定的屬性在消費目標與每個依賴項中帶有 INTERFACE_ 前綴的對應屬性之間必須相容

add_library(lib1Version2 SHARED lib1_v2.cpp)
set_property(TARGET lib1Version2 PROPERTY INTERFACE_CUSTOM_PROP ON)
set_property(TARGET lib1Version2 APPEND PROPERTY
  COMPATIBLE_INTERFACE_BOOL CUSTOM_PROP
)

add_library(lib1Version3 SHARED lib1_v3.cpp)
set_property(TARGET lib1Version3 PROPERTY INTERFACE_CUSTOM_PROP OFF)

add_executable(exe1 exe1.cpp)
target_link_libraries(exe1 lib1Version2) # CUSTOM_PROP will be ON

add_executable(exe2 exe2.cpp)
target_link_libraries(exe2 lib1Version2 lib1Version3) # Diagnostic

非布林屬性也可以參與「相容的介面」計算。 在 COMPATIBLE_INTERFACE_STRING 屬性中指定的屬性在所有遞移指定的依賴項中必須未指定或比較為相同的字串。 這對於確保多個不相容版本的程式庫不會透過目標的可遞移需求連結在一起非常有用

add_library(lib1Version2 SHARED lib1_v2.cpp)
set_property(TARGET lib1Version2 PROPERTY INTERFACE_LIB_VERSION 2)
set_property(TARGET lib1Version2 APPEND PROPERTY
  COMPATIBLE_INTERFACE_STRING LIB_VERSION
)

add_library(lib1Version3 SHARED lib1_v3.cpp)
set_property(TARGET lib1Version3 PROPERTY INTERFACE_LIB_VERSION 3)

add_executable(exe1 exe1.cpp)
target_link_libraries(exe1 lib1Version2) # LIB_VERSION will be "2"

add_executable(exe2 exe2.cpp)
target_link_libraries(exe2 lib1Version2 lib1Version3) # Diagnostic

COMPATIBLE_INTERFACE_NUMBER_MAX 目標屬性指定內容將以數值方式評估,並計算所有指定項中的最大數字

add_library(lib1Version2 SHARED lib1_v2.cpp)
set_property(TARGET lib1Version2 PROPERTY INTERFACE_CONTAINER_SIZE_REQUIRED 200)
set_property(TARGET lib1Version2 APPEND PROPERTY
  COMPATIBLE_INTERFACE_NUMBER_MAX CONTAINER_SIZE_REQUIRED
)

add_library(lib1Version3 SHARED lib1_v3.cpp)
set_property(TARGET lib1Version3 PROPERTY INTERFACE_CONTAINER_SIZE_REQUIRED 1000)

add_executable(exe1 exe1.cpp)
# CONTAINER_SIZE_REQUIRED will be "200"
target_link_libraries(exe1 lib1Version2)

add_executable(exe2 exe2.cpp)
# CONTAINER_SIZE_REQUIRED will be "1000"
target_link_libraries(exe2 lib1Version2 lib1Version3)

同樣地,COMPATIBLE_INTERFACE_NUMBER_MIN 可用於計算依賴項屬性的數值最小值。

可以在產生時使用產生器表達式在消費者中讀取每個計算出的「相容」屬性值。

請注意,對於每個被依賴者,每個相容的介面屬性中指定的屬性集不得與任何其他屬性中指定的集合相交。

屬性來源偵錯

由於建置規格可以由依賴項決定,因此建立目標的程式碼與負責設定建置規格的程式碼缺乏局部性可能會使程式碼更難以推理。 cmake(1) 提供偵錯工具,以列印可能由依賴項決定的屬性內容的來源。 可以在 CMAKE_DEBUG_TARGET_PROPERTIES 變數文件中找到可以偵錯的屬性列表

set(CMAKE_DEBUG_TARGET_PROPERTIES
  INCLUDE_DIRECTORIES
  COMPILE_DEFINITIONS
  POSITION_INDEPENDENT_CODE
  CONTAINER_SIZE_REQUIRED
  LIB_VERSION
)
add_executable(exe1 exe1.cpp)

COMPATIBLE_INTERFACE_BOOLCOMPATIBLE_INTERFACE_STRING 中列出的屬性的情況下,偵錯輸出會顯示哪個目標負責設定屬性,以及哪些其他依賴項也定義了該屬性。 在 COMPATIBLE_INTERFACE_NUMBER_MAXCOMPATIBLE_INTERFACE_NUMBER_MIN 的情況下,偵錯輸出會顯示來自每個依賴項的屬性值,以及該值是否決定了新的極值。

使用產生器表達式的建置規格

建置規格可以使用產生器表達式,其中包含可能是有條件的或僅在產生時才知道的內容。 例如,可以使用 TARGET_PROPERTY 表達式讀取屬性的計算出的「相容」值

add_library(lib1Version2 SHARED lib1_v2.cpp)
set_property(TARGET lib1Version2 PROPERTY
  INTERFACE_CONTAINER_SIZE_REQUIRED 200)
set_property(TARGET lib1Version2 APPEND PROPERTY
  COMPATIBLE_INTERFACE_NUMBER_MAX CONTAINER_SIZE_REQUIRED
)

add_executable(exe1 exe1.cpp)
target_link_libraries(exe1 lib1Version2)
target_compile_definitions(exe1 PRIVATE
    CONTAINER_SIZE=$<TARGET_PROPERTY:CONTAINER_SIZE_REQUIRED>
)

在這種情況下,exe1 原始碼檔案將使用 -DCONTAINER_SIZE=200 進行編譯。

一元 TARGET_PROPERTY 產生器表達式和 TARGET_POLICY 產生器表達式會使用消費目標上下文進行評估。 這表示使用需求規格可能會根據消費者而以不同的方式評估

add_library(lib1 lib1.cpp)
target_compile_definitions(lib1 INTERFACE
  $<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,EXECUTABLE>:LIB1_WITH_EXE>
  $<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,SHARED_LIBRARY>:LIB1_WITH_SHARED_LIB>
  $<$<TARGET_POLICY:CMP0182>:CONSUMER_CMP0182_NEW>
)

add_executable(exe1 exe1.cpp)
target_link_libraries(exe1 lib1)

cmake_policy(SET CMP0182 NEW)

add_library(shared_lib shared_lib.cpp)
target_link_libraries(shared_lib lib1)

exe1 可執行檔將使用 -DLIB1_WITH_EXE 進行編譯,而 shared_lib 共享庫將使用 -DLIB1_WITH_SHARED_LIB-DCONSUMER_CMP0182_NEW 進行編譯,因為在建立 shared_lib 目標時,政策 CMP0182NEW

BUILD_INTERFACE 表達式包裝了僅在從同一建置系統中的目標使用時,或從使用 export() 命令匯出到建置目錄的目標使用時使用的需求。 INSTALL_INTERFACE 表達式包裝了僅在從已安裝並使用 install(EXPORT) 命令匯出的目標使用時使用的需求

add_library(ClimbingStats climbingstats.cpp)
target_compile_definitions(ClimbingStats INTERFACE
  $<BUILD_INTERFACE:ClimbingStats_FROM_BUILD_LOCATION>
  $<INSTALL_INTERFACE:ClimbingStats_FROM_INSTALLED_LOCATION>
)
install(TARGETS ClimbingStats EXPORT libExport ${InstallArgs})
install(EXPORT libExport NAMESPACE Upstream::
        DESTINATION lib/cmake/ClimbingStats)
export(EXPORT libExport NAMESPACE Upstream::)

add_executable(exe1 exe1.cpp)
target_link_libraries(exe1 ClimbingStats)

在這種情況下,exe1 可執行檔將使用 -DClimbingStats_FROM_BUILD_LOCATION 進行編譯。 匯出命令產生省略了 INSTALL_INTERFACEBUILD_INTERFACEIMPORTED 目標,並剝離了 *_INTERFACE 標記。 消費 ClimbingStats 套件的單獨專案將包含

find_package(ClimbingStats REQUIRED)

add_executable(Downstream main.cpp)
target_link_libraries(Downstream Upstream::ClimbingStats)

取決於 ClimbingStats 套件是從建置位置還是安裝位置使用,Downstream 目標將會使用 -DClimbingStats_FROM_BUILD_LOCATION-DClimbingStats_FROM_INSTALL_LOCATION 進行編譯。有關套件和匯出的更多資訊,請參閱 cmake-packages(7) 手冊。

包含目錄和使用需求

當包含目錄指定為使用需求,以及與產生器運算式一起使用時,需要一些特別的考量。target_include_directories() 命令接受相對和絕對包含目錄

add_library(lib1 lib1.cpp)
target_include_directories(lib1 PRIVATE
  /absolute/path
  relative/path
)

相對路徑會根據命令出現的來源目錄進行解譯。在 INTERFACE_INCLUDE_DIRECTORIESIMPORTED 目標中不允許使用相對路徑。

在使用了非平凡產生器運算式的情況下,INSTALL_PREFIX 運算式可以用於 INSTALL_INTERFACE 運算式的引數中。它是一個替換標記,當被消費專案匯入時,會展開為安裝前綴。

包含目錄使用需求通常在建置樹和安裝樹之間有所不同。BUILD_INTERFACEINSTALL_INTERFACE 產生器運算式可用於根據使用位置描述不同的使用需求。相對路徑允許在 INSTALL_INTERFACE 運算式中使用,並根據安裝前綴進行解譯。例如

add_library(ClimbingStats climbingstats.cpp)
target_include_directories(ClimbingStats INTERFACE
  $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/generated>
  $<INSTALL_INTERFACE:/absolute/path>
  $<INSTALL_INTERFACE:relative/path>
  $<INSTALL_INTERFACE:$<INSTALL_PREFIX>/$<CONFIG>/generated>
)

提供了兩個與包含目錄使用需求相關的便捷 API。CMAKE_INCLUDE_CURRENT_DIR_IN_INTERFACE 變數可以啟用,效果等同於

set_property(TARGET tgt APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES
  $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR};${CMAKE_CURRENT_BINARY_DIR}>
)

對於每個受影響的目標。已安裝目標的便利之處在於 install(TARGETS) 命令的 INCLUDES DESTINATION 组件

install(TARGETS foo bar bat EXPORT tgts ${dest_args}
  INCLUDES DESTINATION include
)
install(EXPORT tgts ${other_args})
install(FILES ${headers} DESTINATION include)

這等同於將 ${CMAKE_INSTALL_PREFIX}/include 附加到每個由 install(EXPORT) 生成的已安裝 IMPORTED 目標的 INTERFACE_INCLUDE_DIRECTORIES

當使用 匯入目標INTERFACE_INCLUDE_DIRECTORIES 時,屬性中的條目可能會被視為系統包含目錄。其效果取決於工具鏈,但一個常見的效果是忽略在這些目錄中找到的標頭檔的編譯器警告。已安裝目標的 SYSTEM 屬性決定了此行為(請參閱 EXPORT_NO_SYSTEM 屬性,以了解如何修改目標的已安裝值)。也可以通過在消費者上設定 NO_SYSTEM_FROM_IMPORTED 目標屬性,來更改消費者如何解釋已消費的匯入目標的系統行為。

如果二進制目標以遞移方式連結到 macOS FRAMEWORK,則框架的 Headers 目錄也會被視為使用需求。這具有與將框架目錄作為包含目錄傳遞相同的效果。

輸出成品

add_library()add_executable() 命令建立的建置系統目標會建立規則以產生二進制輸出。二進制輸出的確切輸出位置只能在產生時確定,因為它可能取決於建置配置和連結依賴項的連結語言等。TARGET_FILETARGET_LINKER_FILE 和相關的運算式可用於存取生成的二進制檔案的名稱和位置。但是,這些運算式不適用於 OBJECT 庫,因為此類庫不會生成與運算式相關的單個檔案。

目標可能會建置三種類型的輸出成品,詳情在以下章節中說明。它們的分類在 DLL 平台和非 DLL 平台之間有所不同。所有基於 Windows 的系統(包括 Cygwin)都是 DLL 平台。

執行時期輸出成品

建置系統目標的執行時期輸出成品可能是

  • add_executable() 命令建立的可執行目標的可執行檔(例如 .exe)。

  • 在 DLL 平台上:由 add_library() 命令使用 SHARED 選項建立的共享庫目標的可執行檔(例如 .dll)。

可以使用 RUNTIME_OUTPUT_DIRECTORYRUNTIME_OUTPUT_NAME 目標屬性來控制建置樹中執行時期輸出成品的的位置和名稱。

庫輸出成品

建置系統目標的輸出成品可能是

  • add_library() 命令使用 MODULE 選項建立的模組庫目標的可加載模組文件(例如 .dll.so)。

  • 在非 DLL 平台上:由 add_library() 命令使用 SHARED 選項建立的共享庫目標的共享庫文件(例如 .so.dylib)。

可以使用 LIBRARY_OUTPUT_DIRECTORYLIBRARY_OUTPUT_NAME 目標屬性來控制建置樹中庫輸出成品的的位置和名稱。

封存輸出成品

建置系統目標的封存輸出成品可能是

  • add_library() 命令使用 STATIC 選項建立的靜態庫目標的靜態庫文件(例如 .lib.a)。

  • 在 DLL 平台上:由 add_library() 命令使用 SHARED 選項建立的共享庫目標的匯入庫文件(例如 .lib)。僅當庫匯出至少一個非託管符號時,才能保證此檔案存在。

  • 在 DLL 平台上:當 ENABLE_EXPORTS 目標屬性設定時,由 add_executable() 命令建立的可執行目標的匯入庫文件(例如 .lib)。

  • 在 AIX 上:當 ENABLE_EXPORTS 目標屬性設定時,由 add_executable() 命令建立的可執行目標的連結器匯入文件(例如 .imp)。

  • 在 macOS 上:當 ENABLE_EXPORTS 目標屬性設定時,由 add_library() 命令使用 SHARED 選項建立的共享庫目標的連結器匯入文件(例如 .tbd)。

可以使用 ARCHIVE_OUTPUT_DIRECTORYARCHIVE_OUTPUT_NAME 目標屬性來控制建置樹中封存輸出成品的的位置和名稱。

目錄範圍命令

target_include_directories()target_compile_definitions()target_compile_options() 命令一次只對一個目標產生影響。add_compile_definitions()add_compile_options()include_directories() 命令具有相似的功能,但為了方便起見,它們在目錄範圍而不是目標範圍內運作。

建置配置

配置決定了特定類型建置的規範,例如 ReleaseDebug。指定方式取決於所使用的 產生器 類型。對於單一配置產生器,例如 Makefile 產生器Ninja,配置是在配置時通過 CMAKE_BUILD_TYPE 變數指定的。對於多配置產生器,例如 Visual StudioXcodeNinja Multi-Config,配置是由用戶在建置時選擇的,並且 CMAKE_BUILD_TYPE 會被忽略。在多配置情況下,可用配置的集合是在配置時通過 CMAKE_CONFIGURATION_TYPES 變數指定的,但是實際使用的配置在建置階段之前是未知的。這種差異經常被誤解,導致了如下的問題代碼

# WARNING: This is wrong for multi-config generators because they don't use
#          and typically don't even set CMAKE_BUILD_TYPE
string(TOLOWER ${CMAKE_BUILD_TYPE} build_type)
if (build_type STREQUAL debug)
  target_compile_definitions(exe1 PRIVATE DEBUG_BUILD)
endif()

產生器運算式 應該被用來正確地處理特定於配置的邏輯,而不管使用的是哪個產生器。例如

# Works correctly for both single and multi-config generators
target_compile_definitions(exe1 PRIVATE
  $<$<CONFIG:Debug>:DEBUG_BUILD>
)

在存在 IMPORTED 目標的情況下,MAP_IMPORTED_CONFIG_DEBUG 的內容也會被上面的 $<CONFIG:Debug> 運算式考慮在內。

大小寫敏感性

CMAKE_BUILD_TYPECMAKE_CONFIGURATION_TYPES 與其他變數一樣,對其值進行的任何字串比較都將區分大小寫。$<CONFIG> 產生器運算式也保留了用戶或 CMake 預設設定的配置的大小寫。例如

# NOTE: Don't use these patterns, they are for illustration purposes only.

set(CMAKE_BUILD_TYPE Debug)
if(CMAKE_BUILD_TYPE STREQUAL DEBUG)
  # ... will never get here, "Debug" != "DEBUG"
endif()
add_custom_target(print_config ALL
  # Prints "Config is Debug" in this single-config case
  COMMAND ${CMAKE_COMMAND} -E echo "Config is $<CONFIG>"
  VERBATIM
)

set(CMAKE_CONFIGURATION_TYPES Debug Release)
if(DEBUG IN_LIST CMAKE_CONFIGURATION_TYPES)
  # ... will never get here, "Debug" != "DEBUG"
endif()

相反地,當 CMake 在內部使用配置類型來修改基於配置的行為時,CMake 會以不區分大小寫的方式處理配置類型。例如,$<CONFIG:Debug> 產生器運算式對於不僅是 Debug,而且還有 DEBUGdebug 甚至 DeBuG 的配置都將評估為 1。因此,您可以在 CMAKE_BUILD_TYPECMAKE_CONFIGURATION_TYPES 中使用任何大小寫混合來指定配置類型,儘管存在強烈的慣例(請參閱下一節)。如果您必須在字串比較中測試值,請始終先將值轉換為大寫或小寫,然後相應地調整測試。

預設和自訂配置

預設情況下,CMake 定義了許多標準配置

  • Debug

  • Release

  • RelWithDebInfo

  • MinSizeRel

在多配置產生器中,除非專案或用戶覆蓋,否則 CMAKE_CONFIGURATION_TYPES 變數預設將填充上述列表(可能是子集)。實際使用的配置由用戶在建置時選擇。

對於單配置產生器,配置是在配置時使用 CMAKE_BUILD_TYPE 變數指定的,並且無法在建置時更改。預設值通常不是上述任何標準配置,而是空字串。一個常見的誤解是這與 Debug 相同,但事實並非如此。用戶應始終明確指定建置類型,以避免這個常見問題。

上述標準配置類型在大多數平台上提供了合理的行為,但它們可以擴展以提供其他類型。每個配置都為使用的語言定義了一組編譯器和連結器標誌變數。這些變數遵循 CMAKE_<LANG>_FLAGS_<CONFIG> 慣例,其中 <CONFIG> 始終是大寫配置名稱。當定義自訂配置類型時,請確保正確設定這些變數,通常作為快取變數。

虛擬目標

某些目標類型不代表建置系統的輸出,而僅代表輸入,例如外部依賴項、別名或其他非建置成品。虛擬目標不表示在生成的建置系統中。

匯入目標

IMPORTED 目標表示預先存在的依賴項。通常,此類目標由上游套件定義,應視為不可變的。在宣告 IMPORTED 目標後,可以像任何其他常規目標一樣,使用常用的命令(例如 target_compile_definitions()target_include_directories()target_compile_options()target_link_libraries())來調整其目標屬性。

IMPORTED 目標可能具有與二進制目標相同的已填充使用需求屬性,例如 INTERFACE_INCLUDE_DIRECTORIESINTERFACE_COMPILE_DEFINITIONSINTERFACE_COMPILE_OPTIONSINTERFACE_LINK_LIBRARIESINTERFACE_POSITION_INDEPENDENT_CODE

LOCATION 也可從 IMPORTED 目標讀取,但很少有理由這樣做。像是 add_custom_command() 等命令可以透明地使用 IMPORTED EXECUTABLE 目標作為 COMMAND 可執行檔。

定義 IMPORTED 目標的範圍是定義它的目錄。它可以從子目錄存取和使用,但不能從父目錄或同層目錄存取和使用。範圍類似於 cmake 變數的範圍。

也可以定義一個 GLOBAL IMPORTED 目標,該目標在建置系統中是全域可存取的。

請參閱 cmake-packages(7) 手冊,以了解更多關於使用 IMPORTED 目標建立套件的資訊。

別名目標

ALIAS 目標是一個名稱,可以在唯讀環境中與二進制目標名稱互換使用。ALIAS 目標的主要用例是例如或單元測試可執行檔伴隨程式庫,這些可執行檔可能是同一個建置系統的一部分,或是根據使用者組態單獨建置的。

add_library(lib1 lib1.cpp)
install(TARGETS lib1 EXPORT lib1Export ${dest_args})
install(EXPORT lib1Export NAMESPACE Upstream:: ${other_args})

add_library(Upstream::lib1 ALIAS lib1)

在另一個目錄中,我們可以無條件地連結到 Upstream::lib1 目標,該目標可能是來自套件的 IMPORTED 目標,或者如果作為同一個建置系統的一部分建置,則為 ALIAS 目標。

if (NOT TARGET Upstream::lib1)
  find_package(lib1 REQUIRED)
endif()
add_executable(exe1 exe1.cpp)
target_link_libraries(exe1 Upstream::lib1)

ALIAS 目標是不可變的、不可安裝的或不可匯出的。它們完全是建置系統描述的本地目標。可以通過從中讀取 ALIASED_TARGET 屬性來測試名稱是否為 ALIAS 名稱

get_target_property(_aliased Upstream::lib1 ALIASED_TARGET)
if(_aliased)
  message(STATUS "The name Upstream::lib1 is an ALIAS for ${_aliased}.")
endif()

介面程式庫

INTERFACE 程式庫目標不編譯來源程式碼,也不會在磁碟上產生程式庫成品,因此它沒有 LOCATION

它可以指定使用需求,例如 INTERFACE_INCLUDE_DIRECTORIESINTERFACE_COMPILE_DEFINITIONSINTERFACE_COMPILE_OPTIONSINTERFACE_LINK_LIBRARIESINTERFACE_SOURCESINTERFACE_POSITION_INDEPENDENT_CODE。只有 target_include_directories()target_compile_definitions()target_compile_options()target_sources()target_link_libraries() 命令的 INTERFACE 模式可以與 INTERFACE 程式庫一起使用。

自 CMake 3.19 起,INTERFACE 程式庫目標可以選擇性地包含來源檔案。包含來源檔案的介面程式庫將作為建置目標包含在產生的建置系統中。它不編譯來源程式碼,但可能包含自訂命令以產生其他來源程式碼。此外,IDE 將顯示來源檔案作為目標的一部分,以進行互動式閱讀和編輯。

INTERFACE 程式庫的主要用例是僅標頭檔程式庫。自 CMake 3.23 起,標頭檔可以通過使用 target_sources() 命令將它們添加到標頭集合中,從而與程式庫關聯

add_library(Eigen INTERFACE)

target_sources(Eigen PUBLIC
  FILE_SET HEADERS
    BASE_DIRS src
    FILES src/eigen.h src/vector.h src/matrix.h
)

add_executable(exe1 exe1.cpp)
target_link_libraries(exe1 Eigen)

當我們在這裡指定 FILE_SET 時,我們定義的 BASE_DIRS 會自動成為目標 Eigen 的使用需求中的 include 目錄。來自目標的使用需求在編譯時會被消耗和使用,但對連結沒有影響。

另一個用例是對於使用需求採用完全以目標為中心的設計

add_library(pic_on INTERFACE)
set_property(TARGET pic_on PROPERTY INTERFACE_POSITION_INDEPENDENT_CODE ON)
add_library(pic_off INTERFACE)
set_property(TARGET pic_off PROPERTY INTERFACE_POSITION_INDEPENDENT_CODE OFF)

add_library(enable_rtti INTERFACE)
target_compile_options(enable_rtti INTERFACE
  $<$<OR:$<COMPILER_ID:GNU>,$<COMPILER_ID:Clang>>:-rtti>
)

add_executable(exe1 exe1.cpp)
target_link_libraries(exe1 pic_on enable_rtti)

這樣一來,exe1 的建置規範完全表示為連結的目標,而編譯器特定旗標的複雜性被封裝在 INTERFACE 程式庫目標中。

INTERFACE 程式庫可以安裝和匯出。我們可以將預設標頭集合與目標一起安裝

add_library(Eigen INTERFACE)

target_sources(Eigen PUBLIC
  FILE_SET HEADERS
    BASE_DIRS src
    FILES src/eigen.h src/vector.h src/matrix.h
)

install(TARGETS Eigen EXPORT eigenExport
  FILE_SET HEADERS DESTINATION include/Eigen)
install(EXPORT eigenExport NAMESPACE Upstream::
  DESTINATION lib/cmake/Eigen
)

在這裡,標頭集合中定義的標頭會安裝到 include/Eigen。安裝目的地會自動成為 include 目錄,這是消費者的使用需求。