cmake buildsystem

520 阅读15分钟

原文地址:cmake.org/cmake/help/…

1. Introduction

基于 CMake 的 buildsystem 被组织为一组 high-level logic targets。每个 target 对应一个 executable 或 library,或者是包含 custom commands 的 custom target。target 之间的依赖关系在 buildsystem 中表达,以确定 build order 和响应变化的 regeneration rules。

2. Binary Targets

executables 和 libraries 是使用 add_executable()add_library() commands 定义的。生成的 binary files 具有适合 target platform 的PREFIX, SUFFIX 和 extensions。binary target 之间的依赖关系使用 target_link_libraries() 命令表示:

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

archive 被定义为一个 STATIC library——一个包含从 archive.cpp、zip.cpp 和 lzma.cpp compiled 的 objects 的 archive。 zipapp 被定义为通过 compiling 和 linking zipapp.cpp 形成的 executable。当 linking zipapp executable 时,archive static library 被 linked in。

2.1 Binary Executables

add_executable() command 定义了一个 executable target:

add_executable(mytool mytool.cpp)

其他 commands 比如 add_custom_command(), 可以直接使用 mytool 这个 target, 并且会管理 add_custom_command() 与 mytool 的 dependency。

诸如 add_custom_command() 之类的 command 生成在 build 时运行的 rules,可以透明地将 EXECUTABLE 目标用作 COMMAND executable。buildsystem rules 将确保在尝试运行 command 之前 build executable。

2.2 Binary Library Types

2.2.1 Normal Libraries

默认情况下,add_library() command 定义一个 STATIC library,除非指定了 type。使用 command 时可以指定 type:

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

可以 enable BUILD_SHARED_LIBS 变量来更改 add_library() 的默认行为以 build shared libraries。

MODULE类型的库有点特殊,与STATIC和SHARED的不同之处在于它通常不被用于链接,即他不在target_link_libraries()命令里使用。他是一种使用运行时技术作为插件加载的类型。如果库不导出任何非托管符号(例如Windows 资源 DLL、C++/CLI DLL),则要求库不是 SHARED 库,因为CMake期望 SHARED 库至少导出一个符号。

在整个 buildsystem 定义的上下文中,特定库是 SHARED 的还是 STATIC 的在很大程度上是无关紧要的——无论 library type 如何,commands、dependency specifications 和其他 API 的工作方式都是相似的。 MODULE library type 的不同之处在于它通常不会被 linked to——它不在 target_link_libraries() command 的右侧使用。它是一种使用 runtime techniques 作为插件加载的 type。如果 library 不 export 任何 unmanaged(非托管) symbols(例如 Windows 资源 DLL、C++/CLI DLL),则要求 library 不是 SHARED library,因为 CMake 期望 SHARED library 至少 export 一个 symbol。

add_library(archive MODULE 7z.cpp)

2.2.1.1 Apple Frameworks

SHARED library 可以 marked 有 FRAMEWORK target property 以创建 macOS 或 iOS Framework Bundle。具有 FRAMEWORK target property 的 library 还应设置 FRAMEWORK_VERSION target property。根据 macOS 约定,此 property 通常设置为值“A”。 MACOSX_FRAMEWORK_IDENTIFIER 设置 CFBundleIdentifier key,它唯一标识 bundle。

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

2.2.2 Object Libraries

OBJECT library type 定义了编译给定源文件所产生的 object files 的 non-archival collection。通过使用语法 $<TARGET_OBJECTS:name>,object files collection 可以用作其他 targets 的源输入。这是一个 generator expression,可用于将 OBJECT library 内容提供给其他 targets:

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)

其他 targets 的 link(或 archiving)步骤将使用 object files collection 以及来自它们自己的 sources 的 objection files collection。

或者,object library 可以 linked to 其他 targets:

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)

