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 靜態函式庫會被連結進來。

二進制執行檔

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

add_executable(mytool mytool.cpp)

像是 add_custom_command() 等在建置時產生要執行的規則的命令,可以透明地使用一個 EXECUTABLE 目標作為 COMMAND 可執行檔。建置系統規則將確保可執行檔在嘗試執行命令之前先被建置。

二進制函式庫類型

一般函式庫

預設情況下,add_library() 命令定義一個 STATIC 函式庫,除非指定了類型。使用命令時可以指定類型。

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

可以啟用 BUILD_SHARED_LIBS 變數,以變更 add_library() 的行為,使其預設建置共享函式庫。

在整個建置系統定義的背景下,無論特定的函式庫是 SHAREDSTATIC,在很大程度上是不相關的 -- 命令、依賴關係規範和其他 API 的運作方式與函式庫類型無關。MODULE 函式庫類型不同之處在於它通常不被連結 -- 它不會在 target_link_libraries() 命令的右側使用。它是一種使用執行時技術作為外掛程式載入的類型。如果函式庫沒有匯出任何未受管理的符號(例如,Windows 資源 DLL、C++/CLI DLL),則該函式庫必須不是 SHARED 函式庫,因為 CMake 預期 SHARED 函式庫至少匯出一個符號。

add_library(archive MODULE 7z.cpp)
Apple 框架

SHARED 函式庫可以使用 FRAMEWORK 目標屬性標記,以建立 macOS 或 iOS 框架套件。具有 FRAMEWORK 目標屬性的函式庫也應設定 FRAMEWORK_VERSION 目標屬性。根據 macOS 慣例,此屬性通常設定為值 "A"。 MACOSX_FRAMEWORK_IDENTIFIER 設定 CFBundleIdentifier 鍵,它會唯一地識別套件。

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
)

物件函式庫

OBJECT 函式庫類型定義了從給定原始檔案編譯而來的物件檔案的非封存集合。可以使用 $<TARGET_OBJECTS:name> 語法,將物件檔案集合用作其他目標的來源輸入。這是一個 產生器運算式,可用於將 OBJECT 函式庫內容提供給其他目標。

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

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

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

這些其他目標的連結(或封存)步驟將使用物件檔案集合,以及來自其自身來源的物件檔案。

或者,物件函式庫可以連結到其他目標中。

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

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

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

這些其他目標的連結(或封存)步驟將使用來自直接連結的 OBJECT 函式庫的物件檔案。此外,在編譯這些其他目標中的來源時,將會遵守 OBJECT 函式庫的使用需求。此外,這些使用需求將遞迴地傳播到這些其他目標的依賴項。

物件函式庫不能用作 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 使用需求屬性。

它還支援指定 檔案集,這可以新增未列在 SOURCESINTERFACE_SOURCES 屬性中的 C++ 模組原始碼和標頭。檔案集也可以使用包含標頭的 include 目錄來填充 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 版本。

目標呼叫 uic 時,AUTOUIC 使用的選項列表。

目標使用需求

目標的使用需求是指傳播到取用者的設定,這些取用者會透過 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

一般來說,如果依賴項僅在函式庫的實作中使用,而不在標頭檔中使用,則應使用 target_link_libraries() 命令,並搭配 PRIVATE 關鍵字來指定。如果依賴項也用於函式庫的標頭檔中(例如,用於類別繼承),則應將其指定為 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 版本中新增。

例如

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>"
  )

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)

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)

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

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

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

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)

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

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>
)

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:CMP0041>:CONSUMER_CMP0041_NEW>
)

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

cmake_policy(SET CMP0041 NEW)

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

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 進行編譯。導出命令會產生 IMPORTED 目標,並省略 INSTALL_INTERFACEBUILD_INTERFACE,且移除 *_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_INTERFACE 表達式的參數中使用 INSTALL_PREFIX 表達式。它是一個替換標記,當被使用專案導入時,它會擴展為安裝前綴。

包含目錄的使用需求通常在建置樹和安裝樹之間有所不同。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 平台之間有所不同。包括 Cygwin 在內的所有基於 Windows 的系統都是 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 目標,則上述 $<CONFIG:Debug> 表達式也會將 MAP_IMPORTED_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 在內部根據組態修改行為時,會以不區分大小寫的方式處理組態類型。例如,$<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

也可以從 IMPORTED 目標讀取 LOCATION,儘管很少有這樣做的理由。諸如 add_custom_command() 之類的命令可以透明地使用 IMPORTED EXECUTABLE 目標作為 COMMAND 可執行檔。

IMPORTED 目標的定義範圍是其定義所在的目錄。它可以在子目錄中存取和使用,但不能從父目錄或同級目錄存取和使用。其範圍與 cmake 變數的範圍相似。

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

請參閱 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 INTERFACE
  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 目錄。