第十八章:目标类型

Posted by lili on

CMake支持各种各样的目标类型,不仅仅是在第4章“构建简单目标”中介绍的简单可执行文件和库。可以定义不同类型的目标,它们作为对其他实体的引用,而不是自己构建。它们可以用于收集传递属性和依赖关系,而不实际生成自己的二进制文件,或者它们甚至可以是一种库,只是一组对象文件的集合,而不是传统的静态库或共享库。许多东西可以抽象为目标,以隐藏平台差异、文件系统中的位置、文件名等复杂性。本章涵盖了所有这些不同的目标类型,并讨论了它们的用途。

另一类目标是实用程序或自定义目标。这些可以用于执行任意命令并定义自定义构建规则,允许项目实现几乎任何所需的行为。它们有自己的专用命令和独特的行为,并且将在下一章中深入讨论。

18.1. 可执行文件

add_executable()命令不仅仅是在第4章“构建简单目标”中介绍的形式。还有另外两种形式,可以用来定义引用其他内容的可执行目标。支持的完整形式集如下:

add_executable(targetName
 [WIN32] [MACOSX_BUNDLE]
 [EXCLUDE_FROM_ALL]
 source1 [source2 ...]
)
add_executable(targetName IMPORTED [GLOBAL])
add_executable(aliasName ALIAS targetName)

IMPORTED形式可以用于为现有可执行文件创建一个CMake目标,而不是项目构建的可执行文件。通过创建一个表示可执行文件的目标,项目的其他部分可以像对待项目自身构建的任何其他可执行目标一样对待它(有一些限制)。最显著的好处是,它可以在CMake自动将目标名称替换为磁盘上的位置的上下文中使用,例如执行测试或自定义任务的命令(后面章节会介绍)。与常规目标相比,仅有一些差异之一是导入的目标不能安装,这是在第27章“安装”中介绍的主题。

在定义导入的可执行目标时,需要在它有用之前设置某些目标属性。任何导入目标的大多数相关属性都以IMPORTED开头命名,但对于可执行文件,IMPORTED_LOCATION和IMPORTED_LOCATION_是最重要的。当需要导入可执行文件的位置时,CMake首先查看配置特定属性,只有在该属性未设置时才会查看更通用的IMPORTED_LOCATION属性。通常,位置不需要特定于配置,因此仅设置IMPORTED_LOCATION是非常常见的。

在不使用GLOBAL关键字的情况下定义导入目标时,导入目标只会在当前目录范围及以下可见,但是添加GLOBAL会使目标在任何地方可见。相比之下,由项目构建的常规可执行目标始终是全局的。关于这一点的原因以及减少目标可见性的相关含义在稍后的“提升导入目标”一节中进行了介绍。

ALIAS目标是在CMake中引用另一个目标的只读方式。它可以用于读取别名目标的属性,并且可以像别名目标一样在自定义命令和测试命令中使用(分别参见第19.1节“自定义目标”和第26.1节“定义和执行简单测试”)。别名不会创建一个以别名命名的新构建目标。定义和使用别名有一些限制:

  • 别名不能被安装或导出(两者在第27章“安装”中介绍)。
  • 不支持别名的别名。
  • 在CMake 3.11之前,根本无法为导入目标创建别名。
  • 从CMake 3.11开始,具有全局可见性的导入目标可以被别名。
  • 从CMake 3.18开始,可以创建非全局导入目标的别名,并且该别名也将是非全局的。即使导入的目标被提升(参见第18.3节“提升导入目标”),别名也不能后来被提升为全局可见性。

18.2. 库

add_library()命令也有多种不同形式。与可执行文件相比,库的细节更加复杂,这是由库在项目中可以扮演的各种角色所决定的。

18.2.1. 基本库类型

在第4章“构建简单目标”中介绍的基本形式可以用于定义大多数开发人员熟悉的常见类型的库:

add_library(targetName
 [STATIC | SHARED | MODULE]
 [EXCLUDE_FROM_ALL]
 source1 [source2 ...]
)

如果未给出STATIC、SHARED或MODULE关键字,则库将是STATIC或SHARED中的一个。选择取决于BUILD_SHARED_LIBS变量的值(参见第22.1节“构建基础知识”)。

18.2.2. 对象库

add_library()命令还可以用于定义对象库。这些是一组未组合成单个存档或共享库的对象文件:

add_library(targetName OBJECT
 [EXCLUDE_FROM_ALL]
 source1 [source2 ...]
)

在CMake 3.11或更早版本中,对象库不能像其他库类型一样被链接(即不能与target_link_libraries()一起使用)。它们需要使用生成器表达式形式$<TARGET_OBJECTS:objLib>作为另一个可执行文件或库目标的源列表的一部分。

因为它们不能被链接,所以它们不会向它们作为对象/源添加到的目标提供传递依赖项。这可能使它们比其他库类型不那么方便,因为需要手动将头文件搜索路径、编译器定义等传递到它们添加到的目标。

CMake 3.12引入了使对象库行为更像其他类型库的功能,但有一些注意事项。从CMake 3.12开始,对象库可以与target_link_libraries()一起使用,可以作为被添加到的目标(即命令的第一个参数)或作为要添加的库之一。但是,因为它们添加的是对象文件而不是实际的库,它们的传递性更受限制,以防止将对象文件多次添加到消耗目标中。简单的解释是,对象文件仅添加到直接链接到对象库的目标中,而不是在这之后进行传递。然而,对象库的使用要求会像普通库一样传递传递。

对象库自身链接库依赖项的传播最初包含一个实现错误,直到CMake 3.14.0才修复。如果项目打算链接到对象库,则应将其最低CMake版本设置为3.14或更高版本。

一些开发人员可能会发现对象库更自然,如果来自于非CMake项目的背景,非CMake项目根据源或对象文件定义其目标,而不是相关的一组静态库。然而,一般来说,在有选择的情况下,对于CMake项目,静态库通常是更方便的选择。在依赖于CMake 3.12及以后版本可用的扩展功能之前,请考虑普通静态库是否更合适,最终是否更容易使用。

18.2.3. 导入库

与可执行文件一样,库也可以被定义为导入目标。这些在打包期间创建的配置文件或Find模块实现(在第25章“查找内容”和第27章“安装”中介绍)中被广泛使用,但在这些上下文之外的用途有限。它们不定义项目构建的库,而是作为对外部提供的库的引用(例如,它已经存在于系统上,由当前CMake项目之外的某个进程构建,或者由配置文件所属的包提供)。

add_library(targetName
 (STATIC | SHARED | MODULE | OBJECT | UNKNOWN)
 IMPORTED [GLOBAL]
)

库类型必须紧跟在targetName之后给出。如果已知新目标将引用的库的类型,则应该指定该类型。这将使CMake能够在各种情况下将导入目标视为命名类型的常规库目标。类型只能在CMake 3.9或更高版本中设置为OBJECT(在该版本之前不支持导入的对象库)。如果不知道库的类型,则应给出UNKNOWN类型,在这种情况下,CMake将仅在诸如链接器命令行之类的地方使用库的完整路径而不进行进一步解释。这意味着更少的检查,并且在Windows构建的情况下,不处理DLL导入库。

除了OBJECT库之外,导入目标所代表的文件系统上的位置需要通过IMPORTED_LOCATION和/或IMPORTED_LOCATION_属性指定(即与导入的可执行文件相同)。在Windows平台的情况下,应设置两个属性:IMPORTED_LOCATION应该保存DLL的位置,IMPORTED_IMPLIB应该保存相关联的导入库的位置,通常具有.lib文件扩展名(这些属性的...变体也可以设置,并且将优先)。对于对象库,必须将IMPORTED_OBJECTS属性设置为导入目标代表的对象文件列表,而不是上述位置属性。

