CMake构建系统

翻译自cmake-buildsystem. 注意:翻译不一定准确,内容仅供参考!

介绍(Introduction)

一个基于CMake的构建系统由一套高级的逻辑目标(high-level logical targets)组成,每个目标对应一个可执行文件或库,或者作为一个包含自定义命令的自定义目标。目标之间的依赖关系在构建系统中表示,以确定构建顺序和响应变化的再生成规则。

二进制目标(Binary Targets)

可执行文件和库通过使用add_executable()add_library()命令来定义,产生的二进制文件有适合当前目标平台的前缀(prefixes)、后缀(suffixes)和扩展名(extensions)。二进制目标之间的依赖关系通过命令target_link_libraries()来表示:

1
2
3
add_library(archive archive.cpp zip.cpp lzma.cpp)
add_executable(zipapp zipapp.cpp)
target_link_libraries(zipapp archive)

archive被定义为一个静态库,包含由archive.cppzip.cpplzma.cpp编译而来的目标。zipapp是一个可执行文件,由编译和链接zipapp.cpp构成。当链接zipapp可执行文件时,archive静态库即被链接。

二进制可执行文件(Binary Executables)

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

1
add_executable(mytool mytool.cpp)

有些命令,如在构建时生成运行时规则的add_custom_command()命令,可以显式地使用一个EXECUTABLE target作为COMMAND executable。构建系统的规则会保证在尝试运行命令前构建好可执行文件。

二进制库类型(Binary Library Types)

普通库(Normal Libraries)

默认情况下,add_library()命令定义一个静态库,除非有具体类型指明。类型可以通过下面的命令指定:

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

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

通过开启BUILD_SHARED_LIBS变量,能够改变add_library()的行为,使得默认构建共享库。

在构建系统定义的环境中,库是SHARED或者STATIC并没有什么关系,命令依赖和其他的API的工作方式一样,和库的类型无关。
MODULE库不一样,一般它不会被链接进去,它不会在target_link_libraries()命令的右侧使用。MODULE库通过运行时技术当作一个插件载入。如果一个库不导出任何非托管的符号(unmanaged symbols)(如Windows resource DLL, C++/CLI DLL),要求这个库不是SHARED库,因为CMake期待SHARED库至少导出一个符号。

1
add_library(archive MODULE 7z.cpp)

Apple框架(Apple Frameworks)

一个共享库可能会被标记为FRAMEWORK目标属性来创建一个OS X或者iOS的框架捆绑包(Framework Bundle),MACOSX_FRAMEWORK_IDENTIFIER设置CFBundleIdentifier键值来唯一地识别一个捆绑包。

1
2
3
4
5
6
add_library(MyFramework SHARED MyFramework.cpp)
set_target_properties(MyFramework PROPERTIES
FRAMEWORK TRUE
FRAMEWORK_VERSION A
MACOSX_FRAMEWORK_IDENTIFIER org.cmake.MyFramework
)

目标库(Object Libraries)

目标库同样不会被链接,它定义了从给定的源文件编译生成的一些未归档的目标文件(object files)。这些目标文件可以作为其他目标的源输入使用:

1
2
3
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)

OBJECT库可能不会在target_link_libraries()的右端项使用,它们也可能不作为TARGETadd_custom_command(TARGET)命令签名中使用。它们可能会被安装,并作为一个INTERFACE库导出。

虽然在调用target_link_libraries()命令时目标库不会被直接命名,但是它们能够通过使用一个Interface Library间接地被链接,但Interface LibraryINTERFACE_SOURCES

虽然在使用add_custom_command(TARGET)命令签名时目标库可能不会被当作TARGET使用,但使用$<TARGET_OBJECTS:objlib>可以让目标列表可以被add_custom_command(OUTPUT)file(GENERATE)使用。

构建规范和使用要求(Build Specification and Usage Requirements)