其他 targets 的 link(或 archiving)步骤将使用 OBJECT library 中的 object files。此外,在 targets 中编译源代码时,将遵守 OBJECT libraries 的 usage requirements。此外,这些 usage requirements 将传递给 targets 的 dependents。

使用 add_custom_command(TARGET) command 时,OBJECT libraries 不能用作 TARGET。但是,通过使用 $<TARGET_OBJECTS:objlib>add_custom_command(OUTPUT)file(GENERATE) 可以使用 objects 列表。

3. Build Specification and Usage Requirements

构建规范和使用要求

target_include_directories()target_compile_definitions()target_compile_options() command 指定 build specification 和 binary targets 的 usage requirements。这些 command 分别填充 INCLUDE_DIRECTORIESCOMPILE_DEFINITIONSCOMPILE_OPTIONS target properties,和/或 INTERFACE_INCLUDE_DIRECTORIESINTERFACE_COMPILE_DEFINITIONSINTERFACE_COMPILE_OPTIONS target properties。

每个 command 都有 PRIVATEPUBLICINTERFACE 模式。 PRIVATE mode 仅填充 target property 的 non-INTERFACE_ 变体,而 INTERFACE mode 仅填充 INTERFACE_ 变体。 PUBLIC mode 填充相应 target property 的两个变体。每个 command 都可以通过多次使用每个关键字来调用:

target_compile_definitions(archive
  PRIVATE BUILDING_WITH_LZMA
  INTERFACE USING_ARCHIVE_LIB
)

请注意,usage requirements 并不是为了让下游使用特定的 COMPILE_OPTIONSCOMPILE_DEFINITIONS 等而设计的,只是为了方便。properties 的内容必须是要求,而不仅仅是建议或方便。

请参阅 cmake-packages(7) 手册的Creating Relocatable Packages,以讨论在创建用于重新分发的 packages 时指定 usage requirements 的时候必须注意的额外注意事项。

3.1 Target Properties

目标属性

编译 binary target 的源文件时,会适当使用 INCLUDE_DIRECTORIESCOMPILE_DEFINITIONSCOMPILE_OPTIONS target properties 的内容。

INCLUDE_DIRECTORIES 中的条目添加到带有 -I-isystem prefixes 的 compile line,并按照 property 值中出现的顺序添加。

COMPILE_DEFINITIONS 中的条目以 -D/D 为 prefixes,并以未指定的顺序添加到 compile line。 DEFINE_SYMBOL target property 也作为 compile definition 添加,作为 SHAREDMODULE library target 的特殊便利情况。

COMPILE_OPTIONS 中的条目针对 shell 进行了转义,并按照 property 值中出现的顺序添加。几个编译选项有特殊的单独处理,例如 POSITION_INDEPENDENT_CODE

INTERFACE_INCLUDE_DIRECTORIESINTERFACE_COMPILE_DEFINITIONSINTERFACE_COMPILE_OPTIONS target properties 的内容是 Usage Requirements——它们指定消费者必须使用的内容才能正确 compile 并 link 到它们出现的 target。对于任何 binary target,会使用 target_link_libraries() 命令中指定的每个 target 上的每个 INTERFACE_ property 的内容:

set(srcs archive.cpp zip.cpp)
if (LZMA_FOUND)
  list(APPEND srcs lzma.cpp)
endif()
add_library(archive SHARED ${srcs})
if (LZMA_FOUND)
  # The archive library sources are compiled with -DBUILDING_WITH_LZMA
  target_compile_definitions(archive PRIVATE BUILDING_WITH_LZMA)
endif()
target_compile_definitions(archive INTERFACE USING_ARCHIVE_LIB)

add_executable(consumer)
# Link consumer to archive and consume its usage requirements. The consumer
# executable sources are compiled with -DUSING_ARCHIVE_LIB.
target_link_libraries(consumer archive)

