cmake-建置系統(7)

介紹

基於CMake的構建系統被組織為一組高級邏輯目標。每個目標對應一個可執行文件或庫,或是一個包含自定義命令的自定義目標。目標之間的依賴性在構建系統中表達,以確定構建順序和響應變化的重生規則。

二進位目標

可執行文件和庫是通過:command:add_executable`和:command:`add_library`命令定義的。生成的二進制文件具有適合目標平台的:prop_tgt:`PREFIX,:prop_tgt:`SUFFIX`和擴展名。二進制目標之間的依賴性通過:command:`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生成構建規則,以將源文件編譯成對象文件並鏈接為一個可執行文件。

可執行文件的鏈接依賴可以使用:command:`target_link_libraries`命令指定。鏈接器從可執行文件自己的源文件編譯的對象文件開始,然後通過搜索鏈接庫來解決剩餘的符號依賴。

add_custom_command() 這樣的命令會生成在構建時運行的規則,可以透明地使用 EXECUTABLE 目標作為 COMMAND 可執行文件。構建系統規則將確保在嘗試運行命令之前構建可執行文件。

靜態程式庫

靜態庫是對象文件的檔案。它們由歸檔器而不是鏈接器生成。 可執行文件`共享庫`_和 `模塊庫`_可以將靜態庫作為依賴項鏈接。鏈接器會根據需要從靜態庫中選擇對象文件的子集以解析符號並將它們鏈接到消費者二進制文件中。每個鏈接到靜態庫的二進制文件都會獲得其符號的副本,在運行時不需要靜態庫本身。

當以 STATIC 庫類型調用時,add_library() 命令定義了一個靜態庫目標:

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

或者,當 BUILD_SHARED_LIBS 變量為false時,無任何類型:

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

CMake生成構建規則以將源文件編譯為對象文件並將其存檔到一個靜態庫中。

靜態庫的鏈接依賴可以使用:command:target_link_libraries`命令指定。由於靜態庫是檔案而不是鏈接的二進制文件,其鏈接依賴的對象文件不包含在它們自己中(除了指定為*直接*鏈接依賴的 `對象庫)。相反,CMake記錄靜態庫的鏈接依賴,以供連接消費者二進制時的傳遞使用。

共享程式庫

共享庫是由鏈接對象文件創建的二進制文件。 可執行文件,其他共享庫和 `模塊庫`_可以將共享庫作為依賴項鏈接。鏈接器在消費者二進制文件中記錄共享庫的引用。在運行時,動態加載器搜索磁盤上的引用共享庫並加載其符號。

當以 SHARED 庫類型調用時,add_library() 命令定義了一個共享庫目標:

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

或者,當 BUILD_SHARED_LIBS 變量為true時,無任何類型:

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

CMake生成構建規則以將源文件編譯成對象文件並將其鏈接成共享庫。

共享庫的鏈接依賴可以使用:command:`target_link_libraries`命令指定。鏈接器從共享庫自身源文件編譯的對象文件開始,然後通過搜索鏈接庫來解決剩餘的符號依賴。

備註

CMake期望共享库至少导出一个符号。如果库没有导出任何未管理符号,如Windows资源DLL或C++/CLI DLL,应创建为`模块库 <模块库>`_。

Apple 框架

共享庫`_和 `靜態庫`_可以使用 :prop_tgt:`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
)

模組程式庫

模塊庫是通過鏈接對象文件創建的二進制文件。 與 共享庫`_不同,模塊庫可能不會被其他二進制文件作為依賴項鏈接——不要在:command:`target_link_libraries`命令的右側命名它們。相反,模塊庫是應用程序可以在運行時按需動態加載的插件,例如,通過``dlopen`

當以 MODULE 庫類型調用時,add_library() 命令定義了一個模塊庫目標:

add_library(archivePlugin MODULE 7z.cpp)

CMake生成構建規則來將源文件編譯成對象文件並將其鏈接成模塊庫。

模塊庫的鏈接依賴可以使用:command:`target_link_libraries`命令指定。鏈接器從模塊庫自己的源文件編譯的對象文件開始,然後通過搜索鏈接庫解決剩餘的符號依賴。

物件程式庫

對象庫是源文件編譯而成的對象文件集合,不做任何歸檔或鏈接。這些對象文件可用於鏈接 可執行文件共享庫模塊庫,或在歸檔 靜態庫 時使用。

當以 OBJECT 庫類型調用時,add_library() 命令定義了一個對象庫目標:

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

CMake生成構建規則以將源文件編譯為對象文件。

其他目標可以使用 generator expression 語法 $<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)

消费目标通过其自身源文件和由:command:target_link_libraries`指定作为*直接*链接依赖的对象库中的对象文件进行链接(或存档)。 见:ref:`链接对象库

對象庫不能作為 add_custom_command(TARGET) 命令簽名中 TARGET 使用。但是,可以使用 $<TARGET_OBJECTS:objlib> 列表通過 add_custom_command(OUTPUT)file(GENERATE) 使用對象列表。

建置規範與使用要求

目標根據其自己 構建規範 與從其鏈接依賴傳播的 使用要求 結合而建。 兩者都可以使用目標特定的 命令 指定。

例如:

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)

目標命令

目标特定的命令填充了`二进制目标 <Binary Targets>`_的`构建规范 <构建规范>`_以及`二进制目标 <Binary Targets>`_、接口库 <接口库_>`_和`已引入目标 <已引入目标_>`_的`使用要求

調用必須指定作用域關鍵字,每個關鍵字影響後續參數的可見性。 作用域為:

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_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 使用需求屬性。

目標建置規範

`二进制目标 <Binary Targets>`_的构建规范通过目标属性进行表示。对于以下`编译 <项目编译属性>`_和`链接 <链接属性>`_属性,目标的编译和链接受其自身值以及从链接依赖的传递闭包收集到的以``INTERFACE_``为前缀的`使用需要 <项目使用需要>`_属性的影响。

目標編譯屬性

這些表示用於編譯目標的 構建規範

COMPILE_DEFINITIONS

用於編譯目標中的源的編譯定義列表。這些會以``-D``標志或同等方式傳遞給編譯器,順序不定。

在編譯``SHARED``庫、MODULE``庫或開啟`:prop_tgt:`ENABLE_EXPORTS`的可執行文件的來源時,CMake會自動定義一個目標特定的預處理器符號。默認情況下,該定義的形式為``<target>_EXPORTS,但可以通過`:prop_tgt:`DEFINE_SYMBOL`目標屬性覆蓋掉。這允許頭文件檢測它們是否從內部實施來源包含,並正確設置導出/導入註釋或符號的可見性。

COMPILE_OPTIONS

用於編譯目標中的源的編譯選項列表。這些以標志形式傳遞給編譯器,按出現順序排列。

編譯選項會自動對shell轉義。

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

COMPILE_FEATURES

在 3.1 版被加入.

用於編譯目標源所需的 編譯功能 列表。通常,這些確保目標的源以足夠的語言標準級別編譯。

INCLUDE_DIRECTORIES

用於編譯目標源的包含目錄列表。這些由``-I``或``-isystem``旗標或同等旗標,按出現順序傳遞給編譯器。

為方便起見,可以啟用 CMAKE_INCLUDE_CURRENT_DIR 變量以將源目錄和相應的構建目錄添加為所有目標上的 INCLUDE_DIRECTORIES

SOURCES

与目标关联的源文件列表。这包括目标创建时通过:command:add_executable、:command:add_library`或:command:`add_custom_target`指定的源,也包括由:command:`target_sources`命令添加的源,但不包括:ref:`文件集

PRECOMPILE_HEADERS

在 3.16 版被加入.

預編譯並包含在目標中編譯源文件時的頭文件列表。

AUTOMOC_MACRO_NAMES

在 3.10 版被加入.

由:prop_tgt:`AUTOMOC`使用的宏名稱列表,以確定目標中的C++源是否需要經過``moc``處理。

AUTOUIC_OPTIONS

在 3.0 版被加入.

在調用``uic``時,:prop_tgt:`AUTOUIC`使用的選項列表。

目標使用要求

目标的*使用要求*是通过:command:`target_link_libraries`链接到目标以确保正确编译和链接而传递给消费者的设置。它们由传递的`编译 <传递编译属性>`_和`链接 <传递链接属性>`_属性表示。

請注意,使用要求並不是為了僅出於便利而讓下游使用特定的:prop_tgt:COMPILE_OPTIONS、:prop_tgt:`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``。

通常,應該在使用:command:`target_link_libraries`時指定``PRIVATE``關鍵詞,如果某依賴項僅由庫的實現使用,而不是在頭文件中。如果某依賴項還在庫的頭文件中使用(例如用於類繼承),則應將其指定為``PUBLIC``依賴。僅由頭文件使用而不由庫實現使用的依賴應指定為``INTERFACE``依賴。 :command:`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>)

請注意,指定用於使用:command:install(EXPORT)`命令安裝導出的目標的使用要求時,必須謹慎。請參閱 :ref:`創建包 以獲取更多信息。

傳遞編譯屬性

这些代表用于编译消费者的`使用要求 <目标使用要求>`_。

INTERFACE_COMPILE_DEFINITIONS

目標消費者中用於編譯源文件的編譯定義列表。通常這些是由目標的頭文件使用。

INTERFACE_COMPILE_OPTIONS

目標消費者中用於編譯源文件的編譯選項列表。

INTERFACE_COMPILE_FEATURES

在 3.1 版被加入.

目標消費者中用於編譯源文件所需的:manual:`編譯特性 <cmake-compile-features(7)>`列表。 通常這些可確保在編譯消費者時使用足夠的語言標準水平處理目標的頭文件。

INTERFACE_INCLUDE_DIRECTORIES

目標消費者中用於編譯源文件的包含目錄列表。 通常這些是目標的頭文件的位置。

INTERFACE_SYSTEM_INCLUDE_DIRECTORIES

当指定为包含目录时,例如通过:prop_tgt:`INCLUDE_DIRECTORIES`或:prop_tgt:`INTERFACE_INCLUDE_DIRECTORIES`进行指定,在编译目标的消费者的源文件时应将以下目录视为“系统”包含目录的列表。

INTERFACE_SOURCES

要與目標消費者關聯的源文件列表。

INTERFACE_PRECOMPILE_HEADERS

在 3.16 版被加入.

目標消費者中用於編譯源文件時要預編譯和包含的頭文件列表。

INTERFACE_AUTOMOC_MACRO_NAMES

在 3.27 版被加入.

由:prop_tgt:`AUTOMOC`使用的宏名稱列表,以確定C++源文件在目標的消費者中是否需要經過``moc``處理。

INTERFACE_AUTOUIC_OPTIONS

在 3.0 版被加入.

在調用``uic``時,:prop_tgt:`AUTOUIC`使用的選項列表。

自定義傳遞屬性

在 3.30 版被加入.

`:genex:`TARGET_PROPERTY`生成器表达式用于将上述`构建规格 <目标构建规格>`_及`使用要求 <目标使用要求>`_属性视为内建的传递属性进行评估。它还支持在目标及其链接依赖项上定义的:prop_tgt:`TRANSITIVE_COMPILE_PROPERTIES`和:prop_tgt:`TRANSITIVE_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`目標屬性可能指定一個布爾值來表明一個目標是否應該以位置無關代碼編譯,這具有特定於平臺的影響。目標還可以指定使用要求 :prop_tgt:`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)

在這裡,``exe1``和``exe2``都將編譯為位置無關代碼。由於這是對於``SHARED``庫的默認設置,``lib1``也將編譯為位置無關代碼。若依賴有衝突且不兼容的要求,:manual:`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_CODE``與``exe1``目標的:prop_tgt:`POSITION_INDEPENDENT_CODE`屬性不兼容。該庫要求消費者以位置無關代碼形式構建,而可執行文件則指定不構建為位置無關代碼,因此發出診斷。

``lib1``和``lib2``的要求不兼容。某一项要求消费者以位置无关代码形式构建,而另一项要求消费者不构建为位置无关代码。由于``exe2``同时链接到且彼此存在冲突,CMake发出错误消息::

CMake 错误:"lib2"的INTERFACE_POSITION_INDEPENDENT_CODE属性不符合"exe2"已确定的 POSITION_INDEPENDENT_CODE值。

為了“兼容”,POSITION_INDEPENDENT_CODE`屬性,如果設置,必須與所有設置了該屬性的傳遞指定依賴項中的 :prop_tgt:`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

非布爾屬性還可以參與“兼容界面”計算。屬性必須在 :prop_tgt:`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 来计算依赖关系属性的数值最小值。

每个计算得出的 "compatible" 属性值可在生成时使用生成器表达式在消费端读取。

注意,对于每个依赖项,在每个兼容接口属性中指定的属性集不应与任何其他属性中指定的集相交。

属性起源调试

由于构建规范可以通过依赖关系确定,缺乏生成目标的本地性和负责设置构建规范的代码,可能会使代码更难推理。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,调试输出显示来自每个依赖关系的属性值,以及该值是否决定了新的极限。

使用生成器表達式的建置規範

构建规范可以使用 生成器表达式,其中的内容可能根据条件或仅在生成时是已知的。例如,属性的计算 "compatible" 值可使用 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 编译,因为策略 CMP0182 在创建 shared_lib 目标时是 NEW

``BUILD_INTERFACE``表达式包裹的需求仅在从同一构建系统中的目标消费或从导出到构建目录的目标消费时使用;``INSTALL_INTERFACE``表达式包裹的需求则是仅在已安装并使用命令:command:`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
  相對路徑
)

