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 框架

共享库和静态库可以标记 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
)

模块库

模块库是通过链接对象文件生成的二进制文件。与共享库不同,模块库不能作为依赖项被其他二进制文件链接——不要在 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 使用要求属性。

它还支持指定 文件集,这些文件集可以添加 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 使用要求属性。

目标构建规范

二进制目标的构建规范由目标属性表示。对于以下每种 编译链接 属性,目标的编译和链接将受其自身值以及从链接依赖项的传递闭包收集的相应 使用要求 属性(名称以 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

通常,依赖项应在 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 版本新增。

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_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 进行编译,因为策略 CMP0182 在创建 shared_lib 目标时为 NEW

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

这相当于在由 install(EXPORT) 生成的每个已安装 IMPORTED 目标上将其 ${CMAKE_INSTALL_PREFIX}/include 追加到 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 文件(例如 .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 平台上:当 add_executable() 命令的 ENABLE_EXPORTS 目标属性设置为 true 时,该命令创建的可执行目标的导入库文件(例如 .lib)。

  • 在 AIX 上:当 add_executable() 命令的 ENABLE_EXPORTS 目标属性设置为 true 时,该命令创建的可执行目标的链接器导入文件(例如 .imp)。

  • 在 macOS 上:当 add_library() 命令和 SHARED 选项一起使用,并且其 ENABLE_EXPORTS 目标属性设置为 true 时,该命令创建的共享库目标的链接器导入文件(例如 .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 配置求值为 1,对于 DEBUGdebug 甚至 DeBuG 也是如此。因此,您可以在 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 变量的范围。

还可以定义一个 GLOBAL IMPORTED 目标,该目标在构建系统中全局可访问。

有关使用 IMPORTED 目标创建包的更多信息,请参阅 cmake-packages(7) 手册。

别名目标

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

它可以指定用法要求兼容接口属性自定义传递属性。只能使用 target_include_directories()target_compile_definitions()target_compile_options()target_sources()target_link_libraries() 命令的 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 目标用法要求的包含目录。目标的使用要求被消耗并用于编译,但对链接没有影响。

另一个用例是采用完全面向目标的用法要求设计。

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 之前,接口库只允许设置或读取具有有限名称集的目标属性。