因为通常要求将 source directory 和对应的 build directory 添加到 INCLUDE_DIRECTORIES 中,可以 enble CMAKE_INCLUDE_CURRENT_DIR 变量,方便的将对应的 directories 添加到所有 targets 的 INCLUDE_DIRECTORIES 中。可以 enable 变量 CMAKE_INCLUDE_CURRENT_DIR_IN_INTERFACE 将相应的 directories 添加到所有 targets 的 INTERFACE_INCLUDE_DIRECTORIES 中。这使得通过使用 target_link_libraries() command 可以方便地使用多个不同 directories 中的 targets。

3.2 Transitive Usage Requirements

传递使用要求

target 的 usage requirements 可以传递地传播到 dependents。 target_link_libraries() command 由 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 dependency,所以它的 usage requirements 也会传播给 consumer

因为 serializationarchiveExtrasPRIVATE dependency,所以它的 usage requirements 不会传播给 consumer。

通常,如果仅由 library 的实现而不是在头文件中使用,则应在使用带有 PRIVATE 关键字的 target_link_libraries() 指定 dependency。如果在 library 的头文件中额外使用 dependency(例如,用于类继承),则应将其指定为 PUBLIC dependency。不被 library 的实现使用,而仅被其头文件使用的 dependency 应指定为 INTERFACE dependency。 target_link_libraries() command 可以通过多次使用每个关键字来调用:

target_link_libraries(archiveExtras
  PUBLIC archive
  PRIVATE serialization
)

通过从 dependencies 中读取 target property 的 INTERFACE_ 变体并将值附加到 operand 的 non-INTERFACE_ 变体来传播 usage requirements。例如,读取 dependencies 的 INTERFACE_INCLUDE_DIRECTORIES 并将其附加到 operand 的 INCLUDE_DIRECTORIES。如果顺序相关并得到维护,并且 target_link_libraries() 调用产生的顺序不允许正确编译,则使用适当的 command 直接设置 property 可以更新顺序。

例如,如果必须以 lib1 lib2 lib3 的顺序指定 target 的 libraries libraries,但必须以 lib3 lib1 lib2 的顺序指定 include directories:

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

请注意,在为将使用 install(EXPORT) command export 以进行 installation 的 targets 指定 usage requirements 时必须小心。有关更多信息,请参阅 Creating Packages

3.3 Compatible Interface Properties

兼容接口属性

一些 target properties 需要在 target 和每个 dependency 的 interface 之间兼容。例如,POSITION_INDEPENDENT_CODE target property 可以指定一个布尔值,表示 target 是否应 compiled 为 position-independent-code,这具有 platform-specific 的后果。target 还可以指定 usage requirement INTERFACE_POSITION_INDEPENDENT_CODE 以传达 consumers 必须 compiled 为 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 都将被 compiled 为 position-independent-code。 lib1 也将被 compiled 为 position-independent-code,因为这是 SHARED libraries的默认设置。如果 dependencies 有冲突、不兼容的要求,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 requirement INTERFACE_POSITION_INDEPENDENT_CODEexe1 target 的 POSITION_INDEPENDENT_CODE property 不“兼容”。该 library 要求 consumers built 为 position-independent-code,而 executable 指定不 built 为 position-independent-code,因此发出诊断。

lib1lib2 requirements 不“兼容”。其中一个要求 consumers 被 built 为position-independent-code,而另一个要求 consumers 不被 built 为 position-independent-code。由于 exe2 links 到两者并且它们存在冲突,因此发出 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 property(如果设置)必须在布尔意义上与设置该 property 的所有传递指定 dependencies 的 INTERFACE_POSITION_INDEPENDENT_CODE property 相同。

通过在 COMPATIBLE_INTERFACE_BOOL target property 的内容中指定 property,可以将“兼容接口要求”的这一属性扩展到其他 property。每个指定的 property 必须在consuming target 和相应 dependencies 之间兼容,每个 dependency 都带有 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

