cmake-buildsystem(7)¶
简介¶
基于 CMake 的构建系统组织为一组高级逻辑目标。每个目标对应于一个可执行文件或库,或者是一个包含自定义命令的自定义目标。目标之间的依赖关系在构建系统中表达,以确定构建顺序以及响应更改而重新生成的规则。
二进制目标¶
可执行文件和库使用 add_executable()
和 add_library()
命令定义。生成的二进制文件具有针对目标平台的适当的 PREFIX
、 SUFFIX
和扩展名。二进制目标之间的依赖关系使用 target_link_libraries()
命令表达。
add_library(archive archive.cpp zip.cpp lzma.cpp)
add_executable(zipapp zipapp.cpp)
target_link_libraries(zipapp archive)
archive
被定义为一个 STATIC
库 -- 一个包含从 archive.cpp
、 zip.cpp
和 lzma.cpp
编译的对象的归档文件。zipapp
被定义为一个通过编译和链接 zipapp.cpp
形成的可执行文件。当链接 zipapp
可执行文件时,archive
静态库被链接进来。
可执行文件¶
可执行文件是通过将目标文件链接在一起而创建的二进制文件,其中一个目标文件包含程序入口点,例如 main
。
add_executable()
命令定义一个可执行目标。
add_executable(mytool mytool.cpp)
CMake 生成构建规则以将源文件编译为目标文件,并将它们链接到可执行文件中。
可执行文件的链接依赖项可以使用 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 生成构建规则以将源文件编译为目标文件,并将它们归档到静态库中。
静态库的链接依赖项可以使用 target_link_libraries()
命令指定。由于静态库是归档文件而不是链接的二进制文件,因此来自其链接依赖项的目标文件不包含在库本身中(除了作为直接链接依赖项指定的 对象库 之外)。相反,CMake 记录静态库的链接依赖项,以便在链接使用者二进制文件时进行传递使用。
Apple 框架¶
共享库 和 静态库 可以使用 FRAMEWORK
目标属性标记,以创建 macOS 或 iOS 框架。具有 FRAMEWORK
目标属性的库还应设置 FRAMEWORK_VERSION
目标属性。根据 macOS 约定,此属性通常设置为值“A”。MACOSX_FRAMEWORK_IDENTIFIER
设置 CFBundleIdentifier
键,并且它唯一标识 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
)
模块库¶
模块库是通过将目标文件链接在一起而创建的二进制文件。与 共享库 不同,模块库不能被其他二进制文件链接为依赖项 -- 不要在 target_link_libraries()
命令的右侧命名它们。相反,模块库是应用程序可以在运行时按需动态加载的插件,例如,通过 dlopen
。
当使用 MODULE
库类型调用时,add_library()
命令定义一个模块库目标。
add_library(archivePlugin MODULE 7z.cpp)
CMake 生成构建规则以将源文件编译为目标文件,并将它们链接到模块库中。
模块库的链接依赖项可以使用 target_link_libraries()
命令指定。链接器首先处理从模块库自己的源文件编译的目标文件,然后通过搜索链接库来解析剩余的符号依赖关系。
对象库¶
对象库是通过编译源文件而创建的目标文件集合,没有任何归档或链接。目标文件可以在链接 可执行文件、 共享库 和 模块库 时使用,或者在归档 静态库 时使用。
当使用 OBJECT
库类型调用时,add_library()
命令定义一个对象库目标。
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
。但是,对象列表可以通过使用 $<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)
目标命令¶
特定于目标的命令填充 二进制目标 的 构建规范 和 二进制目标、 接口库 和 导入目标 的 使用要求。
调用必须指定作用域关键字,每个关键字都会影响其后参数的可见性。作用域是:
命令包括:
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
使用要求属性。它还支持指定 文件集,文件集可以添加 C++ 模块源文件和标头,这些源文件和标头未在
SOURCES
和INTERFACE_SOURCES
属性中列出。文件集还可以使用包含标头的包含目录填充INCLUDE_DIRECTORIES
构建规范和INTERFACE_INCLUDE_DIRECTORIES
使用要求属性。target_precompile_headers()
在 3.16 版本中添加。
填充
PRECOMPILE_HEADERS
构建规范和INTERFACE_PRECOMPILE_HEADERS
使用要求属性。target_link_libraries()
填充
LINK_LIBRARIES
构建规范和INTERFACE_LINK_LIBRARIES
使用要求属性。这是链接依赖项及其 使用要求 被传递传播以影响目标的编译和链接的主要机制。
target_link_directories()
在 3.13 版本中添加。
填充
LINK_DIRECTORIES
构建规范和INTERFACE_LINK_DIRECTORIES
使用要求属性。target_link_options()
在 3.13 版本中添加。
填充
LINK_OPTIONS
构建规范和INTERFACE_LINK_OPTIONS
使用要求属性。
目标构建规范¶
二进制目标 的构建规范由目标属性表示。对于以下每个 编译 和 链接 属性,目标的编译和链接既受其自身值的影响,也受相应的 使用要求 属性的影响,该属性以 INTERFACE_
前缀命名,并从链接依赖项的传递闭包中收集。
目标编译属性¶
这些代表编译目标的 构建规范。
COMPILE_DEFINITIONS
用于编译目标中源文件的编译定义列表。这些以未指定的顺序通过
-D
标志或等效标志传递给编译器。DEFINE_SYMBOL
目标属性也用作编译定义,作为SHARED
和MODULE
库目标的特殊便利情况。COMPILE_OPTIONS
用于编译目标中源文件的编译选项列表。这些以标志的形式按出现顺序传递给编译器。
编译选项会自动为 shell 转义。
某些编译选项最好通过专用设置来指定,例如
POSITION_INDEPENDENT_CODE
目标属性。COMPILE_FEATURES
在 3.1 版本中添加。
编译目标中源文件所需的
编译特性
列表。通常,这些确保目标的源文件使用足够的语言标准级别进行编译。INCLUDE_DIRECTORIES
用于编译目标中源文件的包含目录列表。这些以
-I
或-isystem
标志或等效标志,按出现顺序传递给编译器。为方便起见,可以启用
CMAKE_INCLUDE_CURRENT_DIR
变量,以将源目录和相应的构建目录作为所有目标的INCLUDE_DIRECTORIES
添加。SOURCES
与目标关联的源文件列表。这包括在通过
add_executable()
、add_library()
或add_custom_target()
命令创建目标时指定的源文件。它还包括通过target_sources()
命令添加的源文件,但不包括 文件集。PRECOMPILE_HEADERS
在 3.16 版本中添加。
在编译目标中的源文件时要预编译和包含的头文件列表。
AUTOMOC_MACRO_NAMES
在 3.10 版本中添加。
由
AUTOMOC
使用的宏名称列表,用于确定目标中的 C++ 源代码是否需要由moc
处理。AUTOUIC_OPTIONS
在版本 3.0 中添加。
由
AUTOUIC
在为目标调用uic
时使用的选项列表。
目标链接属性¶
这些代表用于链接目标的构建规范。
LINK_LIBRARIES
用于链接目标的链接库列表,如果目标是可执行文件、共享库或模块库。 静态库和共享库的条目通过其链接产物的路径,或使用
-l
标志或等效项传递给链接器。对象库的条目通过其目标文件的路径传递给链接器。此外,对于编译和链接目标本身,使用要求从命名 静态库、共享库、接口库、对象库和 导入目标 的
LINK_LIBRARIES
条目中传播,并在它们的INTERFACE_LINK_LIBRARIES
属性的传递闭包上收集。LINK_DIRECTORIES
在 3.13 版本中添加。
用于链接目标的链接目录列表,如果目标是可执行文件、共享库或模块库。目录通过
-L
标志或等效项传递给链接器。LINK_OPTIONS
在 3.13 版本中添加。
用于链接目标的链接选项列表,如果目标是可执行文件、共享库或模块库。选项作为标志按出现顺序传递给链接器。
链接选项会自动为 shell 转义。
LINK_DEPENDS
链接目标所依赖的文件列表,如果目标是可执行文件、共享库或模块库。例如,通过
LINK_OPTIONS
指定的链接器脚本可以在此处列出,以便更改它们会导致二进制文件重新链接。
目标使用要求¶
目标的使用要求是传播给消费者的设置,这些消费者通过 target_link_libraries()
链接到目标,以便正确地与其进行编译和链接。 它们由传递的编译和链接属性表示。
请注意,使用要求并非旨在使下游仅为了方便而使用特定的 COMPILE_OPTIONS
、COMPILE_DEFINITIONS
等。 属性的内容必须是要求,而不仅仅是建议。
有关在为重新分发创建软件包时指定使用要求时必须采取的额外注意事项,请参阅 cmake-packages(7)
手册的创建可重定位软件包 部分。
目标的使用要求可以传递地传播给依赖项。 target_link_libraries()
命令具有 PRIVATE
、INTERFACE
和 PUBLIC
关键字来控制传播。
add_library(archive archive.cpp)
target_compile_definitions(archive INTERFACE USING_ARCHIVE_LIB)
add_library(serialization serialization.cpp)
target_compile_definitions(serialization INTERFACE USING_SERIALIZATION_LIB)
add_library(archiveExtras extras.cpp)
target_link_libraries(archiveExtras PUBLIC archive)
target_link_libraries(archiveExtras PRIVATE serialization)
# archiveExtras is compiled with -DUSING_ARCHIVE_LIB
# and -DUSING_SERIALIZATION_LIB
add_executable(consumer consumer.cpp)
# consumer is compiled with -DUSING_ARCHIVE_LIB
target_link_libraries(consumer archiveExtras)
由于 archive
是 archiveExtras
的 PUBLIC
依赖项,因此它的使用要求也会传播到 consumer
。
由于 serialization
是 archiveExtras
的 PRIVATE
依赖项,因此它的使用要求不会传播到 consumer
。
通常,如果依赖项仅在库的实现中使用,而不是在头文件中使用,则应在使用 target_link_libraries()
时使用 PRIVATE
关键字指定。 如果依赖项还在库的头文件中使用(例如,用于类继承),则应将其指定为 PUBLIC
依赖项。 如果依赖项未在库的实现中使用,而仅在其头文件中使用,则应将其指定为 INTERFACE
依赖项。 可以使用每个关键字的多次使用来调用 target_link_libraries()
命令
target_link_libraries(archiveExtras
PUBLIC archive
PRIVATE serialization
)
使用要求通过从依赖项读取目标属性的 INTERFACE_
变体并将值附加到操作数的非 INTERFACE_
变体来传播。 例如,依赖项的 INTERFACE_INCLUDE_DIRECTORIES
被读取并附加到操作数的 INCLUDE_DIRECTORIES
。 如果顺序是相关的并且被维护,并且 target_link_libraries()
调用的顺序不允许正确编译,则使用适当的命令直接设置属性可能会更新顺序。
例如,如果目标的链接库必须按顺序 lib1
lib2
lib3
指定,但包含目录必须按顺序 lib3
lib1
lib2
指定
target_link_libraries(myExe lib1 lib2 lib3)
target_include_directories(myExe
PRIVATE $<TARGET_PROPERTY:lib3,INTERFACE_INCLUDE_DIRECTORIES>)
请注意,在为使用 install(EXPORT)
命令安装导出而指定目标的使用要求时,必须小心。 有关更多信息,请参阅 创建软件包。
传递编译属性¶
这些代表用于编译消费者的使用要求。
INTERFACE_COMPILE_DEFINITIONS
用于编译目标消费者的源代码的编译定义列表。 通常,这些由目标的头文件使用。
INTERFACE_COMPILE_OPTIONS
用于编译目标消费者的源代码的编译选项列表。
INTERFACE_COMPILE_FEATURES
在 3.1 版本中添加。
编译目标消费者源代码所需的
编译 特性
列表。 通常,这些确保在使用足够的语言标准级别编译消费者时处理目标的头文件。INTERFACE_INCLUDE_DIRECTORIES
用于编译目标消费者的源代码的包含目录列表。 通常,这些是目标头文件的位置。
INTERFACE_SYSTEM_INCLUDE_DIRECTORIES
当指定为包含目录时,例如通过
INCLUDE_DIRECTORIES
或INTERFACE_INCLUDE_DIRECTORIES
,应该在编译目标消费者中的源代码时被视为“系统”包含目录的目录列表。INTERFACE_SOURCES
要与目标消费者关联的源文件列表。
INTERFACE_PRECOMPILE_HEADERS
在 3.16 版本中添加。
在编译目标消费者中的源代码时要预编译和包含的头文件列表。
INTERFACE_AUTOMOC_MACRO_NAMES
在版本 3.27 中添加。
由
AUTOMOC
使用的宏名称列表,用于确定目标的消费者中的 C++ 源代码是否需要由moc
处理。INTERFACE_AUTOUIC_OPTIONS
在版本 3.0 中添加。
由
AUTOUIC
在为目标的消费者调用uic
时使用的选项列表。
传递链接属性¶
这些代表用于链接消费者的使用要求。
INTERFACE_LINK_LIBRARIES
用于链接目标消费者的链接库列表,对于那些是可执行文件、共享库或模块库的消费者。 这些是目标的传递依赖项。
此外,对于编译和链接目标的消费者,使用要求从命名 静态库、共享库、接口库、对象库和 导入目标 的
INTERFACE_LINK_LIBRARIES
条目的传递闭包中收集。INTERFACE_LINK_DIRECTORIES
在 3.13 版本中添加。
用于链接目标消费者的链接目录列表,对于那些是可执行文件、共享库或模块库的消费者。
INTERFACE_LINK_OPTIONS
在 3.13 版本中添加。
用于链接目标消费者的链接选项列表,对于那些是可执行文件、共享库或模块库的消费者。
INTERFACE_LINK_DEPENDS
在 3.13 版本中添加。
链接目标的消费者所依赖的文件列表,对于那些是可执行文件、共享库或模块库的消费者。
自定义传递属性¶
在版本 3.30 中添加。
TARGET_PROPERTY
生成器表达式评估上述构建规范和使用要求属性作为内置的传递属性。 它还支持由目标及其链接依赖项上的 TRANSITIVE_COMPILE_PROPERTIES
和 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
目标属性可以指定一个布尔值,指示目标是否应编译为位置无关代码,这具有平台特定的后果。 目标还可以指定使用要求 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
都将被编译为位置无关代码。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_CODE
与 exe1
目标的 POSITION_INDEPENDENT_CODE
属性“不兼容”。 该库要求消费者构建为位置无关代码,而可执行文件指定不构建为位置无关代码,因此会发出诊断信息。
lib1
和 lib2
的要求“不兼容”。 其中一个要求消费者构建为位置无关代码,而另一个要求消费者不构建为位置无关代码。 因为 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_BOOL
或 COMPATIBLE_INTERFACE_STRING
中列出的属性,调试输出显示哪个目标负责设置该属性,以及哪些其他依赖项也定义了该属性。 对于 COMPATIBLE_INTERFACE_NUMBER_MAX
和 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
。导出命令生成省略了 INSTALL_INTERFACE
或 BUILD_INTERFACE
的 IMPORTED
目标,并剥离了 *_INTERFACE
标记。使用 ClimbingStats
包的单独项目将包含:
find_package(ClimbingStats REQUIRED)
add_executable(Downstream main.cpp)
target_link_libraries(Downstream Upstream::ClimbingStats)
取决于 ClimbingStats
包是从构建位置还是安装位置使用,Downstream
目标将在编译时使用 -DClimbingStats_FROM_BUILD_LOCATION
或 -DClimbingStats_FROM_INSTALL_LOCATION
。有关包和导出的更多信息,请参阅 cmake-packages(7)
手册。
包含目录和使用要求¶
当包含目录被指定为使用要求并与生成器表达式一起使用时,需要进行一些特殊考虑。target_include_directories()
命令接受相对和绝对包含目录。
add_library(lib1 lib1.cpp)
target_include_directories(lib1 PRIVATE
/absolute/path
relative/path
)
相对路径相对于命令出现的源目录进行解释。在 INTERFACE_INCLUDE_DIRECTORIES
的 IMPORTED
目标中不允许使用相对路径。
在使用非平凡生成器表达式的情况下,可以在 INSTALL_INTERFACE
表达式的参数中使用 INSTALL_PREFIX
表达式。它是一个替换标记,当被消费项目导入时,它会扩展为安装前缀。
包含目录使用要求通常在构建树和安装树之间有所不同。 BUILD_INTERFACE
和 INSTALL_INTERFACE
生成器表达式可用于根据使用位置描述不同的使用要求。相对路径允许在 INSTALL_INTERFACE
表达式中使用,并相对于安装前缀进行解释。例如:
add_library(ClimbingStats climbingstats.cpp)
target_include_directories(ClimbingStats INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/generated>
$<INSTALL_INTERFACE:/absolute/path>
$<INSTALL_INTERFACE:relative/path>
$<INSTALL_INTERFACE:$<INSTALL_PREFIX>/$<CONFIG>/generated>
)
提供了两个与包含目录使用要求相关的便捷 API。CMAKE_INCLUDE_CURRENT_DIR_IN_INTERFACE
变量可以启用,其效果等同于:
set_property(TARGET tgt APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR};${CMAKE_CURRENT_BINARY_DIR}>
)
对于每个受影响的目标。安装目标的便利之处在于 install(TARGETS)
命令的 INCLUDES DESTINATION
组件:
install(TARGETS foo bar bat EXPORT tgts ${dest_args}
INCLUDES DESTINATION include
)
install(EXPORT tgts ${other_args})
install(FILES ${headers} DESTINATION include)
这等效于将 ${CMAKE_INSTALL_PREFIX}/include
附加到由 install(EXPORT)
生成的每个已安装的 IMPORTED
目标的 INTERFACE_INCLUDE_DIRECTORIES
。
当消费 导入目标 的 INTERFACE_INCLUDE_DIRECTORIES
时,属性中的条目可以被视为系统包含目录。其效果取决于工具链,但一个常见的效果是忽略在这些目录中找到的头文件的编译器警告。已安装目标的 SYSTEM
属性决定了此行为(有关如何修改目标的已安装值,请参阅 EXPORT_NO_SYSTEM
属性)。还可以通过在消费者上设置 NO_SYSTEM_FROM_IMPORTED
目标属性来更改消费者如何解释消费的导入目标的系统行为。
如果二进制目标传递链接到 macOS FRAMEWORK
,则框架的 Headers
目录也被视为使用要求。这具有与将框架目录作为包含目录传递相同的效果。
链接库和生成器表达式¶
与构建规范一样,可以使用生成器表达式条件指定 link libraries
。但是,由于使用要求的消费是基于从链接的依赖项收集的,因此还有一个额外的限制,即链接依赖项必须形成“有向无环图”。也就是说,如果链接到目标取决于目标属性的值,则该目标属性可能不依赖于链接的依赖项。
add_library(lib1 lib1.cpp)
add_library(lib2 lib2.cpp)
target_link_libraries(lib1 PUBLIC
$<$<TARGET_PROPERTY:POSITION_INDEPENDENT_CODE>:lib2>
)
add_library(lib3 lib3.cpp)
set_property(TARGET lib3 PROPERTY INTERFACE_POSITION_INDEPENDENT_CODE ON)
add_executable(exe1 exe1.cpp)
target_link_libraries(exe1 lib1 lib3)
由于 exe1
目标的 POSITION_INDEPENDENT_CODE
属性的值取决于链接的库 (lib3
),并且链接 exe1
的边由相同的 POSITION_INDEPENDENT_CODE
属性确定,因此上面的依赖关系图包含一个循环。cmake(1)
发出错误消息。
输出工件¶
由 add_library()
和 add_executable()
命令创建的构建系统目标会创建用于创建二进制输出的规则。二进制文件的确切输出位置只能在生成时确定,因为它可能取决于构建配置和链接语言的链接依赖项等。TARGET_FILE
、TARGET_LINKER_FILE
和相关表达式可用于访问生成的二进制文件的名称和位置。但是,这些表达式不适用于 OBJECT
库,因为此类库不会生成与这些表达式相关的单个文件。
目标可以构建三种类型的输出工件,详见以下部分。它们的分类在 DLL 平台和非 DLL 平台之间有所不同。包括 Cygwin 在内的所有基于 Windows 的系统都是 DLL 平台。
运行时输出工件¶
构建系统目标的运行时输出工件可能是:
由
add_executable()
命令创建的可执行目标的可执行文件(例如.exe
)。在 DLL 平台上:由
add_library()
命令使用SHARED
选项创建的共享库目标的可执行文件(例如.dll
)。
可以使用 RUNTIME_OUTPUT_DIRECTORY
和 RUNTIME_OUTPUT_NAME
目标属性来控制构建树中运行时输出工件的位置和名称。
库输出工件¶
构建系统目标的库输出工件可能是:
由
add_library()
命令使用MODULE
选项创建的模块库目标的可加载模块文件(例如.dll
或.so
)。在非 DLL 平台上:由
add_library()
命令使用SHARED
选项创建的共享库目标的共享库文件(例如.so
或.dylib
)。
可以使用 LIBRARY_OUTPUT_DIRECTORY
和 LIBRARY_OUTPUT_NAME
目标属性来控制构建树中库输出工件的位置和名称。
归档输出工件¶
构建系统目标的归档输出工件可能是:
由
add_library()
命令使用STATIC
选项创建的静态库目标的静态库文件(例如.lib
或.a
)。在 DLL 平台上:由
add_library()
命令使用SHARED
选项创建的共享库目标的导入库文件(例如.lib
)。仅当库导出至少一个非托管符号时,才能保证此文件存在。在 DLL 平台上:当
ENABLE_EXPORTS
目标属性设置时,由add_executable()
命令创建的可执行目标的导入库文件(例如.lib
)。在 AIX 上:当
ENABLE_EXPORTS
目标属性设置时,由add_executable()
命令创建的可执行目标的链接器导入文件(例如.imp
)。在 macOS 上:当
ENABLE_EXPORTS
目标属性设置时,由add_library()
命令使用SHARED
选项创建的共享库目标的链接器导入文件(例如.tbd
)。
可以使用 ARCHIVE_OUTPUT_DIRECTORY
和 ARCHIVE_OUTPUT_NAME
目标属性来控制构建树中归档输出工件的位置和名称。
目录作用域命令¶
target_include_directories()
、target_compile_definitions()
和 target_compile_options()
命令一次只对一个目标生效。add_compile_definitions()
、add_compile_options()
和 include_directories()
命令具有类似的功能,但为了方便起见,它们在目录作用域而不是目标作用域中操作。
构建配置¶
配置确定特定类型构建的规范,例如 Release
或 Debug
。指定此配置的方式取决于所使用的 generator
类型。对于单配置生成器,如 Makefile Generators 和 Ninja
,配置在配置时通过 CMAKE_BUILD_TYPE
变量指定。对于多配置生成器,如 Visual Studio、Xcode
和 Ninja Multi-Config
,配置由用户在构建时选择,并且 CMAKE_BUILD_TYPE
被忽略。在多配置情况下,可用配置的集合在配置时通过 CMAKE_CONFIGURATION_TYPES
变量指定,但实际使用的配置在构建阶段之前是未知的。这种差异经常被误解,导致出现以下有问题的代码:
# WARNING: This is wrong for multi-config generators because they don't use
# and typically don't even set CMAKE_BUILD_TYPE
string(TOLOWER ${CMAKE_BUILD_TYPE} build_type)
if (build_type STREQUAL debug)
target_compile_definitions(exe1 PRIVATE DEBUG_BUILD)
endif()
生成器表达式
应该用于正确处理特定于配置的逻辑,而不管使用哪个生成器。例如:
# Works correctly for both single and multi-config generators
target_compile_definitions(exe1 PRIVATE
$<$<CONFIG:Debug>:DEBUG_BUILD>
)
在存在 IMPORTED
目标的情况下,上述 $<CONFIG:Debug>
表达式也考虑了 MAP_IMPORTED_CONFIG_DEBUG
的内容。
大小写敏感性¶
CMAKE_BUILD_TYPE
和 CMAKE_CONFIGURATION_TYPES
就像其他变量一样,对它们的值进行的任何字符串比较都将是区分大小写的。$<CONFIG>
生成器表达式也保留了用户或 CMake 默认设置的配置的大小写。例如:
# NOTE: Don't use these patterns, they are for illustration purposes only.
set(CMAKE_BUILD_TYPE Debug)
if(CMAKE_BUILD_TYPE STREQUAL DEBUG)
# ... will never get here, "Debug" != "DEBUG"
endif()
add_custom_target(print_config ALL
# Prints "Config is Debug" in this single-config case
COMMAND ${CMAKE_COMMAND} -E echo "Config is $<CONFIG>"
VERBATIM
)
set(CMAKE_CONFIGURATION_TYPES Debug Release)
if(DEBUG IN_LIST CMAKE_CONFIGURATION_TYPES)
# ... will never get here, "Debug" != "DEBUG"
endif()
相反,当 CMake 在内部使用配置类型来修改基于配置的行为时,CMake 会不区分大小写地对待配置类型。例如,$<CONFIG:Debug>
生成器表达式对于配置不仅为 Debug
,而且还为 DEBUG
、debug
甚至 DeBuG
时,都将评估为 1。因此,您可以在 CMAKE_BUILD_TYPE
和 CMAKE_CONFIGURATION_TYPES
中使用大小写混合的形式指定配置类型,尽管存在强约定(请参阅下一节)。如果必须在字符串比较中测试该值,请始终先将该值转换为大写或小写,并相应地调整测试。
默认配置和自定义配置¶
默认情况下,CMake 定义了许多标准配置:
Debug
Release
RelWithDebInfo
MinSizeRel
在多配置生成器中,默认情况下 CMAKE_CONFIGURATION_TYPES
变量将填充(可能是上述列表的子集),除非项目或用户覆盖它。实际使用的配置由用户在构建时选择。
对于单配置生成器,配置在配置时使用 CMAKE_BUILD_TYPE
变量指定,并且在构建时无法更改。默认值通常不是上述任何标准配置,而是空字符串。一个常见的误解是这与 Debug
相同,但事实并非如此。用户应始终显式指定构建类型,以避免此常见问题。
上述标准配置类型在大多数平台上都提供了合理的行为,但可以对其进行扩展以提供其他类型。每个配置都为使用的语言定义了一组编译器和链接器标志变量。这些变量遵循约定 CMAKE_<LANG>_FLAGS_<CONFIG>
,其中 <CONFIG>
始终是大写配置名称。定义自定义配置类型时,请确保正确设置这些变量,通常作为缓存变量。
伪目标¶
某些目标类型不代表构建系统的输出,而仅代表输入,例如外部依赖项、别名或其他非构建工件。伪目标不显示在生成的构建系统中。
导入目标¶
IMPORTED
目标表示预先存在的依赖项。通常,此类目标由上游包定义,应被视为不可变的。声明 IMPORTED
目标后,可以像使用任何其他常规目标一样,使用常用的命令(例如 target_compile_definitions()
、target_include_directories()
、target_compile_options()
或 target_link_libraries()
)来调整其目标属性。
IMPORTED
目标可能具有与二进制目标相同的已填充使用要求属性,例如 INTERFACE_INCLUDE_DIRECTORIES
、INTERFACE_COMPILE_DEFINITIONS
、INTERFACE_COMPILE_OPTIONS
、INTERFACE_LINK_LIBRARIES
和 INTERFACE_POSITION_INDEPENDENT_CODE
。
也可以从 IMPORTED 目标读取 LOCATION
,尽管很少有理由这样做。诸如 add_custom_command()
之类的命令可以透明地使用 IMPORTED
EXECUTABLE
目标作为 COMMAND
可执行文件。
IMPORTED
目标的定义范围是定义它的目录。它可以从子目录访问和使用,但不能从父目录或同级目录访问和使用。范围类似于 cmake 变量的范围。
也可以定义 GLOBAL
IMPORTED
目标,该目标在构建系统中全局可访问。
有关使用 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
。
它可以指定使用要求,例如 INTERFACE_INCLUDE_DIRECTORIES
、 INTERFACE_COMPILE_DEFINITIONS
、 INTERFACE_COMPILE_OPTIONS
、 INTERFACE_LINK_LIBRARIES
、 INTERFACE_SOURCES
和 INTERFACE_POSITION_INDEPENDENT_CODE
。 只有 INTERFACE
模式的 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
。 安装目标目录自动成为使用者的使用要求中的包含目录。