导入库还支持许多其他目标属性,其中大多数通常可以保持不变或由CMake自动设置。需要手动编写配置包的开发人员应参考CMake参考文档,以了解其他可能与其情况相关的IMPORTED…目标属性。大多数项目将依赖于CMake为其生成此类文件,因此这样做的需求应该相当罕见。

默认情况下,导入库被定义为局部目标,这意味着它们仅在当前目录范围及以下可见。可以给出GLOBAL关键字使它们具有全局可见性,就像其他常规目标一样。一个库可以最初在没有GLOBAL关键字的情况下创建,但稍后可以提升为全局可见性,这是在稍后的“提升导入目标”一节中详细介绍的话题。

# Windows-specific example of imported library
add_library(MyWindowsLib SHARED IMPORTED)
set_target_properties(MyWindowsLib PROPERTIES
 IMPORTED_LOCATION /some/path/bin/foo.dll
 IMPORTED_IMPLIB
 /some/path/lib/foo.lib
)
# Assume FOO_LIB holds the location of the library but its type is unknown
add_library(MysteryLib UNKNOWN IMPORTED)
set_target_properties(MysteryLib PROPERTIES
 IMPORTED_LOCATION ${FOO_LIB}
)

18.2.4. 接口库

add_library()命令的另一种形式允许定义接口库。这些通常不代表实际的库,而是主要用于收集用法要求和依赖关系,以应用于链接到它们的任何内容。一个常见的使用示例是用于头文件库,其中没有需要链接的实际库,但需要将头文件搜索路径、编译器定义等传递给使用头文件的任何内容。

add_library(targetName INTERFACE)

可以使用各种target_…()命令及其INTERFACE关键字来定义接口库将携带的用法要求。也可以直接使用set_property()或set_target_properties()设置相关的INTERFACE_…属性,但是target_…()命令更安全且更易于使用。

add_library(MyHeaderOnlyToolkit INTERFACE)
target_include_directories(MyHeaderOnlyToolkit
 INTERFACE /some/path/include
)
target_compile_definitions(MyHeaderOnlyToolkit
 INTERFACE COOL_FEATURE=1
 $<$<COMPILE_FEATURES:cxx_std_11>:HAVE_CXX11>
)
add_executable(MyApp ...)
target_link_libraries(MyApp PRIVATE MyHeaderOnlyToolkit)

在上述示例中,MyApp目标链接到MyHeaderOnlyToolkit接口库。编译MyApp源时,它们将具有/some/path/include作为头文件搜索路径,并且还将在编译器命令行上提供编译器定义COOL_FEATURE=1。如果使用了C++11支持来构建MyApp目标,则还将定义符号HAVE_CXX11。然后,MyHeaderOnlyToolkit中的头文件可以使用此符号来确定它们声明和定义的内容,而不是依赖于C++标准提供的__cplusplus符号,该符号的值经常对一系列编译器不可靠。

通常,接口库不会有任何源文件,但在某些情况下可能是有意义的。头文件库就是这样的一个例子。开发人员可能对头文件感兴趣,并且可能希望在IDE中看到头文件。由于头文件不是任何其他目标的一部分,因此它们通常不会出现。通过将它们添加到接口库作为源文件,IDE通常会有足够的信息来能够在一个或多个目标下显示头文件。

上述示例的一个特殊子情况是,当头文件库中的一个或多个头文件作为构建的一部分生成时(在第19.3节“生成文件的命令”中介绍了这个话题)。在CMake 3.18或更早版本中,如果项目实际上没有使用接口库,项目需要创建一个单独的自定义目标,以确保生成头文件。CMake 3.18及更早版本的另一个限制是,在add_library()调用中不能向接口库添加源文件。必须使用target_sources()的单独调用代替(请参见第15.2.6节“源文件”)。生成的代码将采用以下形式:

# Defines how to generate the header
add_custom_command(OUTPUT someHeader.h COMMAND ...)
# Required for CMake <= 3.18 to ensure header is generated
add_custom_target(GenerateSomeHeader ALL
 DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/someHeader.h
)
# CMake <= 3.18 doesn't allow sources to be added to an
# INTERFACE target directly in the add_library() call
add_library(MyHeaderOnly INTERFACE)
target_sources(MyHeaderOnly
 INTERFACE
 ${CMAKE_CURRENT_BINARY_DIR}/someHeader.h
)

从CMake 3.19开始,上述两个限制已经被删除。源文件可以直接在add_library()调用中列出,用于接口库,并且它们将被视为私有源。这意味着与上面的示例不同,头文件将不会被添加到链接到接口库的目标中。相反,它们将仅与接口库本身关联,并且因此只会在IDE中显示在该库下,而不是在其所有使用者中。

如果给接口库添加了源文件,CMake还将为接口库创建一个构建系统目标。像任何其他目标一样将该构建系统目标更新到最新,然后将确保生成其生成的源文件。最终的结果既更加简洁,又在IDE中产生更好的结果:

add_custom_command(OUTPUT someHeader.h COMMAND ...)
# Requires CMake 3.19 or later
add_library(MyHeaderOnly INTERFACE
 ${CMAKE_CURRENT_BINARY_DIR}/someHeader.h
)

接口库的另一个用途是为链接更大的一组库提供方便,可能封装了选择应该在集合中的哪些库的逻辑。例如:

# Regular library targets
add_library(Algo_Fast ...)
add_library(Algo_Accurate ...)
add_library(Algo_Beta ...)
# Convenience interface library
add_library(Algo_All INTERFACE)
target_link_libraries(Algo_All INTERFACE
 Algo_Fast
 Algo_Accurate
 $<$<BOOL:${ENABLE_ALGO_BETA}>:Algo_Beta>
)
# Other targets link to the interface library
# instead of each of the real libraries
add_executable(MyApp ...)
target_link_libraries(MyApp PRIVATE Algo_All)

上述代码将只在CMake选项变量ENABLE_ALGO_BETA为true时包含Algo_Beta在要链接的库列表中。然后,其他目标只需链接到Algo_All,而Algo_Beta的条件链接由接口库处理。这是使用接口库来抽象掉实际上将要链接、定义等细节的一个示例,以便链接到它们的目标不必为自己实现这些细节。这可以用于抽象化在不同平台上完全不同的库结构、基于某些条件(变量、生成器表达式等)切换库实现、在库结构已经重构(例如拆分为单独的库)的情况下提供旧的库目标名称等。

18.2.5. 接口导入库

add_library(targetName INTERFACE IMPORTED [GLOBAL])

虽然对于接口库的使用案例通常很容易理解,但添加IMPORTED关键字以产生INTERFACE IMPORTED库有时可能会引起混淆。这种组合通常出现在将INTERFACE库导出或安装供项目外部使用时。当被另一个项目使用时,它仍然起到INTERFACE库的作用,但IMPORTED部分被添加以表示库来自其他地方。其效果是将库的默认可见性限制为当前目录范围而不是全局范围。除了以下小的例外情况外,添加GLOBAL关键字以产生关键字组合INTERFACE IMPORTED GLOBAL的结果与仅INTERFACE相比几乎没有实际差异。

不需要(事实上不允许)为INTERFACE IMPORTED库设置IMPORTED_LOCATION。相反,如果需要,可以设置其IMPORTED_LIBNAME属性。IMPORTED_LIBNAME用于表示由工具链或平台提供的库,但其位置未知。IMPORTED_LIBNAME指定要包含在链接器命令行中的名称。不允许指定任何路径,只能是纯库名称。在第16.1节“链接要求目标”中包含了一个需要使用它的示例。