非布尔 properties 也可以参与“兼容接口”计算。 COMPATIBLE_INTERFACE_STRING property 中指定的 properties 必须未指定或与所有可传递指定的 dependencies 中的相同字符串进行比较。这对于确保 library 的多个不兼容版本不会通过 target 的传递要求 linked 在一起很有用:

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 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_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 可用于计算 dependencies property 的最小数值。

每个计算出的“兼容” property 值都可以在生成时使用 generator expressions 在消费者中读取。

请注意,对于每个 dependee,每个兼容接口 property 中指定的 properties set 不得与任何其他 properties 中指定的 properties set 相交。

3.4 Property Origin Debugging

属性源调试

因为 build specifications 可以由 dependencies 确定,所以 creates target 的代码和负责 set build specifications 的代码缺乏局部性可能会使代码更难以推理。 cmake(1) 提供了一个调试工具来打印可以由 dependencies 确定的 properties 内容的 origin。可以调试的 properties 列在 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 中列出的 properties,调试输出显示哪个 target 负责设置该 property,以及哪些其他 dependencies 也定义了该 property。在 COMPATIBLE_INTERFACE_NUMBER_MAXCOMPATIBLE_INTERFACE_NUMBER_MIN 的情况下,调试输出显示每个 dependency 的 property 值,以及该值是否确定新的极值。

3.5 Build Specification with Generator Expressions

使用生成器表达式构建规范

build specification 可以使用包含可能是有条件的或仅在生成时已知的内容的 generator expressions。例如,可以使用 TARGET_PROPERTY expression 读取 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>
)

在这种情况下,将使用 -DCONTAINER_SIZE=200 编译 exe1 源文件。

一元 TARGET_PROPERTY generator expression 和 TARGET_POLICY generator expression 使用 consuming target 上下文进行评估。这意味着可以根据 consumer 对 usage requirement specification 进行不同的评估:

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)

exe1 executable 将使用 -DLIB1_WITH_EXE 进行编译,而 shared_lib shared library 将使用 -DLIB1_WITH_SHARED_LIB-DCONSUMER_CMP0041_NEW 进行编译,因为策略 CMP0041 在创建 shared_lib 目标时是 NEW 的。

BUILD_INTERFACE expression 包装了 requirements,这些 requirements 仅在从同一 buildsystem 中的 target 使用时使用,或者从使用 export() command exported 到 build directory 的 target 使用时使用。 INSTALL_INTERFACE expression 包装了仅在从已使用 install(EXPORT) command install 和 export 的 target 使用时使用的 requirements:

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 executable 将使用 -DClimbingStats_FROM_BUILD_LOCATION 进行编译。导出命令生成 IMPORTED targets,其中省略了 INSTALL_INTERFACEBUILD_INTERFACE,并删除了 *_INTERFACE 标记。使用 ClimbingStats package 的单独项目将包含:

find_package(ClimbingStats REQUIRED)

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

根据 ClimbingStats package 是从 build 位置还是从 install 位置使用,Downstream target 将使用 -DClimbingStats_FROM_BUILD_LOCATION-DClimbingStats_FROM_INSTALL_LOCATION 进行编译。有关 packages 和 exporting 的更多信息,请参阅 cmake-packages(7) 手册。

3.5.1 Include Directories and Usage Requirements

Include Directories 和 Usage Requirements

include directories 在指定 usage requirements 时以及与 generator expressions 一起使用时需要一些特殊考虑。 target_include_directories() command 接受相对和绝对 include directories:

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

相对路径被解释为相对于 command 出现的源目录。 IMPORTED targets 的 INTERFACE_INCLUDE_DIRECTORIES 中不允许使用相对路径。

在使用 non-trivial generator expression 的情况下,可以在 INSTALL_INTERFACE 表达式的参数中使用 INSTALL_PREFIX 表达式。它是一个 replacement marker,在由 consuming project import 时扩展为 installation prefix。