相对路径按照命令出现的源目录进行解释。在 IMPORTED 目标的 INTERFACE_INCLUDE_DIRECTORIES 中不允许使用相对路径。

在使用非平凡生成表达式的情况下,可以在 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 属性)。也可以通过在 consumer 上设置 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 上:由 add_library() 命令使用 SHARED 选项创建并且其 ENABLE_EXPORTS 目标属性设置时的共享库目标的链接器导入文件(例如 .tbd)。

:prop_tgt:`ARCHIVE_OUTPUT_DIRECTORY`和:prop_tgt:`ARCHIVE_OUTPUT_NAME`目標屬性可用於控制建置樹中的歸檔輸出項目位置和名稱。

目錄作用域的命令

:command:`target_include_directories:command:`target_compile_definitions`和target_compile_options`命令仅对单一目标产生作用。`add_compile_definitions()`add_compile_options`和`include_directories`命令具有类似功能,但为便捷起见适用于目录范围而不是目标范围。

建置組態

配置决定某种构建类型的规格,如``Release``或``Debug``。指定方式取决于所用的`:manual:生成器<cmake-generators(7)>`的类型。对于单一配置生成器,如Makefile 向导`和忍者,在配置时通过`:variable:CMAKE_BUILD_TYPE`变量指定配置。对于多配置生成器,如Visual Studio:generator:`Xcode`和Ninja Multi-Config,用户可在构建时选择配置,而`:variable:CMAKE_BUILD_TYPE`会被忽略。多配置情况下,配置集合在配置时由:variable:`CMAKE_CONFIGURATION_TYPES`变量指定,但实际使用配置只会在构建阶段方能确定。这种差异往往容易被误解,导致如以下代码所示的问题出现:

# 警告:這對於多配置生成器是錯誤的,因為它們不使用並且通常甚至不設置CMAKE_BUILD_TYPE
string(TOLOWER ${CMAKE_BUILD_TYPE} build_type)
if (build_type STREQUAL debug)
  target_compile_definitions(exe1 PRIVATE DEBUG_BUILD)
endif()

相反,應使用`:manual:`生成器表達式 <cmake-generator-expressions(7)>`來正確處理配置特定的邏輯,而不管所使用的生成器。 例如:

# 對於單配置和多配置生成器都正確運行
target_compile_definitions(exe1 PRIVATE
  $<$<CONFIG:Debug>:DEBUG_BUILD>
)

在存在`:prop_tgt:已引入的`目标的情况时,MAP_IMPORTED_CONFIG_DEBUG < MAP_IMPORTED_CONFIG_< CONFIG>>`的内容也会被上文:genex:`$ < CONFIG:Debug>`表达式纳入考量。

大小寫敏感度

CMAKE_BUILD_TYPE`和:variable:`CMAKE_CONFIGURATION_TYPES`就像其他變數一樣,與它們的值進行的任何字符串比較都將是大小寫敏感的。:genex:`$<CONFIG>`生成器表達式也保留了由用戶或CMake默認設置的配置的大小寫。例如:

# 注意:不要使用這些模式,它們僅用於說明。

set(CMAKE_BUILD_TYPE Debug)
if(CMAKE_BUILD_TYPE STREQUAL DEBUG)
  # ... 永遠到不了這裡,"Debug" != "DEBUG"
endif()
add_custom_target(print_config ALL
  # 在這單一配置情況下顯示"Config is Debug"
  COMMAND ${CMAKE_COMMAND} -E echo "Config is $<CONFIG>"
  VERBATIM
)

set(CMAKE_CONFIGURATION_TYPES Debug Release)
if(DEBUG IN_LIST CMAKE_CONFIGURATION_TYPES)
  # ... 永遠到不了這裡,"Debug" != "DEBUG"
endif()

相反,CMake內部在使用它進行更改行為的地方對配置類型是不區分大小寫的。例如,:genex:`$<CONFIG:Debug>`生成器表達式將對於配置的結果為`DebugDEBUGdebug`甚至`DeBuG`值為1。因此,你可以用任意大小寫混合方式在CMAKE_BUILD_TYPE`和:variable:`CMAKE_CONFIGURATION_TYPES`中指定配置類型,儘管有強烈的慣例(見下一節)。如果必須在字符串比較中測試數值,則應先將數值轉換為大寫或小寫,並相應調整測試。