在CMake 3.11之前,不能使用target_…()命令在任何类型的IMPORTED库上设置INTERFACE_…属性。然而,可以使用set_property()或set_target_properties()设置这些属性。CMake 3.11取消了使用target_…()命令设置这些属性的限制,因此,虽然INTERFACE IMPORTED过去与普通的IMPORTED库非常相似,但在CMake 3.11中,它们在限制集合方面现在更接近普通的INTERFACE库。

以下表总结了各种关键字组合支持的内容:

*各种target_…()命令可以在CMake 3.11或更高版本中用于设置INTERFACE_…属性。set_property()或set_target_properties()命令可以在任何CMake版本中使用。

可以原谅人们认为不同的接口和导入库组合数量过于复杂和令人困惑。然而,对于大多数开发人员来说,导入目标通常在幕后为他们创建,并且它们看起来或多或少像普通目标。在上表中的所有组合中,通常只有普通的INTERFACE目标会由项目直接定义。第27章“安装”涵盖了其他组合的动机和机制的大部分内容。

18.2.6. 库别名

add_library()命令的最后一种形式是用于定义库别名:

add_library(aliasName ALIAS otherTarget)

库别名在很大程度上类似于可执行文件别名。它是一种只读方式引用另一个库,但不会创建一个新的构建目标。库别名不能被安装,也不能被定义为另一个别名的别名。在CMake 3.11之前,不能为导入目标创建库别名,但是随着CMake 3.11中针对导入目标的其他更改,这个限制被放宽了,现在可以为全局可见的导入目标创建别名。CMake 3.18进一步放宽了这个限制,允许为非全局的导入目标创建非全局别名。

有一个特别常见的库别名的用法与CMake 3.0引入的一个重要功能有关。对于每个将被安装或打包的库,一个常见的模式是创建一个匹配的库别名,其名称形式为projNamespace::targetName。项目中的所有这些别名通常会共享相同的projNamespace。例如:

# Any sort of real library (SHARED, STATIC, MODULE or possibly OBJECT)
add_library(MyRealThings SHARED src1.cpp ...)
add_library(OtherThings STATIC srcA.cpp ...)

# Aliases to the above with special names
add_library(BagOfBeans::MyRealThings ALIAS MyRealThings)
add_library(BagOfBeans::OtherThings ALIAS OtherThings)

在项目本身中,其他目标将链接到真实目标或命名空间目标(两者效果相同)。别名的动机来自于项目安装时,其他东西链接到由安装/打包的配置文件创建的导入目标。这些配置文件将使用命名空间名称定义导入库,而不是裸露的原始名称(请参见第27.3节“安装导出”)。消费项目将链接到命名空间名称。例如:

# Pull in imported targets from an installed package
find_package(BagOfBeans REQUIRED)# Define an executable that links to the imported library from the installed package
add_executable(EatLunch main.cpp ...)
target_link_libraries(EatLunch PRIVATE 
 BagOfBeans::MyRealThings
)

① The find_package() 会在26章讨论。

如果在某个时刻,上述项目想要直接将BagOfBeans项目并入其自身的构建中,而不是找到一个已安装的包,它可以这样做而不改变其链接关系,因为BagOfBeans项目提供了一个用于命名空间名称的别名:

# Add BagOfBeans directly to this project, making all of its targets directly available
add_subdirectory(BagOfBeans)
# Same definition of linking relationship still works
add_executable(EatLunch main.cpp ...)
target_link_libraries(EatLunch PRIVATE
 BagOfBeans::MyRealThings
)

另一个重要的方面是名称具有双冒号(::),CMake将始终将其视为别名或导入目标的名称。任何尝试使用这样的名称作为不同目标类型的名称将导致错误。然而,更有用的是,当目标名称用作target_link_library()调用的一部分时,如果CMake不知道该名称的目标,它将在生成时发出错误。与CMake将普通名称视为系统提供的库不同,如果它不知道该名称的目标,这可能导致错误直到构建时才显现出来。