命令target_include_directories()target_compile_definitions()target_compile_options()指定二进制目标的构建规范和使用需求,这些命令分别填入(populate)INCLUDE_DIRECTORIESCOMPILE_DEFINITIONSCOMPILE_OPTIONS目标属性,和/或INTERFACE_INCLUDE_DIRECTORIESINTERFACE_COMPILE_DEFINITIONSINTERFACE_COMPILE_OPTIONS目标属性。

每个命令都有PRIVATEPUBLICINTERFACE三种模式。PRIVATE模式只填入(populate)目标属性非INTERFACE_变体(variant),INTERFACE模式只填入INTERFACE_变体,PUBLIC模式同时填入各自目标属性的变体。每个命令可以通过多次使用这些关键词来调用:

1
2
3
4
target_compile_definitions(archive
PRIVATE BUILDING_WITH_LZMA
INTERFACE USING_ARCHIVE_LIB
)

注意:使用要求并非旨在使下游能够方便地使用特定的COMPILE_OPTIONSCOMPILE_DEFINITIONS等。 属性的内容必须是要求,而不仅仅是建议或方便。

有关在创建重新分发包时指定使用要求,参考cmake-packages(7))的Creating Relocatable Packages章节的内容。

目标属性(Target Properties)

编译二进制目标的源文件时,将正确使用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_属性的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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,所以可以启用CMAKE_INCLUDE_CURRENT_DIR变量以方便地将相应的目录添加到所有目标的INCLUDE_DIRECTORIES。 可以启用变量CMAKE_INCLUDE_CURRENT_DIR_IN_INTERFACE以将相应的目录添加到所有目标的INTERFACE_INCLUDE_DIRECTORIES。 这样可以使target_link_libraries()命令方便地在多个不同目录中使用目标。

可传递使用要求(Transitive Usage Requirements)

目标的使用要求可以传递给它的依赖。 target_link_libraries()命令具有PRIVATEINTERFACEPUBLIC关键字来控制传播。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

由于archivearchiveExtrasPUBLIC依赖项,因此它的使用要求也会传递给consumer。由于serializationarchiveExtrasPRIVATE依赖关系,因此它的使用要求不会传递给consumer

通常,如果仅在库的实现使用了依赖项,而在头文件中没有使用,则应使用带有PRIVATE关键字的target_link_libraries()来指定依赖关系。如果在库的头文件中也使用了依赖项(例如类的继承),则应将其指定为PUBLIC依赖项。如果依赖性没有在库的实现使用,只在库的头文件中使用,应该将其指定为INTERFACE依赖项。在调用target_link_libraries()命令时可以多次使用关键字:

1
2
3
4
target_link_libraries(archiveExtras
PUBLIC archive
PRIVATE serialization
)

通过从依赖项中读取目标属性的INTERFACE_变体并将值附加到操作数的非INTERFACE_变体来传播使用要求。 例如,读取依赖项的INTERFACE_INCLUDE_DIRECTORIES并将其附加到操作数的INCLUDE_DIRECTORIES。 如果依赖项的顺序有关系且需要维护时,通过target_link_libraries()的调用产生的顺序无法正确编译,则使用适当的命令直接设置属性可以更新顺序。
例如,如果必须按lib1 lib2 lib3的顺序指定目标的链接库,但包含路径必须按lib3 lib1 lib2的顺序指定:

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

注意,在使用install(EXPORT)命令导出目标的使用要求以进行安装时必须小心,具体参阅Creating Packages

可兼容接口属性(Compatible Interface Properties)

某些目标属性需要在目标和每个依赖项的接口之间兼容。 例如,POSITION_INDEPENDENT_CODE目标属性可以指定一个布尔值来说明是否应将目标编译为位置无关代码(position-independent-code),该代码具有特定于平台的后果。 目标还可以指定使用要求INTERFACE_POSITION_INDEPENDENT_CODE以传达consumers必须编译为位置无关代码。

1
2
3
4
5
6
7
8
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))发出诊断:

1
2
3
4
5
6
7
8
9
10
11
12
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属性”不兼容”。该库要求将其消费者构建为与位置无关的代码,而可执行文件exe1指定不构建为与位置无关的代码,因此发出诊断。

lib1lib2的要求不是“兼容的”。其中一个要求其消费者构建为与位置无关的代码,而另一个要求消费者不构建为与位置无关的代码。因为exe2链接到两者并且它们存在冲突,所以会发出诊断。

要互相“兼容”,POSITION_INDEPENDENT_CODE属性如果被设置,必须与所有可传递依赖项的INTERFACE_POSITION_INDEPENDENT_CODE属性在
布尔意义上相同。

通过在COMPATIBLE_INTERFACE_BOOL目标属性的内容中指定属性,可以将“兼容接口要求”的属性扩展到其他属性。每个指定的属性必须在使用目标和相应的属性之间兼容,并且每个依赖项都有一个INTERFACE_前缀:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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属性中指定的属性必须是未指定的,或者与所有可传递指定的依赖项中的相同字符串进行比较。 这可以用于确保库的多个不兼容版本不会通过目标的传递要求一起被链接:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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目标属性指定将以数值方式评估内容,并计算所有指定的最大数量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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可用于从依赖项计算属性的数字最小值。
每个计算的“兼容”属性值可以在生成阶段使用生成器表达式被消费者读取。
请注意,对于每个依赖项,每个兼容接口属性中指定的属性集不得与任何其他属性中指定的集相交。

属性来源调试(Property Origin Debugging)

因为构建规范可以由依赖性来确定,所以缺少用于创建目标和代码以设置构建规范的代码的局部性,可能使代码更难以推理。 cmake(1))提供了一个调试工具来打印可能由依赖关系确定的属性内容的来源。 可以调试的属性列在CMAKE_DEBUG_TARGET_PROPERTIES变量文档中:

1
2
3
4
5
6
7
8
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,调试输出显示每个依赖项的属性值,以及该值是否确定新的极值。

使用生成器表达式构建规范(Build Specification with Generator Expressions)

构建规范可以使用包含内容的generator expressions),该内容可以是有条件的或仅在生成时知道。 例如,可以使用TARGET_PROPERTY表达式读取属性计算的“兼容”值:

1
2
3
4
5
6
7
8
9
10
11
12
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进行编译。

由配置决定的构建规范可以使用CONFIG生成器表达式方便地设置。

1
2
3
target_compile_definitions(exe1 PRIVATE
$<$<CONFIG:Debug>:DEBUG_BUILD>
)

CONFIG参数在与正在构建的配置进行比较时不区分大小写。 在存在IMPORTED目标的情况下,此表达式还会考虑MAP_IMPORTED_CONFIG_DEBUG的内容。

cmake(1))生成的一些构建系统在CMAKE_BUILD_TYPE变量中具有预定的构建配置集。 诸如Visual StudioXcode之类的IDE的构建系统是独立于构建配置生成的,并且在构建之前不知道实际的构建配置。 因此,代码如

1
2
3
4
string(TOLOWER ${CMAKE_BUILD_TYPE} _type)
if (_type STREQUAL debug)
target_compile_definitions(exe1 PRIVATE DEBUG_BUILD)
endif()

可能适用于Makefile生成器和Ninja生成器,但不能移植到IDE生成器。 此外,IMPORTED配置映射不会像这样的代码一起考虑,因此应该避免。

一元TARGET_PROPERTY生成器表达式和TARGET_POLICY生成器表达式在使用它的目标上下文中被计算,这意味着可以根据消费者不同地评估使用要求规范:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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进行编译,因为策略CMP0041在创建shared_lib目标时为NEW

BUILD_INTERFACE表达式包含仅被同一构建系统中的目标使用时的需求,或者使用export()命令导出到构建目录的目标使用时的需求。 INSTALL_INTERFACE表达式包含仅在使用install(EXPORT)命令安装和导出目标时才使用的需求:

1
2
3
4
5
6
7
8
9
10
11
12
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包的单独项目将包含:

1
2
3
4
find_package(ClimbingStats REQUIRED)

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

根据是从构建位置还是安装位置使用ClimbingStats包,可以使用-DClimbingStats_FROM_BUILD_LOCATION-DClimbingStats_FROM_INSTALL_LOCATION编译Downstream目标。 有关包和导出的更多信息,请参阅cmake-packages(7))手册。

包含目录和使用要求(Include Directories and Usage Requirements)

当包含目录指定为使用要求以及与生成器表达式一起使用时,包含目录需要特别考虑。 target_include_directories()命令接受相对和绝对包含目录:

1
2
3
4
5
add_library(lib1 lib1.cpp)
target_include_directories(lib1 PRIVATE
/absolute/path
relative/path
)

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

在使用非平凡的生成器表达式的情况下,INSTALL_PREFIX表达式可以在INSTALL_INTERFACE表达式的参数内使用。 它是一个替换标记,在使用项目导入时会扩展为安装前缀。

包含目录使用要求通常在构建树和安装树之间有所不同。 BUILD_INTERFACEINSTALL_INTERFACE生成器表达式可用于根据使用位置描述单独的使用要求。 INSTALL_INTERFACE表达式中允许使用相对路径,并相对于安装前缀进行解释。 例如:

1
2
3
4
5
6
7
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变量,对于每个影响的目标,其效果与下面等价:

1
2
3
set_property(TARGET tgt APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR};${CMAKE_CURRENT_BINARY_DIR}>
)

安装目标的便利性是带有install(TARGETS)命令的INCLUDES DESTINATION组件:

1
2
3
4
5
install(TARGETS foo bar bat EXPORT tgts ${dest_args}
INCLUDES DESTINATION include
)
install(EXPORT tgts ${other_args})
install(FILES ${headers} DESTINATION include)

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

当使用imported targetINTERFACE_INCLUDE_DIRECTORIES时,属性中的条目将被视为SYSTEM包含目录,就好像它们列在依赖项的INTERFACE_SYSTEM_INCLUDE_DIRECTORIES中一样。 这可能导致省略在这些目录中找到的头文件的编译器警告。 可以通过在导入目标(Imported Targets)的使用者上设置NO_SYSTEM_FROM_IMPORTED目标属性来控制导入目标的此行为。

如果二进制目标与macOS FRAMEWORK传递链接,则框架的Headers目录也会被视为使用要求。 这与将framework目录作为包含目录传递具有相同的效果。

与构建规范一样,可以使用生成表达式条件指定链接库(link libraries)。 但是,由于使用要求的消耗是基于链接依赖关系的收集,因此链接依赖关系必须形成“有向非循环图”作为另一个限制。 也就是说,如果链接到目标依赖于目标属性的值,那么该目标属性可能不依赖于链接的依赖项:

1
2
3
4
5
6
7
8
9
10
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))在这种情况下发出诊断。

输出工件(Output Artifacts)

add_library()add_executable()命令创建的构建系统目标生成创建二进制输出的规则。 二进制文件的确切输出位置只能在生成时确定,因为它依赖于构建配置和链接依赖项的链接语言等。TARGET_FILETARGET_LINKER_FILE和相关表达式可用于访问生成的二进制文件的名称和位置 。 但是,这些表达式不适用于OBJECT库,因为这些类不会生成与表达式相关的单个文件。

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

运行时输出工件(Runtime Output Artifacts)

构建系统目标的运行时输出工件(runtime output artifact)可以是:

  • add_executable()命令创建的可执行目标的可执行文件(例如.exe)。
  • 在DLL平台上:由add_library()命令和SHARED选项创建的共享库目标的可执行文件(例如.dll)。
    RUNTIME_OUTPUT_DIRECTORYRUNTIME_OUTPUT_NAME目标属性可用于控制构建树中的运行时输出工件位置和名称。