include directories usage requirements 通常在 build-tree 和 install-tree 之间有所不同。 BUILD_INTERFACEINSTALL_INTERFACE generator expressions 可用于根据使用位置描述单独的 usage requirements。 INSTALL_INTERFACE expressions 中允许使用相对路径,并相对于 installation prefix 进行解释。例如:

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

提供了两个与 include directories usage requirements 相关的便利 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}>
)

对于每个受影响的 targets。installed targets 的便利是一个 INCLUDES DESTINATION 组件和 install(TARGETS) 命令:

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

这相当于在由 install(EXPORT) 生成时将 ${CMAKE_INSTALL_PREFIX}/include 附加到每个已安装的 IMPORTED targets 的 INTERFACE_INCLUDE_DIRECTORIES

当使用 imported targetINTERFACE_INCLUDE_DIRECTORIES 时,property 中的条目将被视为 SYSTEM 包含目录,就好像它们列在 dependency 的 INTERFACE_SYSTEM_INCLUDE_DIRECTORIES 中一样。这可能会导致忽略针对在这些目录中找到的标头的编译器警告。Imported Targets 的这种行为可以通过在 imported targets 的消费者上设置 NO_SYSTEM_FROM_IMPORTED target property,或通过在 imported targets 本身上设置 IMPORTED_NO_SYSTEM target property 来控制。

如果 binary target 可传递地链接到 macOS FRAMEWORK,则框架的 Headers 目录也被视为 usage requirements。这与将 framework 目录作为 include directory 传递具有相同的效果。

3.6 Link Libraries and Generator Expressions

链接库和生成器表达式

与 build specifications 一样,可以使用 generator expression condition 制定 link libraries。然而,由于对 consumption 的约束是基于从 linked dependencies 中收集的,因此存在一个额外的限制,即 linked dependencies 必须是一个“有向无环图”。也就是说,如果 linking to a target 依赖于 target property 的值,则该 target property 不可以依赖于 linked dependencies:

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 target 的 POSITION_INDEPENDENT_CODE 属性的值依赖于 linked libraries(lib3),并且 linking exe1 的 edge 由相同的 POSITION_INDEPENDENT_CODE 属性确定,因此上面的依赖关系图包含一个循环。 cmake(1) 发出错误消息。

3.7 Output Artifacts

输出工件

buildsystem targets 由 add_library()add_executable() 命令生成,他们制定了生成 binary output 的规则。二进制文件的确切输出位置只能在生成时确定,因为它可能取决于 build-configuration 和 linked dependencies 的 link-language 等。TARGET_FILETARGET_LINKER_FILE 和 related expressions 可用于访问 generated binaries 的 name 和 location。但是,这些 expressions 不适用于 OBJECT 类型的库,因为此类库没有生成与 expressions 相关的单个文件。

目标可以构建三种 output artifacts ,如以下部分所述。它们的分类在 DLL 平台和非 DLL 平台之间有所不同。包括 Cygwin 在内的所有基于 Windows 的系统都是 DLL 平台。

3.7.1 Runtime Output Artifacts

运行时输出工件

buildsystem target 的 Runtime Output Artifacts 可能是:

  • add_executable() 命令创建的 executable target 的 executable file(例如 .exe)。

  • 在 DLL 平台上:由带有 SHARED 选项的 add_library() 命令创建的 shared library target 的 executable file(例如 .dll)。

RUNTIME_OUTPUT_DIRECTORYRUNTIME_OUTPUT_NAME target properties 可用于控制构建树中的 Runtime Output Artifacts locations 和 names。

3.7.2 Library Output Artifacts

库输出工件

buildsystem target 的 Library Output Artifacts 可能是:

  • module library target 的 loadable module 文件(例如 .dll 或 .so),由带有 MODULE 选项的 add_library() 命令创建。

  • 在 non-DLL 平台上:由带有 SHARED 选项的 add_library() 命令创建的 shared library target 的 shared library file(例如 .so 或 .dylib)。