默認與自訂組態

默認情況下,CMake 定義了許多標準組態:

  • Debug

  • Debug

  • RelWithDebInfo

  • RelWithDebInfo

在多組態生成器中,`:variable:`CMAKE_CONFIGURATION_TYPES`變數將默認填充為上述列表(可能是一個子集),除非由專案或用戶重寫。實際使用的配置由用戶在構建時選擇。

對於單配置生成器,組態在配置時間用`:variable:`CMAKE_BUILD_TYPE`變數指定且不能在建置時更改。默認值通常不是上述標準配置中的任何一個,而是空字符串。一種常見的誤解是這與“Debug”相同,但情況並非如此。用戶應始終明確指定構建類型以避免此常見問題。

上述標準配置類型在大多數平台上提供合理的行為,但它們可以擴展以提供其他類型。每個配置都為所用語言定義一組編譯器和鏈接器旗標變量。這些變量遵循`:variable:`CMAKE_<LANG>_FLAGS_<CONFIG>`約定,其中`<CONFIG>`始終是大寫的配置名稱。在定義自定義組態類型時,確保正確設定這些變數,通常作為緩存變量。

偽目標

某些目標類型不代表構建系統的輸出,而僅代表輸入,如外部依賴、別名或其他非構建工件。偽目標不在生成的構建系統中表示。

匯入目標

:prop_tgt:`引入的`目标表示已有依赖。通常这样的目标由上游包定义并应被视为不可改变。声明一个已引入的`目标后,可以像常规目标一样使用如target_compile_definitions():command:`target_include_directories:command:`target_compile_options`或:command:`target_link_libraries`等常用命令调整其目标属性。

IMPORTED`目標可能具有與二進制目標相同的使用要求屬性,如:INTERFACE_INCLUDE_DIRECTORIES, :prop_tgt:`INTERFACE_COMPILE_DEFINITIONS, :prop_tgt:`INTERFACE_COMPILE_OPTIONS, :prop_tgt:`INTERFACE_LINK_LIBRARIES:prop_tgt:`INTERFACE_POSITION_INDEPENDENT_CODE