库输出工件(Library Output Artifacts)

构建系统目标的库输出工件(library output artifact)可以是:

  • add_library()命令和MODULE选项创建的模块库目标的可加载模块文件(例如.dll.so)。
  • 在非DLL平台上:由add_library()命令和SHARED选项创建的共享库目标的共享库文件(例如.so.dylib)。
    LIBRARY_OUTPUT_DIRECTORYLIBRARY_OUTPUT_NAME目标属性可用于控制构建树中的库输出工件位置和名称。

归档输出工件(Archive Output Artifacts)

构建系统目标的归档输出工件(archive output artifact)可以是:

  • add_library()命令和STATIC选项创建的静态库目标的静态库文件(例如.lib.a)。
  • 在DLL平台上:由add_library()命令和SHARED选项创建的共享库目标的导入库文件(例如.lib)。 该文件只在库导出至少一个非托管符号的情况下才保证存在。
  • 在DLL平台上:当设置了ENABLE_EXPORTS目标属性时,add_executable()命令创建的可执行目标的导入库文件(例如.lib)。
    ARCHIVE_OUTPUT_DIRECTORYARCHIVE_OUTPUT_NAME目标属性可用于控制构建树中的归档输出工件位置和名称。

目录范围命令(Directory-Scoped Commands)

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

伪目标(Pseudo Targets)

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

导入目标(Imported Targets)

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

LOCATION也可以从IMPORTED目标中读取,但很少有理由这样做。诸如add_custom_command()之类的命令可以透明地将IMPORTED EXECUTABLE目标用作COMMAND可执行文件。

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

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

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

别名目标(Alias Targets)

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

1
2
3
4
5
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目标。

1
2
3
4
5
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名称:

1
2
3
4
get_target_property(_aliased Upstream::lib1 ALIASED_TARGET)
if(_aliased)
message(STATUS "The name Upstream::lib1 is an ALIAS for ${_aliased}.")
endif()

接口库(Interface Libraries)

INTERFACE目标没有LOCATION并且是可变的,但在其他方面类似于IMPORTED目标。

它可以指定使用要求,例如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模式可以与INTERFACE库一起使用。

INTERFACE库的主要用例是只含头文件的库(header-only libraries)。

1
2
3
4
5
6
7
8
add_library(Eigen INTERFACE)
target_include_directories(Eigen INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>
$<INSTALL_INTERFACE:include/Eigen>
)

add_executable(exe1 exe1.cpp)
target_link_libraries(exe1 Eigen)

这里,Eigen目标的使用要求在编译时被消耗和使用,但它对链接没有影响。

另一个用例是针对使用要求采用完全以目标为中心的设计:

1
2
3
4
5
6
7
8
9
10
11
12
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库中设置或读取的属性包括:

  • INTERFACE_*匹配的属性
  • COMPATIBLE_INTERFACE_*匹配的内置属性
  • EXPORT_NAME
  • EXPORT_PROPERTIES
  • IMPORTED
  • MANUALLY_ADDED_DEPENDENCIES
  • NAME
  • IMPORTED_LIBNAME_*匹配的属性
  • MAP_IMPORTED_CONFIG_*匹配的属性
    INTERFACE库可以安装和导出。 他们引用的任何内容必须单独安装:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    add_library(Eigen INTERFACE)
    target_include_directories(Eigen INTERFACE
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>
    $<INSTALL_INTERFACE:include/Eigen>
    )

    install(TARGETS Eigen EXPORT eigenExport)
    install(EXPORT eigenExport NAMESPACE Upstream::
    DESTINATION lib/cmake/Eigen
    )
    install(FILES
    ${CMAKE_CURRENT_SOURCE_DIR}/src/eigen.h
    ${CMAKE_CURRENT_SOURCE_DIR}/src/vector.h
    ${CMAKE_CURRENT_SOURCE_DIR}/src/matrix.h
    DESTINATION include/Eigen
    )

参考(Reference)