LIBRARY_OUTPUT_DIRECTORYLIBRARY_OUTPUT_NAME target properties 可用于控制构建树中的library output artifact locations 和 names。

3.7.3 Archive Output Artifacts

存档输出工件

buildsystem target 的 Archive Output Artifacts 可能是:

  • 由带有 STATIC 选项的 add_library() 命令创建的 static library target 的 static library file(例如 .lib 或 .a)。

  • 在 DLL 平台上:由带有 SHARED 选项的 add_library() 命令创建的 shared library target 的 import library file(例如 .lib)。仅当库导出至少一个非托管符号时,才能保证此文件存在。

  • 在 DLL 平台上:在设置 ENABLE_EXPORTS target property 时由 add_executable() 命令创建的 executable target 的 import library file(例如 .lib)。

  • 在 AIX 上:由 add_executable() 命令创建的 executable target 的 linker import file(例如 .imp),当它的 ENABLE_EXPORTS target property 被设置时。

ARCHIVE_OUTPUT_DIRECTORYARCHIVE_OUTPUT_NAME target property 可用于控制构建树中的 archive output artifact locations 和 names。

3.8 Directory-Scoped Commands

目录范围的命令

target_include_directories()target_compile_definitions()target_compile_options() 命令一次仅对一个 target 产生影响。命令 add_compile_definitions()add_compile_options()include_directories() 具有类似的功能,但为方便起见,它们在 directory scope 而不是 target scope 内运行。

4 Build Configurations

构建配置

configurations 确定特定 type 构建的规范,例如 ReleaseDebug。指定的方式取决于所使用的 generator 的 type。对于像 Makefile GeneratorsNinja 这样的 single configuration generators,configuration 在配置时由 CMAKE_BUILD_TYPE 变量指定。对于 Visual StudioXcodeNinja Multi-Config 等 multi-configuration generators,configuration 由用户在构建时选择,CMAKE_BUILD_TYPE 被忽略。在 multi-configuration 情况下,the set of available configurations 在配置时由 CMAKE_CONFIGURATION_TYPES 变量指定,但实际使用的 configuration 要到构建阶段才能知道。这种差异经常被误解,导致出现如下有问题的代码:

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

无论使用何种 generator,都应使用 Generator expressions 来正确处理 configuration-specific 的逻辑。例如:

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

在存在 IMPORTED target 的情况下,MAP_IMPORTED_CONFIG_DEBUG 的内容也由上述 $<CONFIG:Debug> 表达式说明。

4.1 Case Sensitivity

区分大小写

CMAKE_BUILD_TYPECMAKE_CONFIGURATION_TYPES 就像其他变量一样,因为任何字符串与它们的值进行比较都是区分大小写的。 $<CONFIG> generator expression 还保留用户设置的配置的大小写或 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> generator expression 的结果在以下情况下都为1,不仅是 Debug,而且是 DEBUG、debug 甚至 DeBuG。因此,您可以在 CMAKE_BUILD_TYPECMAKE_CONFIGURATION_TYPES 中使用大小写的任意混合指定配置类型,尽管有严格的约定(请参阅下一节)。如果必须在字符串比较中测试值,请始终先将值转换为大写或小写,然后相应地调整测试。

4.2 Default And Custom Configurations

默认和自定义配置