add_executable(Main main.cpp)
add_library(Bar STATIC ...)
add_library(Foo::Bar ALIAS Bar)
# Typo in name being linked to, CMake will assume a library called "Bart"
# will be provided by the system at link time and won't issue an error.
target_link_libraries(Main PRIVATE Bart)
# Typo in name being linked to, CMake flags an error at generation time
# because a namespaced name must be a CMake target.
target_link_libraries(Main PRIVATE Foo::Bart)

因此,如果可能的话,使用命名空间名称更为可靠。强烈建议项目至少为所有打算安装/打包的目标定义命名空间别名。这种命名空间别名甚至可以在项目内部使用,而不仅仅是由其他项目作为预构建包或子项目使用。第27.3节“安装导出”还讨论了如何更改命名空间名称中targetName部分的名称,这样可以使原始目标(如MyProj_Algo)具有类似于MyProj::Algo的命名空间名称,而不是更冗长和重复的MyProj::MyProj_Algo。

18.3. 提升导入目标的可见性

当在没有GLOBAL关键字的情况下定义导入目标时,这些目标只能在创建它们或在其下级目录范围内可见。这种行为源自于它们的主要预期用途,即作为Find模块或包配置文件的一部分。通常情况下,由Find模块或包配置文件定义的任何内容都预期具有局部可见性,因此它们通常不应该添加全局可见的目标。这使得项目层次结构的不同部分可以使用不同的设置引入相同的包和模块,而不会相互干扰。

尽管如此,有些情况下需要创建具有全局可见性的导入目标,例如确保整个项目始终使用相同版本或实例的特定包。在创建导入库时添加GLOBAL关键字可以实现这一点,但项目可能无法控制执行创建的命令。为了为项目提供解决这种情况的方法,CMake 3.11引入了通过将目标的IMPORTED_GLOBAL属性设置为true来将导入目标提升为全局可见性的能力。请注意,这是一个单向转换,无法将全局目标降级为局部可见性。还要注意,只有在完全相同的范围内定义了导入目标,才能将其提升。在父级或子级范围内定义的导入目标无法提升。include()命令不会引入新的目录范围,find_package()调用也不会,因此通过这种方式引入构建的文件定义的导入目标可以被提升。事实上,这是提升导入目标能力的主要用例。

提升导入目标不会提升已指向该目标的任何别名。对于指向导入目标的别名,其可见性始终与创建别名时导入目标的可见性相同。别名不支持提升为全局可见性。

add_library(Original STATIC IMPORTED)
# Local alias (requires CMake 3.18 or later)
add_library(LocalAlias ALIAS Original)
# Promote imported target to global visibility,
# but LocalAlias remains with local visibility
set_target_properties(Original PROPERTIES
 IMPORTED_GLOBAL TRUE
)
# Global alias (requires CMake 3.11 or later)
add_library(GlobalAlias ALIAS Original)

实际上,几乎不需要对导入目标创建别名。INTERFACE IMPORTED库可以在很大程度上实现相同的功能,并且适用于更广泛的CMake版本。INTERFACE IMPORTED库不支持读取实际库目标的基础属性,但它们可以传递所有的链接和传递属性。

add_library(Original STATIC IMPORTED)
add_library(OtherName INTERFACE IMPORTED Original)
target_link_libraries(OtherName INTERFACE Original)

18.4. 推荐实践

CMake 3.0版本引入了一项重大变化,即项目管理依赖关系和目标之间需求的推荐方式发生了改变。不再通过变量来指定大部分内容,然后由项目手动管理,或者通过目录级别的命令来应用到目录及其以下的所有目标而没有太多区别,而是每个目标都具有在其自身属性中携带所有必要信息的能力。这种对目标为中心模型的关注转变还导致了一系列伪目标类型的出现,这些类型更灵活、更准确地表达了目标之间的关系。

