cmake-buildsystem(7)¶
簡介¶
基於 CMake 的建置系統組織成一組高階邏輯目標。每個目標對應到一個可執行檔或函式庫,或是包含自訂命令的自訂目標。目標之間的依賴關係在建置系統中表達,以決定建置順序以及因應變更而重新產生的規則。
二進制目標¶
可執行檔和函式庫使用 add_executable()
和 add_library()
命令定義。產生的二進制檔案對於目標平台具有適當的 PREFIX
、SUFFIX
和副檔名。二進制目標之間的依賴關係使用 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.cpp
、zip.cpp
和 lzma.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()
的行為,使其預設建置共享函式庫。
在整個建置系統定義的背景下,無論特定的函式庫是 SHARED
或 STATIC
,在很大程度上是不相關的 -- 命令、依賴關係規範和其他 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)
目標命令¶
目標特定的命令會填入二進制目標的建置規範,以及二進制目標、介面函式庫和匯入目標的使用需求。
呼叫必須指定範圍關鍵字,每個關鍵字都會影響它後面參數的可見性。範圍如下:
命令如下:
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
使用需求屬性。它還支援指定 檔案集,這可以新增未列在
SOURCES
和INTERFACE_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
目標屬性也用作編譯定義,作為SHARED
和MODULE
程式庫目標的特殊便利情況。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
使用的選項列表。
目標連結屬性¶
這些屬性代表目標連結的建置規格。
LINK_LIBRARIES
連結目標的連結程式庫列表 (如果目標是可執行檔、共用程式庫或模組程式庫)。一般程式庫的項目會透過其連結成品的路徑,或使用
-l
旗標或等效方式傳遞給連結器。物件程式庫的項目會透過其物件檔案的路徑傳遞給連結器。此外,對於編譯和連結目標本身,會從
LINK_LIBRARIES
項目 (指定一般程式庫、介面程式庫、物件程式庫和 匯入目標) 傳播使用需求,這些需求會在它們的INTERFACE_LINK_LIBRARIES
屬性的遞移閉包中收集。LINK_DIRECTORIES
在版本 3.13 中新增。
連結目標的連結目錄列表 (如果目標是可執行檔、共用程式庫或模組程式庫)。這些目錄會以
-L
旗標或等效方式傳遞給連結器。LINK_OPTIONS
在版本 3.13 中新增。
連結目標的連結選項列表 (如果目標是可執行檔、共用程式庫或模組程式庫)。這些選項會以旗標形式依出現順序傳遞給連結器。
連結選項會自動針對 Shell 進行跳脫處理。
LINK_DEPENDS
目標連結所依賴的檔案列表 (如果目標是可執行檔、共用程式庫或模組程式庫)。例如,透過
LINK_OPTIONS
指定的連結器指令檔可能會在此處列出,以便變更它們會導致二進位檔重新連結。
目標使用需求¶
目標的使用需求是指傳播到取用者的設定,這些取用者會透過 target_link_libraries()
連結到目標,以便正確地編譯和連結。它們由遞移編譯和連結屬性表示。
請注意,使用需求並非設計為讓下游僅為了方便而使用特定的 COMPILE_OPTIONS
、COMPILE_DEFINITIONS
等。屬性的內容必須是需求,而不僅僅是建議。
請參閱 cmake-packages(7)
手冊的 建立可重新定位的套件 章節,以了解在為重新散佈建立套件時,指定使用需求時必須額外注意的事項。
目標的使用需求可以遞移地傳播到相依項。target_link_libraries()
命令具有 PRIVATE
、INTERFACE
和 PUBLIC
關鍵字來控制傳播。
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)
由於 archive
是 archiveExtras
的 PUBLIC
相依項,因此它的使用需求也會傳播到 consumer
。
由於 serialization
是 archiveExtras
的 PRIVATE
相依項,因此它的使用需求不會傳播到 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_DIRECTORIES
或INTERFACE_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
時使用的選項列表。
遞移連結屬性¶
這些代表連結用戶端的使用需求。
INTERFACE_LINK_LIBRARIES
用於連結目標用戶端的連結庫列表,適用於可執行檔、共享庫或模組庫。這些是目標的遞移依賴項。
此外,對於編譯和連結目標的用戶端,會從命名普通函式庫、介面函式庫、物件函式庫和匯入目標的
INTERFACE_LINK_LIBRARIES
條目的遞移閉包中收集使用需求。INTERFACE_LINK_DIRECTORIES
在版本 3.13 中新增。
用於連結目標用戶端的連結目錄列表,適用於可執行檔、共享庫或模組庫。
INTERFACE_LINK_OPTIONS
在版本 3.13 中新增。
用於連結目標用戶端的連結選項列表,適用於可執行檔、共享庫或模組庫。
INTERFACE_LINK_DEPENDS
在版本 3.13 中新增。
連結目標用戶端所依賴的檔案列表,適用於可執行檔、共享庫或模組庫。
自訂遞移屬性¶
在 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_INTERFACE
或 BUILD_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_DIRECTORIES
的 IMPORTED
目標中使用。
在使用了非平凡生成器表達式的情況下,可以在 INSTALL_INTERFACE
表達式的參數中使用 INSTALL_PREFIX
表達式。它是一個替換標記,當被使用專案導入時,它會擴展為安裝前綴。
包含目錄的使用需求通常在建置樹和安裝樹之間有所不同。BUILD_INTERFACE
和 INSTALL_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(lib1 lib1.cpp)
add_library(lib2 lib2.cpp)
target_link_libraries(lib1 PUBLIC
$<$<TARGET_PROPERTY:POSITION_INDEPENDENT_CODE>:lib2>
)
add_library(lib3 lib3.cpp)
set_property(TARGET lib3 PROPERTY INTERFACE_POSITION_INDEPENDENT_CODE ON)
add_executable(exe1 exe1.cpp)
target_link_libraries(exe1 lib1 lib3)
由於 exe1
目標的 POSITION_INDEPENDENT_CODE
屬性的值取決於連結的庫(lib3
),並且連結 exe1
的邊緣由相同的 POSITION_INDEPENDENT_CODE
屬性決定,因此上面的依賴圖包含一個循環。cmake(1)
會發出錯誤訊息。
輸出成品¶
由 add_library()
和 add_executable()
命令建立的建置系統目標會建立用於建立二進制輸出的規則。二進制文件的確切輸出位置只能在產生時確定,因為它可能取決於建置配置和連結依賴項的連結語言等。 TARGET_FILE
、TARGET_LINKER_FILE
和相關表達式可用於存取產生的二進制文件的名稱和位置。但是,這些表達式不適用於 OBJECT
庫,因為此類庫沒有產生與表達式相關的單個檔案。
目標可能會建置三種輸出成品,如下面章節中詳述。它們的分類在 DLL 平台和非 DLL 平台之間有所不同。包括 Cygwin 在內的所有基於 Windows 的系統都是 DLL 平台。
執行時期輸出成品¶
建置系統目標的*執行時期*輸出成品可以是
由
add_executable()
命令建立的可執行檔目標的可執行檔(例如.exe
)。在 DLL 平台上:由
add_library()
命令搭配SHARED
選項建立的共享函式庫目標的可執行檔(例如.dll
)。
可以使用 RUNTIME_OUTPUT_DIRECTORY
和 RUNTIME_OUTPUT_NAME
目標屬性來控制建置樹中執行時期輸出成品的位置和名稱。
函式庫輸出成品¶
建置系統目標的函式庫輸出成品可能是
由
add_library()
命令搭配MODULE
選項建立的模組函式庫目標的可載入模組檔案(例如.dll
或.so
)。在非 DLL 平台上:由
add_library()
命令搭配SHARED
選項建立的共享函式庫目標的共享函式庫檔案(例如.so
或.dylib
)。
可以使用 LIBRARY_OUTPUT_DIRECTORY
和 LIBRARY_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_DIRECTORY
和 ARCHIVE_OUTPUT_NAME
目標屬性來控制建置樹中封存輸出成品的位置和名稱。
目錄範圍命令¶
target_include_directories()
、target_compile_definitions()
和 target_compile_options()
命令一次僅對一個目標產生影響。 add_compile_definitions()
、add_compile_options()
和 include_directories()
命令具有相似的功能,但為了方便起見,它們的作用範圍是目錄範圍,而不是目標範圍。
建置組態¶
組態決定特定類型建置的規格,例如 Release
或 Debug
。指定方式取決於所使用的 產生器
類型。對於像 Makefile 產生器 和 Ninja
這樣的單一組態產生器,組態在設定時由 CMAKE_BUILD_TYPE
變數指定。對於像 Visual Studio、Xcode
和 Ninja 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_TYPE
和 CMAKE_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
,也適用於 DEBUG
、debug
甚至是 DeBuG
時,其值會評估為 1。因此,您可以在 CMAKE_BUILD_TYPE
和 CMAKE_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_DIRECTORIES
、INTERFACE_COMPILE_DEFINITIONS
、INTERFACE_COMPILE_OPTIONS
、INTERFACE_LINK_LIBRARIES
和 INTERFACE_POSITION_INDEPENDENT_CODE
。
也可以從 IMPORTED 目標讀取 LOCATION
,儘管很少有這樣做的理由。諸如 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_DIRECTORIES
、INTERFACE_COMPILE_DEFINITIONS
、INTERFACE_COMPILE_OPTIONS
、INTERFACE_LINK_LIBRARIES
、INTERFACE_SOURCES
和 INTERFACE_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 目錄。