cmake 构建系统(7)

介绍

基于 CMake 的构建系统被组织为一组高级逻辑目标。每个目标对应一个可执行文件或库,或者是包含自定义命令的自定义目标。目标之间的依赖关系在构建系统中表达,以确定构建顺序和响应变化的再生规则。

二进制目标

可执行文件和库是使用 add_executable()add_library() 命令定义的。生成的二进制文件具有适合目标平台的 PREFIXSUFFIX 和扩展名。使用 target_link_libraries 命令表示二进制目标之间的依赖关系:

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

archive 被定义为 STATIC 库——包含从 archive.cppzip.cpplzma.cpp 编译的对象的存档。 zipapp 被定义为通过编译和链接``zipapp.cpp`` 形成的可执行文件。当链接 zipapp 可执行文件时,archive 静态库被链接进来。

二进制可执行文件

add_executable() 命令定义了一个可执行目标:

add_executable(mytool mytool.cpp)

诸如:command: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)
苹果框架

SHARED 库可以用 FRAMEWORK 目标属性标记,以创建 macOS 或 iOS Framework Bundle。具有 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>,目标文件集合可以用作其他目标的源输入。这是一个 generator expression 可用于将 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> 使用。

构建规范和使用要求

target_include_directories()target_compile_definitions()target_compile_options() 命令指定构建规范和二进制目标的使用要求。这些命令分别填充 INCLUDE_DIRECTORIESCOMPILE_DEFINITIONSCOMPILE_OPTIONS 目标属性,和/或 INTERFACE_INCLUDE_DIRECTORIESINTERFACE_COMPILE_DEFINITIONS 和 :prop_t GT:` INTERFACE_COMPILE_OPTIONS` 目标属性。

每个命令都有一个 PRIVATEPUBLICINTERFACE 模式。 PRIVATE 模式仅填充目标属性的非``INTERFACE_`` 变体,INTERFACE 模式仅填充``INTERFACE_`` 变体。 PUBLIC 模式填充相应目标属性的两个变体。每个命令都可以通过多次使用每个关键字来调用:

target_compile_definitions(archive
  PRIVATE BUILDING_WITH_LZMA
  INTERFACE USING_ARCHIVE_LIB
)

请注意,使用要求并不是为了让下游使用特定的 COMPILE_OPTIONSCOMPILE_DEFINITIONS 等仅为方便起见而设计的。属性的内容必须是**要求**,而不仅仅是推荐或方便。

请参阅 cmake-packages(7) 手册的 创建可重定位包 部分,讨论在创建用于重新分发的包时指定使用要求时必须注意的额外注意事项。

目标属性

INCLUDE_DIRECTORIESCOMPILE_DEFINITIONSCOMPILE_OPTIONS 目标属性的内容在编译二进制目标的源文件时被适当使用。

INCLUDE_DIRECTORIES 中的条目被添加到带有 -I-isystem 前缀的编译行,并按照属性值中的出现顺序。

COMPILE_DEFINITIONS 中的条目以``-D`` 或``/D`` 为前缀,并以未指定的顺序添加到编译行。 DEFINE_SYMBOL 目标属性也作为编译定义添加为 SHAREDMODULE 库目标的特殊便利情况。

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

INTERFACE_INCLUDE_DIRECTORIESINTERFACE_COMPILE_DEFINITIONSINTERFACE_COMPILE_OPTIONS 目标属性的内容是*使用要求*——它们指定了消费者必须使用的内容才能正确编译并链接到它们出现的目标在。对于任何二进制目标,在 target_link_libraries() 命令中指定的每个目标上的每个 INTERFACE_ 属性的内容被消耗:

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)

因为在INCLUDE_DIRECTORIES中通常需要添加源码目录和对应的build目录,所以可以开启CMAKE_INCLUDE_CURRENT_DIR变量,方便的将对应的目录添加到INCLUDE_DIRECTORIES中所有目标。可以启用变量 CMAKE_INCLUDE_CURRENT_DIR_IN_INTERFACE 以将相应目录添加到所有目标的 INTERFACE_INCLUDE_DIRECTORIES。这使得通过使用 target_link_libraries 命令可以方便地使用多个不同目录中的目标。

传递使用要求

目标的使用要求可以传递地传播到依赖项。 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) 命令导出以进行安装的目标指定使用要求时必须小心。有关更多信息,请参阅 创建包

兼容接口属性

一些目标属性需要在目标和每个依赖项的接口之间兼容。例如, 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)

在这里,exe1exe2 都将被编译为位置无关代码。 lib1 也将被编译为与位置无关的代码,因为这是 SHARED 库的默认设置。如果依赖项有冲突的、不兼容的要求: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`` 目标的 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_MAXCOMPATIBLE_INTERFACE_NUMBER_MIN 的情况下,调试输出显示每个依赖项的属性值,以及该值是否确定新的极值。

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