开发人员应特别熟悉接口库。它们为捕获和表达关系提供了一系列技术手段,而无需创建或引用实际文件。它们可用于表示仅包含头文件的库、资源集合和许多其他场景,应该强烈推荐优先使用它们,而不是仅通过变量或目录级别命令来实现相同的结果。

一旦项目开始使用外部构建的软件包或引用通过Find模块找到的文件系统中的工具,就会经常遇到导入目标。开发人员应该熟悉使用导入目标,但通常不需要了解它们的所有细节,除非编写Find模块或为软件包手动创建配置文件。在第27章“安装”中讨论了一些特定情况,开发人员可能会遇到导入目标的某些限制,但这样的情况并不太常见。

许多较旧的CMake模块以前仅提供变量来引用导入实体。从CMake 3.0开始,这些模块逐渐更新,以在适当的情况下提供导入目标。对于项目需要引用外部工具或库的情况,如果可以的话,最好通过导入目标来实现。这些通常更好地抽象出诸如平台差异、选项依赖的工具选择等问题,但更重要的是,使用要求然后由CMake稳健地处理。如果可以选择在导入库和变量之间引用相同的内容,那么最好使用导入库。

优先定义静态库而不是对象库。静态库更简单,在较早的CMake版本中得到更完整和稳健的支持,并且大多数开发人员都很熟悉。对象库有它们的用途,但它们也比静态库不够灵活。特别是,在CMake 3.12之前根本无法链接对象库,而在CMake 3.14之前也不够稳健。没有这种链接,它们就不支持传递依赖关系,这迫使项目手动应用依赖关系。这增加了错误和遗漏的机会。它还降低了通常由库目标提供的封装性。甚至名称本身也可能会使开发人员感到困惑,因为对象库不是真正的库,而只是一组未组合的对象文件,但开发人员有时仍然期望它像真正的库一样运行。CMake 3.12的更改模糊了这种区别,但剩下的差异仍然存在令人意外的结果的空间,正如在CMake邮件列表和问题跟踪器中涉及对象库及其传递行为的查询数量所证明的那样。

避免使用过于通用的目标名称。全局可见的目标名称必须是唯一的,当在较大的层次结构中使用时,名称可能与其他项目的目标冲突。此外,考虑为每个不是私有于项目的目标(即可能最终被安装或打包的目标)添加别名namespace::… target。这样,使用者项目可以链接到命名空间化的目标名称,而不是真正的目标名称,这使得使用者项目可以相对轻松地在构建子项目自身或使用预构建的已安装项目之间进行切换。虽然这可能最初看起来是额外的工作,但在CMake社区中,特别是对于那些构建时间较长的项目,这已经成为了预期的标准实践。这种模式在第27.3节“安装导出项”和第31.1节“使用项目特定名称”中进一步讨论。

不可避免地,某些时候可能会希望重命名或重构一个库,但可能有外部项目期望现有的库目标可用于链接。在这些情况下,使用接口目标为重命名的目标提供一个旧名称,以便这些外部项目可以继续构建并在方便时更新。在拆分库时,定义一个使用旧目标名称的接口库,并使其定义链接依赖关系到新的拆分库。例如:

# Old library previously defined like this:
add_library(DeepCompute SHARED ...)

现在将DeepCompute更改为一个INTERFACE库,该库链接到新的重构库以保持向后兼容性:

# Now refactored into two separate libraries
add_library(ComputeAlgoA SHARED ...)
add_library(ComputeAlgoB SHARED ...)
# Forwarding interface library keeps old projects working
add_library(DeepCompute INTERFACE)
target_link_libraries(DeepCompute INTERFACE
 ComputeAlgoA
 ComputeAlgoB
)

INTERFACE IMPORTED库的另一个优点是,如果需要,它们可以提升为全局可见性,而别名则不能。