默认情况下,CMake 定义了一些标准配置:

  • Debug
  • Release`
  • RelWithDebInfo
  • MinSizeRel

在 multi-config generators 中,默认情况下,CMAKE_CONFIGURATION_TYPES 变量将填充上述列表(可能是其子集),除非被项目或用户覆盖。实际使用的配置由用户在构建时选择。

对于 single-config generators,配置在配置时使用 CMAKE_BUILD_TYPE 变量指定,并且不能在构建时更改。默认值通常不是上述标准配置,而是一个空字符串。一个常见的误解是,这与 Debug 相同,但事实并非如此。用户应该始终明确指定构建类型以避免这种常见问题。

上述标准配置类型在大多数平台上提供了合理的行为,但它们可以扩展以提供其他类型。每个配置都为所使用的语言定义了一组编译器和链接器 flag 变量。这些变量遵循 CMAKE_<LANG>_FLAGS_<CONFIG> 约定,其中 <CONFIG> 始终是大写的配置名称。定义自定义配置类型时,请确保正确设置这些变量,通常作为缓存变量。

5 Pseudo Targets

伪目标

一些 target 类型不代表 buildsystem 的 outputs,而只代表 inputs,例如 external dependencies、aliases 或其他 non-build artifacts。伪目标不在生成的构建系统中给出。

5.1 Imported Targets

导入目标

IMPORTED target 表示预先存在的依赖项。通常这样的 target 是由上游包定义的,应该被视为不可变的。声明一个 IMPORTED target 后,可以像使用任何其他常规 target 一样,使用 target_compile_definitions()target_include_directories()target_compile_options()target_link_libraries() 等常用命令调整其 target属性。

IMPORTED targets 可能具有与 binary targets 相同的 usage requirement 属性,例如 INTERFACE_INCLUDE_DIRECTORIESINTERFACE_COMPILE_DEFINITIONSINTERFACE_COMPILE_OPTIONSINTERFACE_LINK_LIBRARIESINTERFACE_POSITION_INDEPENDENT_CODE

LOCATION 也可以从 IMPORTED target 中读取,尽管很少有理由这样做。诸如 add_custom_command() 之类的命令可以透明地使用 IMPORTED EXECUTABLE target 作为 COMMAND executable。

IMPORTED target 定义的 scope 是定义它的目录。它可以从子目录访问和使用,但不能从父目录或兄弟目录访问和使用。范围类似于 cmake 变量的 scope。

也可以定义一个 GLOBAL IMPORTED target,它可以在构建系统中全局访问。

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

5.2 Alias Targets

别名目标

ALIAS target 是一个名称,可以在只读上下文中与 binary target 名称互换使用。 ALIAS target 的主要用例是例如库附带的单元测试可执行文件,它们可能是同一 buildsystem 的一部分,也可能根据用户配置单独构建。

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)

在另一个目录中,我们可以无条件地 link 到 Upstream::lib1 目标,它可能是来自 package 的 IMPORTED target,或者如果作为同一个 buildsystem 的一部分构建,则为 ALIAS 目标。

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

ALIAS target 是非 mutable、installable 或 exportable 的。它们完全是 buildsystem 描述的本地内容。可以通过读取 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()

5.3 Interface Libraries

接口库

INTERFACE library target 不编译源代码,也不在磁盘上生成 library artifact,因此它没有 LOCATION

它可以指定 usage requirement,例如 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 modes 可以与 INTERFACE 库一起使用。

从 CMake 3.19 开始,INTERFACE library target 可以选择包含 source files。包含 source files 的 interface library 将作为 build target 包含在生成的 buildsystem 中。它不编译源代码,但可以包含生成其他源代码的 custom commands。此外,IDE 会将源文件显示为交互式阅读和编辑目标的一部分。

INTERFACE libraries 的主要用例是 header-only libraries。从 CMake 3.23 开始,头文件可以通过使用 target_sources()) 命令将头文件添加到 header set 来与库相关联:

add_library(Eigen INTERFACE)

target_sources(Eigen INTERFACE
  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 usage requirements 的 include 目录。来自 target 的 usage requirements 在编译时被消耗和使用,但对链接没有影响。

另一个用例是针对 usage requirements 采用完全以 target 为中心的设计:

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 的构建规范完全表示为 linked targets,compiler-specific flags 的复杂性封装在 INTERFACE library target 中。

可以 install 和 export INTERFACE libraries。我们可以安装默认 header set 和 target:

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
)

这里,header set 中定义的 headers 被安装到 include/Eigen。install destination 自动成为 include directory,这是消费者的 usage requirement。