构建规范可以使用 generator expressions 包含可能是有条件的或仅在生成时已知的内容。例如,可以使用“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: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 可执行文件将使用``-DLIB1_WITH_EXE`` 进行编译,而``shared_lib`` 共享库将使用``-DLIB1_WITH_SHARED_LIB`` 和``-DCONSUMER_CMP0041_NEW`` 进行编译,因为策略:policy: CMP0041 在创建 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_INTERFACE`BUILD_INTERFACE,并删除了 *_INTERFACE 标记。使用“ClimbingStats”包的单独项目将包含:

find_package(ClimbingStats REQUIRED)

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

根据“ClimbingStats”包是从构建位置还是从安装位置使用,“下游”目标将使用“-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
)

相对路径被解释为相对于命令出现的源目录。 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)

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

imported target 的 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 平台。

运行时输出工件

构建系统目标的 runtime 输出工件可能是:

  • add_executable() 命令创建的可执行目标的可执行文件(例如 .exe)。

  • 在 DLL 平台上:由带有 SHARED 选项的 add_library 命令创建的共享库目标的可执行文件(例如 .dll)。

RUNTIME_OUTPUT_DIRECTORYRUNTIME_OUTPUT_NAME 目标属性可用于控制构建树中的运行时输出工件位置和名称。

库输出工件

构建系统目标的 library 输出工件可能是:

  • 由带有 MODULE 选项的 add_library 命令创建的模块库目标的可加载模块文件(例如 .dll 或 .so )。

  • 在非 DLL 平台上:共享库目标的共享库文件(例如 .so.dylib)由带有 SHARED 选项的 add_library 命令创建。

LIBRARY_OUTPUT_DIRECTORYLIBRARY_OUTPUT_NAME 目标属性可用于控制构建树中库输出工件的位置和名称。

存档输出工件

构建系统目标的 archive 输出工件可能是:

  • 由带有 STATIC 选项的 add_library 命令创建的静态库目标的静态库文件(例如 .lib 或 .a )。

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

  • 在 DLL 平台上:当 ENABLE_EXPORTS 目标属性设置时,由 add_executable 命令创建的可执行目标的导入库文件(例如 .lib)。

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

ARCHIVE_OUTPUT_DIRECTORYARCHIVE_OUTPUT_NAME 目标属性可用于控制构建树中的存档输出工件位置和名称。

目录范围的命令

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

构建配置

配置确定特定类型构建的规范,例如“发布”或“调试”。指定的方式取决于所使用的 generator 的类型。对于像 Makefile Generators 和 Ninja 这样的单一配置生成器,配置在配置时由 CMAKE_BUILD_TYPE 变量指定。对于 Visual Studio <Visual Studio Generators>、生成器 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()

Generator expressions 应该被用来正确处理特定于配置的逻辑,而不管使用的是什么生成器。例如:

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

在存在 IMPORTED 目标的情况下, MAP_IMPORTED_CONFIG_DEBUG 的内容也由上述 $<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> 生成器表达式将评估为 1 的配置不仅是 Debug,还有 DEBUGdebug 甚至 调试。因此,您可以在 CMAKE_BUILD_TYPECMAKE_CONFIGURATION_TYPES 中使用大小写的任意混合指定配置类型,尽管有严格的约定(请参阅下一节)。如果必须在字符串比较中测试值,请始终先将值转换为大写或小写,然后相应地调整测试。

默认和自定义配置

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

  • 调试

  • 发布

  • RelWithDebInfo

  • MinSizeRel

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

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

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

伪目标

一些目标类型不代表构建系统的输出,而只代表输入,例如外部依赖项、别名或其他非构建工件。伪目标不在生成的构建系统中表示。

进口目标

IMPORTED 目标表示预先存在的依赖项。通常这样的目标是由上游包定义的,应该被视为不可变的。声明 IMPORTED 目标后,可以使用常用命令调整其目标属性,例如:command:target_compile_definitionstarget_include_directories()target_compile_options() 或 :command:`target_link_libraries`就像任何其他常规目标一样。

IMPORTED 目标可能具有与二进制目标相同的使用要求属性,例如 INTERFACE_INCLUDE_DIRECTORIES、INTERFACE_COMPILE_DEFINITIONS、INTERFACE_COMPILE_OPTIONS、INTERFACE_LINK_LIBRARIES 和 INTERFACE_POSITION_INDEPENDENT_CODE

LOCATION 也可以从 IMPORTED 目标中读取,尽管很少有理由这样做。诸如 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

它可以指定使用要求,例如_tgt: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”。安装目标自动成为包含目录,这是消费者的使用要求。