翻译自cmake-buildsystem. 注意:翻译不一定准确,内容仅供参考!
介绍(Introduction)
一个基于CMake
的构建系统由一套高级的逻辑目标(high-level logical targets)组成,每个目标对应一个可执行文件或库,或者作为一个包含自定义命令的自定义目标。目标之间的依赖关系在构建系统中表示,以确定构建顺序和响应变化的再生成规则。
二进制目标(Binary Targets)
可执行文件和库通过使用add_executable()
和add_library()
命令来定义,产生的二进制文件有适合当前目标平台的前缀(prefixes)、后缀(suffixes)和扩展名(extensions)。二进制目标之间的依赖关系通过命令target_link_libraries()
来表示:1
2
3add_library(archive archive.cpp zip.cpp lzma.cpp)
add_executable(zipapp zipapp.cpp)
target_link_libraries(zipapp archive)
archive
被定义为一个静态库,包含由archive.cpp
、zip.cpp
和lzma.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
6add_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
3add_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()
的右端项使用,它们也可能不作为TARGET
在add_custom_command(TARGET)
命令签名中使用。它们可能会被安装,并作为一个INTERFACE
库导出。
虽然在调用target_link_libraries()
命令时目标库不会被直接命名,但是它们能够通过使用一个Interface Library
间接地被链接,但Interface Library
的INTERFACE_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_DIRECTORIES
、COMPILE_DEFINITIONS
和COMPILE_OPTIONS
目标属性,和/或INTERFACE_INCLUDE_DIRECTORIES
、INTERFACE_COMPILE_DEFINITIONS
和INTERFACE_COMPILE_OPTIONS
目标属性。
每个命令都有PRIVATE
、PUBLIC
和INTERFACE
三种模式。PRIVATE
模式只填入(populate)目标属性非INTERFACE_
变体(variant),INTERFACE
模式只填入INTERFACE_
变体,PUBLIC
模式同时填入各自目标属性的变体。每个命令可以通过多次使用这些关键词来调用:1
2
3
4target_compile_definitions(archive
PRIVATE BUILDING_WITH_LZMA
INTERFACE USING_ARCHIVE_LIB
)
注意:使用要求并非旨在使下游能够方便地使用特定的COMPILE_OPTIONS
或COMPILE_DEFINITIONS
等。 属性的内容必须是要求,而不仅仅是建议或方便。
有关在创建重新分发包时指定使用要求,参考cmake-packages(7))的Creating Relocatable Packages章节的内容。
目标属性(Target Properties)
编译二进制目标的源文件时,将正确使用INCLUDE_DIRECTORIES
,COMPILE_DEFINITIONS
和COMPILE_OPTIONS
目标属性的内容。
INCLUDE_DIRECTORIES
中的条目将使用-I
或-isystem
作为前缀并按照属性值出现的顺序添加到编译行。
COMPILE_DEFINITIONS
中的条目以-D
或/D
为前缀,并以未指定的顺序添加到编译行。 DEFINE_SYMBOL
目标属性也作为SHARED
和MODULE
库目标的特殊便利案例添加到编译定义。
COMPILE_OPTIONS
中的条目将针对shell
进行转义,并按属性值的出现顺序添加。有几个编译选项有特殊的单独处理,例如POSITION_INDEPENDENT_CODE
。
INTERFACE_INCLUDE_DIRECTORIES
,INTERFACE_COMPILE_DEFINITIONS
和INTERFACE_COMPILE_OPTIONS
目标属性的内容是使用要求 - 它们指定消费者必须使用的内容才能正确编译并链接到它们出现的目标。对于任何二进制目标,将使用target_link_libraries()
命令中指定的每个目标上的每个INTERFACE_
属性的内容:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15set(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()
命令具有PRIVATE
,INTERFACE
和PUBLIC
关键字来控制传播。1
2
3
4
5
6
7
8
9
10
11
12
13
14add_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
由于archive
是archiveExtras
的PUBLIC
依赖项,因此它的使用要求也会传递给consumer
。由于serialization
是archiveExtras
的PRIVATE
依赖关系,因此它的使用要求不会传递给consumer
。
通常,如果仅在库的实现使用了依赖项,而在头文件中没有使用,则应使用带有PRIVATE
关键字的target_link_libraries()
来指定依赖关系。如果在库的头文件中也使用了依赖项(例如类的继承),则应将其指定为PUBLIC
依赖项。如果依赖性没有在库的实现使用,只在库的头文件中使用,应该将其指定为INTERFACE
依赖项。在调用target_link_libraries()
命令时可以多次使用关键字:1
2
3
4target_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
3target_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
8add_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))发出诊断:
1 | add_library(lib1 SHARED lib1.cpp) |
lib1
要求INTERFACE_POSITION_INDEPENDENT_CODE
与exe1
目标的POSITION_INDEPENDENT_CODE
属性”不兼容”。该库要求将其消费者构建为与位置无关的代码,而可执行文件exe1
指定不构建为与位置无关的代码,因此发出诊断。
库lib1
和lib2
的要求不是“兼容的”。其中一个要求其消费者构建为与位置无关的代码,而另一个要求消费者不构建为与位置无关的代码。因为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
14add_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
14add_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
16add_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
8set(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
,调试输出显示每个依赖项的属性值,以及该值是否确定新的极值。
使用生成器表达式构建规范(Build Specification with Generator Expressions)
构建规范可以使用包含内容的generator expressions),该内容可以是有条件的或仅在生成时知道。 例如,可以使用TARGET_PROPERTY
表达式读取属性计算的“兼容”值:1
2
3
4
5
6
7
8
9
10
11
12add_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
3target_compile_definitions(exe1 PRIVATE
$<$<CONFIG:Debug>:DEBUG_BUILD>
)
CONFIG
参数在与正在构建的配置进行比较时不区分大小写。 在存在IMPORTED
目标的情况下,此表达式还会考虑MAP_IMPORTED_CONFIG_DEBUG
的内容。
由cmake(1))生成的一些构建系统在CMAKE_BUILD_TYPE
变量中具有预定的构建配置集。 诸如Visual Studio
和Xcode
之类的IDE
的构建系统是独立于构建配置生成的,并且在构建之前不知道实际的构建配置。 因此,代码如1
2
3
4string(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
14add_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
12add_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
包的单独项目将包含:1
2
3
4find_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
5add_library(lib1 lib1.cpp)
target_include_directories(lib1 PRIVATE
/absolute/path
relative/path
)
相对路径相对于命令出现的源目录进行解释。IMPORTED
目标的INTERFACE_INCLUDE_DIRECTORIES
中不允许相对路径。
在使用非平凡的生成器表达式的情况下,INSTALL_PREFIX
表达式可以在INSTALL_INTERFACE
表达式的参数内使用。 它是一个替换标记,在使用项目导入时会扩展为安装前缀。
包含目录使用要求通常在构建树和安装树之间有所不同。 BUILD_INTERFACE
和INSTALL_INTERFACE
生成器表达式可用于根据使用位置描述单独的使用要求。 INSTALL_INTERFACE
表达式中允许使用相对路径,并相对于安装前缀进行解释。 例如:1
2
3
4
5
6
7add_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
3set_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
5install(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 target的INTERFACE_INCLUDE_DIRECTORIES
时,属性中的条目将被视为SYSTEM
包含目录,就好像它们列在依赖项的INTERFACE_SYSTEM_INCLUDE_DIRECTORIES
中一样。 这可能导致省略在这些目录中找到的头文件的编译器警告。 可以通过在导入目标(Imported Targets)的使用者上设置NO_SYSTEM_FROM_IMPORTED
目标属性来控制导入目标的此行为。
如果二进制目标与macOS FRAMEWORK
传递链接,则框架的Headers
目录也会被视为使用要求。 这与将framework
目录作为包含目录传递具有相同的效果。
链接器和生成器表达式(Link Libraries and Generator Expressions)
与构建规范一样,可以使用生成表达式条件指定链接库(link libraries)。 但是,由于使用要求的消耗是基于链接依赖关系的收集,因此链接依赖关系必须形成“有向非循环图”作为另一个限制。 也就是说,如果链接到目标依赖于目标属性的值,那么该目标属性可能不依赖于链接的依赖项:1
2
3
4
5
6
7
8
9
10add_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_FILE
,TARGET_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_DIRECTORY
和RUNTIME_OUTPUT_NAME
目标属性可用于控制构建树中的运行时输出工件位置和名称。
库输出工件(Library Output Artifacts)
构建系统目标的库输出工件(library output artifact)可以是:
- 由
add_library()
命令和MODULE
选项创建的模块库目标的可加载模块文件(例如.dll
或.so
)。 - 在非DLL平台上:由
add_library()
命令和SHARED
选项创建的共享库目标的共享库文件(例如.so
或.dylib
)。LIBRARY_OUTPUT_DIRECTORY
和LIBRARY_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_DIRECTORY
和ARCHIVE_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_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 Targets)
ALIAS
目标是一个名称,可以在只读上下文中与二进制目标名称互换使用。 ALIAS
目标的主要用例是例如库附带的单元测试可执行文件,它可以是同一构建系统的一部分,也可以根据用户配置单独构建。
1 | add_library(lib1 lib1.cpp) |
在另一个目录中,我们可以无条件地链接到Upstream::lib1
目标,它可以是来自包的IMPORTED
目标,或者如果构建为同一构建系统的一部分则是ALIAS
目标。1
2
3
4
5if (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
4get_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_DIRECTORIES
,INTERFACE_COMPILE_DEFINITIONS
,INTERFACE_COMPILE_OPTIONS
,INTERFACE_LINK_LIBRARIES
,INTERFACE_SOURCES
和INTERFACE_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
8add_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
12add_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
16add_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
)