cmake 包(7)¶
介绍¶
包为基于 CMake 的构建系统提供依赖信息。使用 find_package() 命令找到包。使用 find_package() 的结果要么是一组 IMPORTED 目标,要么是一组与构建相关信息相对应的变量。
使用包¶
CMake 直接支持两种形式的包,Config-file Packages 和 Find-module Packages。还通过 FindPkgConfig 模块提供对 pkg-config 包的间接支持。在所有情况下, find_package() 调用的基本形式是相同的:
find_package(Qt4 4.7.0 REQUIRED) # CMake provides a Qt4 find-module
find_package(Qt5Core 5.1.0 REQUIRED) # Qt provides a Qt5 package config file.
find_package(LibXml2 REQUIRED) # Use pkg-config via the LibXml2 find-module
如果已知上游提供了包配置文件,并且只应使用该文件,则可以将 CONFIG 关键字传递给 find_package():
find_package(Qt5Core 5.1.0 CONFIG REQUIRED)
find_package(Qt5Gui 5.1.0 CONFIG)
同样,MODULE 关键字表示只使用一个查找模块:
find_package(Qt4 4.7.0 MODULE REQUIRED)
如果未找到,明确指定包的类型会改进向用户显示的错误消息。
这两种类型的包还支持在 REQUIRED 关键字之后指定包的组件:
find_package(Qt5 5.1.0 CONFIG REQUIRED Widgets Xml Sql)
或者作为单独的“组件”列表:
find_package(Qt5 5.1.0 COMPONENTS Widgets Xml Sql)
或者作为一个单独的``OPTIONAL_COMPONENTS`` 列表:
find_package(Qt5 5.1.0 COMPONENTS Widgets
OPTIONAL_COMPONENTS Xml Sql
)
COMPONENTS 和``OPTIONAL_COMPONENTS`` 的处理由包定义。
通过将 CMAKE_DISABLE_FIND_PACKAGE_<PackageName> 变量设置为 TRUE,将不会搜索 <PackageName> 包,并且将始终为 NOTFOUND。同样,将 CMAKE_REQUIRE_FIND_PACKAGE_<PackageName> 设置为 TRUE 将使包成为必需的。
配置文件包¶
配置文件包是上游提供给下游使用的一组文件。 CMake 在多个位置搜索包配置文件,如 find_package() 文档中所述。 CMake 用户告诉 cmake(1) 在非标准前缀中搜索包的最简单方法是设置 CMAKE_PREFIX_PATH 缓存变量。
配置文件包由上游供应商作为开发包的一部分提供,也就是说,它们与头文件和任何其他提供的文件一起提供,以帮助下游使用该包。
使用配置文件包时,也会自动设置一组提供包状态信息的变量。 <PackageName>_FOUND 变量设置为 true 或 false,具体取决于是否找到包。 <PackageName>_DIR 缓存变量设置为包配置文件的位置。
查找模块包¶
查找模块是一个包含一组规则的文件,用于查找所需的依赖项,主要是头文件和库。通常,当上游不是使用 CMake 构建的,或者 CMake 的意识不足以提供包配置文件时,需要查找模块。与包配置文件不同,它不随上游一起提供,但下游使用它通过使用特定于平台的提示猜测文件位置来查找文件。
与上游提供的包配置文件的情况不同,没有单个参考点将包标识为已找到,因此 <PackageName>_FOUND 变量不会由 find_package() 命令自动设置。然而,它仍然可以按约定设置,并且应该由 Find-module 的作者设置。类似地,没有 <PackageName>_DIR 变量,但是每个工件(例如库位置和头文件位置)都提供了一个单独的缓存变量。
有关创建查找模块文件的更多信息,请参阅 cmake-developer(7) 手册。
包装布局¶
配置文件包由项目分发提供的“包配置文件”和可选的“包版本文件”组成。
包配置文件¶
考虑一个安装以下文件的项目“Foo”:
<prefix>/include/foo-1.2/foo.h
<prefix>/lib/foo-1.2/libfoo.a
它还可能提供一个 CMake 包配置文件
<prefix>/lib/cmake/foo-1.2/FooConfig.cmake
内容定义 IMPORTED 目标,或定义变量,例如:
# ...
# (compute PREFIX relative to file location)
# ...
set(Foo_INCLUDE_DIRS ${PREFIX}/include/foo-1.2)
set(Foo_LIBRARIES ${PREFIX}/lib/foo-1.2/libfoo.a)
如果另一个项目希望使用 Foo,它只需要找到 FooConfig.cmake 文件并加载它以获取它需要的关于包内容位置的所有信息。由于包配置文件是由包安装提供的,它已经知道所有文件位置。
find_package() 命令可用于搜索包配置文件。此命令构造一组安装前缀并在多个位置的每个前缀下搜索。给定名称“Foo”,它会查找名为“FooConfig.cmake”或“foo-config.cmake”的文件。完整的位置集在 find_package() 命令文档中指定。它看起来的一个地方是:
<prefix>/lib/cmake/Foo*/
其中``Foo*`` 是不区分大小写的 globbing 表达式。在我们的示例中,globbing 表达式将匹配 <prefix>/lib/cmake/foo-1.2 并且将找到包配置文件。
一旦找到,就会立即加载一个包配置文件。它与包版本文件一起包含项目使用该包所需的所有信息。
包版本文件¶
当 find_package() 命令找到候选包配置文件时,它会在它旁边查找版本文件。加载版本文件以测试包版本是否与请求的版本匹配。如果版本文件声称兼容,则接受配置文件。否则将被忽略。
包版本文件的名称必须与包配置文件的名称相匹配,但在“.cmake”扩展名之前的名称后附加“-version”或“Version”。例如,文件
<prefix>/lib/cmake/foo-1.3/foo-config.cmake
<prefix>/lib/cmake/foo-1.3/foo-config-version.cmake
和::
<prefix>/lib/cmake/bar-4.2/BarConfig.cmake
<prefix>/lib/cmake/bar-4.2/BarConfigVersion.cmake
是每对包配置文件和相应的包版本文件。
当 find_package() 命令加载版本文件时,它首先设置以下变量:
PACKAGE_FIND_NAME<PackageName>PACKAGE_FIND_VERSION完整请求的版本字符串
PACKAGE_FIND_VERSION_MAJOR如果需要,则为主要版本,否则为 0
PACKAGE_FIND_VERSION_MINOR如果要求是次要版本,否则为 0
PACKAGE_FIND_VERSION_PATCH如果要求补丁版本,否则为 0
PACKAGE_FIND_VERSION_TWEAK如果需要调整版本,否则为 0
PACKAGE_FIND_VERSION_COUNT版本组件数,0 到 4
版本文件必须使用这些变量来检查它是否与请求的版本兼容或完全匹配,并使用结果设置以下变量:
PACKAGE_VERSION完整提供的版本字符串
PACKAGE_VERSION_EXACT如果版本完全匹配则为真
PACKAGE_VERSION_COMPATIBLE如果版本兼容则为真
PACKAGE_VERSION_UNSUITABLE如果不适合任何版本,则为真
版本文件在嵌套范围内加载,因此它们可以自由设置任何他们希望的变量作为计算的一部分。 find_package 命令在版本文件完成并检查输出变量时清除范围。当版本文件声称与请求的版本匹配时,find_package 命令会设置以下变量供项目使用:
<PackageName>_VERSION完整提供的版本字符串
<PackageName>_VERSION_MAJOR如果提供主要版本,否则为 0
<PackageName>_VERSION_MINOR如果提供次要版本,否则为 0
<PackageName>_VERSION_PATCH补丁版本(如果提供),否则为 0
<PackageName>_VERSION_TWEAK调整版本(如果提供),否则为 0
<PackageName>_VERSION_COUNT版本组件数,0 到 4
变量报告实际找到的包的版本。其名称的``<PackageName>`` 部分与提供给 find_package() 命令的参数相匹配。
创建包¶
通常,上游依赖于 CMake 本身,并且可以使用一些 CMake 工具来创建包文件。考虑一个提供单个共享库的上游:
project(UpstreamLib)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_INCLUDE_CURRENT_DIR_IN_INTERFACE ON)
set(Upstream_VERSION 3.4.1)
include(GenerateExportHeader)
add_library(ClimbingStats SHARED climbingstats.cpp)
generate_export_header(ClimbingStats)
set_property(TARGET ClimbingStats PROPERTY VERSION ${Upstream_VERSION})
set_property(TARGET ClimbingStats PROPERTY SOVERSION 3)
set_property(TARGET ClimbingStats PROPERTY
INTERFACE_ClimbingStats_MAJOR_VERSION 3)
set_property(TARGET ClimbingStats APPEND PROPERTY
COMPATIBLE_INTERFACE_STRING ClimbingStats_MAJOR_VERSION
)
install(TARGETS ClimbingStats EXPORT ClimbingStatsTargets
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin
INCLUDES DESTINATION include
)
install(
FILES
climbingstats.h
"${CMAKE_CURRENT_BINARY_DIR}/climbingstats_export.h"
DESTINATION
include
COMPONENT
Devel
)
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/ClimbingStats/ClimbingStatsConfigVersion.cmake"
VERSION ${Upstream_VERSION}
COMPATIBILITY AnyNewerVersion
)
export(EXPORT ClimbingStatsTargets
FILE "${CMAKE_CURRENT_BINARY_DIR}/ClimbingStats/ClimbingStatsTargets.cmake"
NAMESPACE Upstream::
)
configure_file(cmake/ClimbingStatsConfig.cmake
"${CMAKE_CURRENT_BINARY_DIR}/ClimbingStats/ClimbingStatsConfig.cmake"
COPYONLY
)
set(ConfigPackageLocation lib/cmake/ClimbingStats)
install(EXPORT ClimbingStatsTargets
FILE
ClimbingStatsTargets.cmake
NAMESPACE
Upstream::
DESTINATION
${ConfigPackageLocation}
)
install(
FILES
cmake/ClimbingStatsConfig.cmake
"${CMAKE_CURRENT_BINARY_DIR}/ClimbingStats/ClimbingStatsConfigVersion.cmake"
DESTINATION
${ConfigPackageLocation}
COMPONENT
Devel
)
CMakePackageConfigHelpers 模块提供了一个用于创建简单的 ConfigVersion.cmake 文件的宏。该文件设置包的版本。当 find_package() 被调用以确定与请求版本的兼容性,并设置一些特定于版本的变量``<PackageName>_VERSION``、<PackageName>_VERSION_MAJOR、` <PackageName>_VERSION_MINOR` 等。 install(EXPORT) 命令用于导出之前由 install(TARGETS) 定义的 ClimbingStatsTargets 导出集中的目标命令。此命令生成 ClimbingStatsTargets.cmake 文件以包含:prop_tgt:IMPORTED 目标,适合下游使用,并安排将其安装到 lib/cmake/ClimbingStats。生成的 ClimbingStatsConfigVersion.cmake 和 cmake/ClimbingStatsConfig.cmake 安装到相同的位置,完成包。
生成的 IMPORTED 目标具有适当的属性集来定义它们的 使用要求,例如 INTERFACE_INCLUDE_DIRECTORIES、 INTERFACE_COMPILE_DEFINITIONS 和其他相关的内置``INTERFACE_`` 属性。 COMPATIBLE_INTERFACE_STRING 和其他 Compatible Interface Properties 中列出的用户定义属性的 INTERFACE 变体也传播到生成的 IMPORTED 目标。在上述情况下,ClimbingStats_MAJOR_VERSION 被定义为一个字符串,它必须在任何依赖项的依赖项之间兼容。通过在此版本和下一版本的“ClimbingStats”中设置此自定义用户属性,如果尝试将版本 3 与版本 4 一起使用,cmake(1) 将发出诊断。如果包的不同主要版本被设计为不兼容,则可以选择采用这种模式。
导出安装目标时指定带有双冒号的 NAMESPACE。这种双冒号的约定给 CMake 一个提示,当它被下游使用 target_link_libraries() 命令时,该名称是一个 IMPORTED 目标。这样,如果尚未找到提供它的包,CMake 可以发出诊断。
在这种情况下,当使用 install(TARGETS) 时指定了``INCLUDES DESTINATION``。这会导致 IMPORTED 目标的 INTERFACE_INCLUDE_DIRECTORIES 填充 CMAKE_INSTALL_PREFIX 中的 include 目录。当下游使用 IMPORTED 目标时,它会自动使用该属性中的条目。
创建包配置文件¶
在这种情况下,ClimbingStatsConfig.cmake 文件可以很简单:
include("${CMAKE_CURRENT_LIST_DIR}/ClimbingStatsTargets.cmake")
因为这允许下游使用 IMPORTED 目标。如果“ClimbingStats”包应该提供任何宏,它们应该在一个单独的文件中,该文件安装到与“ClimbingStatsConfig.cmake”文件相同的位置,并从那里包含。
这也可以扩展以涵盖依赖项:
# ...
add_library(ClimbingStats SHARED climbingstats.cpp)
generate_export_header(ClimbingStats)
find_package(Stats 2.6.4 REQUIRED)
target_link_libraries(ClimbingStats PUBLIC Stats::Types)
由于``Stats::Types`` 目标是``ClimbingStats`` 的``PUBLIC`` 依赖项,下游还必须找到``Stats`` 包并链接到``Stats::Types`` 库. Stats 包应该在``ClimbingStatsConfig.cmake`` 文件中找到以确保这一点。来自 CMakeFindDependencyMacro 的 find_dependency 宏通过传播包是 REQUIRED 还是 QUIET 等来帮助解决这个问题。包的所有 REQUIRED 依赖项应该是在``Config.cmake`` 文件中找到:
include(CMakeFindDependencyMacro)
find_dependency(Stats 2.6.4)
include("${CMAKE_CURRENT_LIST_DIR}/ClimbingStatsTargets.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/ClimbingStatsMacros.cmake")
find_dependency 宏还会将``ClimbingStats_FOUND`` 设置为``False``(如果未找到依赖项),以及一个诊断,即``ClimbingStats`` 包在没有``Stats`` 的情况下无法使用包裹。
如果在下游使用 find_package() 时指定了 COMPONENTS,它们将列在``<PackageName>_FIND_COMPONENTS`` 变量中。如果特定组件是非可选的,则 <PackageName>_FIND_REQUIRED_<comp> 将为真。这可以用包配置文件中的逻辑进行测试:
include(CMakeFindDependencyMacro)
find_dependency(Stats 2.6.4)
include("${CMAKE_CURRENT_LIST_DIR}/ClimbingStatsTargets.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/ClimbingStatsMacros.cmake")
set(_ClimbingStats_supported_components Plot Table)
foreach(_comp ${ClimbingStats_FIND_COMPONENTS})
if (NOT ";${_ClimbingStats_supported_components};" MATCHES ";${_comp};")
set(ClimbingStats_FOUND False)
set(ClimbingStats_NOT_FOUND_MESSAGE "Unsupported component: ${_comp}")
endif()
include("${CMAKE_CURRENT_LIST_DIR}/ClimbingStats${_comp}Targets.cmake")
endforeach()
在这里,ClimbingStats_NOT_FOUND_MESSAGE 被设置为无法找到包的诊断,因为指定了无效的组件。这个消息变量可以在任何情况下设置 _FOUND` 变量设置为 False,并将显示给用户。
为构建树创建包配置文件¶
export(EXPORT) 命令创建一个 IMPORTED 目标定义文件,该文件特定于构建树,并且不可重定位。这可以类似地与合适的包配置文件和包版本文件一起使用,为构建树定义一个包,无需安装即可使用。构建树的消费者只需确保 CMAKE_PREFIX_PATH 包含构建目录,或在缓存中将``ClimbingStats_DIR`` 设置为``<build_dir>/ClimbingStats``。
创建可重定位包¶
可重定位包不得引用在构建包的机器上的文件的绝对路径,这些文件在可能安装包的机器上不存在。
由 install(EXPORT) 创建的包被设计为可重定位,使用相对于包本身位置的路径。在为 EXPORT 定义目标接口时,请记住,包含目录应指定为相对于 CMAKE_INSTALL_PREFIX 的相对路径:
target_include_directories(tgt INTERFACE
# Wrong, not relocatable:
$<INSTALL_INTERFACE:${CMAKE_INSTALL_PREFIX}/include/TgtName>
)
target_include_directories(tgt INTERFACE
# Ok, relocatable:
$<INSTALL_INTERFACE:include/TgtName>
)
$<INSTALL_PREFIX> generator expression 可以用作安装前缀的占位符,而不会产生不可重定位的包。如果使用复杂的生成器表达式,这是必需的:
target_include_directories(tgt INTERFACE
# Ok, relocatable:
$<INSTALL_INTERFACE:$<$<CONFIG:Debug>:$<INSTALL_PREFIX>/include/TgtName>>
)
这也适用于引用外部依赖项的路径。不建议使用与依赖项相关的路径填充任何可能包含路径的属性,例如:prop_tgt:INTERFACE_INCLUDE_DIRECTORIES 和 INTERFACE_LINK_LIBRARIES。例如,此代码可能不适用于可重定位包:
target_link_libraries(ClimbingStats INTERFACE
${Foo_LIBRARIES} ${Bar_LIBRARIES}
)
target_include_directories(ClimbingStats INTERFACE
"$<INSTALL_INTERFACE:${Foo_INCLUDE_DIRS};${Bar_INCLUDE_DIRS}>"
)
引用的变量可能包含库的绝对路径,并包含目录**在创建包的机器上找到的**。这将创建一个包,其中包含硬编码的依赖项路径并且不适合重定位。
理想情况下,此类依赖项应通过它们自己的 IMPORTED 目标 使用,这些目标具有自己的 IMPORTED_LOCATION 和使用要求属性,例如 INTERFACE_INCLUDE_DIRECTORIES` 适当填充。这些导入的目标然后可以与 target_link_libraries() 命令一起用于 ClimbingStats:
target_link_libraries(ClimbingStats INTERFACE Foo::Foo Bar::Bar)
使用这种方法,包仅通过 IMPORTED targets 的名称引用其外部依赖项。当消费者使用已安装的包时,消费者将运行适当的 find_package() 命令(通过上述的 find_dependency 宏)来查找依赖项并在自己的机器上使用适当的路径填充导入的目标。
不幸的是,CMake 附带的许多 modules 还没有提供 IMPORTED targets,因为它们的开发早于这种方法。随着时间的推移,这可能会逐渐改善。使用此类模块创建可重定位包的解决方法包括:
构建包时,将每个“Foo_LIBRARY”缓存条目指定为库名称,例如``-DFoo_LIBRARY=foo``。这告诉相应的查找模块只用
foo填充Foo_LIBRARIES以要求链接器搜索库而不是硬编码路径。或者,在安装包内容之后但在创建用于重新分发的包安装二进制文件之前,手动将绝对路径替换为占位符,以便在安装包时由安装工具替换。
包注册表¶
CMake 提供了两个中心位置来注册已构建或安装在系统任何位置的包:
这些注册表对于帮助项目在非标准安装位置或直接在它们自己的构建树中查找包特别有用。一个项目可以填充用户或系统注册表(使用它自己的方式,见下文)来引用它的位置。在任何一种情况下,包都应该在注册位置存储一个`包配置文件`_(<PackageName>Config.cmake)和可选的`包版本文件`_(<PackageName>ConfigVersion.cmake ).
find_package() 命令搜索两个包注册表作为其文档中指定的两个搜索步骤。如果它有足够的权限,它还会删除引用不存在或不包含匹配包配置文件的目录的陈旧包注册表项。
用户包注册表¶
用户包注册表存储在每个用户的位置。 export(PACKAGE) 命令可用于在用户包注册表中注册项目构建树。 CMake 目前没有提供接口来将安装树添加到用户包注册表。如果需要,必须手动教导安装者注册他们的包。
在 Windows 上,用户包注册表存储在 Windows 注册表中的“HKEY_CURRENT_USER”中的一个键下。
<PackageName> 可能会出现在注册表项下
HKEY_CURRENT_USER\Software\Kitware\CMake\Packages\<PackageName>
作为 REG_SZ 值,具有任意名称,指定包含包配置文件的目录。
在 UNIX 平台上,用户包注册表存储在 ~/.cmake/packages 下的用户主目录中。 <PackageName> 可能会出现在目录下:
~/.cmake/packages/<PackageName>
作为一个文件,具有任意名称,其内容指定包含包配置文件的目录。
系统包注册表¶
系统包注册表存储在系统范围内的位置。 CMake 当前不提供添加到系统包注册表的接口。如果需要,必须手动教导安装者注册他们的包。
在 Windows 上,系统包注册表存储在 Windows 注册表中的“HKEY_LOCAL_MACHINE”中的一个键下。 <PackageName> 可能会出现在注册表项下
HKEY_LOCAL_MACHINE\Software\Kitware\CMake\Packages\<PackageName>
作为 REG_SZ 值,具有任意名称,指定包含包配置文件的目录。
非 Windows 平台上没有系统包注册表。
禁用包注册表¶
在某些情况下,使用包注册表是不可取的。 CMake 允许使用以下变量禁用它们:
当 CMP0090 设置为 NEW 时,export(PACKAGE) 命令不会填充用户包注册表,除非 CMAKE_EXPORT_PACKAGE_REGISTRY 变量明确启用它。当 CMP0090 *未*设置为 NEW 时,export(PACKAGE) 将填充用户包注册表,除非 CMAKE_EXPORT_NO_PACKAGE_REGISTRY 变量明确禁用它。
CMAKE_FIND_USE_PACKAGE_REGISTRY在设置为FALSE时禁用所有find_package()调用中的用户包注册表。已弃用
CMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY在设置为TRUE时禁用所有find_package()调用中的用户包注册表。当CMAKE_FIND_USE_PACKAGE_REGISTRY已设置时,此变量将被忽略。CMAKE_FIND_PACKAGE_NO_SYSTEM_PACKAGE_REGISTRY在所有find_package()调用中禁用系统包注册表。
包注册表示例¶
命名包注册表项的一个简单约定是使用内容哈希。它们是确定性的并且不太可能发生冲突( export(PACKAGE) 使用这种方法)。引用特定目录的条目的名称只是目录路径本身的内容哈希。
如果项目安排包注册表条目存在,例如:
> reg query HKCU\Software\Kitware\CMake\Packages\MyPackage
HKEY_CURRENT_USER\Software\Kitware\CMake\Packages\MyPackage
45e7d55f13b87179bb12f907c8de6fc4 REG_SZ c:/Users/Me/Work/lib/cmake/MyPackage
7b4a9844f681c80ce93190d4e3185db9 REG_SZ c:/Users/Me/Work/MyPackage-build
或者::
$ cat ~/.cmake/packages/MyPackage/7d1fb77e07ce59a81bed093bbee945bd
/home/me/work/lib/cmake/MyPackage
$ cat ~/.cmake/packages/MyPackage/f92c1db873a1937f3100706657c63e07
/home/me/work/MyPackage-build
然后是 CMakeLists.txt 代码:
find_package(MyPackage)
将在注册位置搜索包配置文件(MyPackageConfig.cmake)。单个包的包注册表条目中的搜索顺序未指定,条目名称(本例中的散列)没有意义。注册位置可能包含包版本文件(MyPackageConfigVersion.cmake)来告诉 find_package() 特定位置是否适合所请求的版本。
包注册表所有权¶
包注册表项由它们引用的项目安装单独拥有。软件包安装程序负责添加自己的条目,相应的卸载程序负责删除它。
export(PACKAGE) 命令使用项目构建树的位置填充用户包注册表。构建树往往被开发人员删除,并且没有可能触发删除其条目的“卸载”事件。为了保持注册表的清洁,如果有足够的权限, find_package() 命令会自动删除它遇到的陈旧条目。一旦 export(PACKAGE) 被调用,CMake 就没有提供接口来删除引用现有构建树的条目。但是,如果项目从构建树中删除其包配置文件,则引用该位置的条目将被视为陈旧。