尽管很少需从引入的目标读取`LOCATION`,但也可以这么做。诸如`:command:添加自定义命令`的命令可以透明地用引入的:prop_tgt:`可执行<TYPE>`目标作为``命令``可执行文件。

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

還可以定義一個“GLOBAL”`:prop_tgt:`IMPORTED`目標,這在建置系統中可以全域存取。

根据`:manual:cmake-packages(7)`手册查看更多有关使用:prop_tgt:`引入的`目标创建软件包的信息。

別名目標

``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”目標,它可以是來自包的`:prop_tgt:`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 "名稱 Upstream::lib1是 ${_aliased} 的別名。")
endif()

介面庫

"INTERFACE" 載體目標不編譯來源,且不生成磁碟上圖書檔,故無`:prop_tgt:LOCATION

它可以指定`使用需求 <目标使用需求>`_,兼容接口属性,和`自定义传递属性 <自定义传递属性>`_。仅``INTERFACE``模式下:command:target_include_directories、:command:target_compile_definitions、:command:target_compile_options、:command:`target_sources`和:command:`target_link_libraries`命令可以与``INTERFACE``库一起使用。

從CMake 3.19開始,"INTERFACE"載體目標可能可選包含來源檔案。含有來源檔案的介面載體將會作為建置目標包含於生成的建置系統中。儘管它不編譯來源,也可能包含自定義指令用於生成其他來源。此外,IDE會將來源檔案作為載體的一部分顯示,以供互動式讀取和編輯。

"INTERFACE" 資料庫的主要用途是僅頭文件型資料庫。自 CMake 3.23 起,頭文件可以通過`:command:`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`的用法要求中的包含目錄。目標的使用要求在編譯時被消耗和使用,但對鏈接沒有影響。

另一個用例是為使用要求擁有一個完全聚焦於目標的設計:

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``中。安裝目標自動成為互換工作的使用要求中的包含目錄。

介面庫上允許的屬性

自CMake 3.19以來,介面庫允許像其他目標類型一樣,設定或閱讀具有任何名稱的目標屬性。

在CMake 3.19以前,介面庫僅允許撰寫或讀取具有有限名稱集的目標屬性:

  • 以``INTERFACE_``前端命名的屬性,無論是內置`使用要求 <Target Usage Requirements>`_,還是個性化名稱。

  • 内建属性以``COMPATIBLE_INTERFACE_``前缀命名(兼容接口属性)。

  • 內置屬性`:prop_tgt:NAME:prop_tgt:`EXPORT_NAME:prop_tgt:`EXPORT_PROPERTIES:prop_tgt:`MANUALLY_ADDED_DEPENDENCIES:prop_tgt:`IMPORTED:prop_tgt:`IMPORTED_LIBNAME_<CONFIG>:prop_tgt:`MAP_IMPORTED_CONFIG_<CONFIG>

  • 在 3.11 版被加入: 以首字下劃線(_)或 ASCII 小寫字母命名的屬性。