cmake-packages(7)

简介

包为基于 CMake 的构建系统提供依赖信息。包通过 find_package() 命令查找。使用 find_package() 的结果是一组 IMPORTED 目标或者与构建相关信息相对应的一组变量。

使用包

CMake 直接支持两种形式的包,配置文件包 以及 查找模块包。还通过 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)

或者作为一个单独的 COMPONENTS 列表

find_package(Qt5 5.1.0 COMPONENTS Widgets Xml Sql)

或作为单独的 OPTIONAL_COMPONENTS 列表

find_package(Qt5 5.1.0 COMPONENTS Widgets
                       OPTIONAL_COMPONENTS Xml Sql
)

由包定义 COMPONENTSOPTIONAL_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变量设置为真或假,具体取决于是否找到了该包。<PackageName>_DIR缓存变量设置为包配置文件的位置。

查找模块包

查找模块是具有针对依赖项的必需部分(主要是标头文件和库)的一组规则的文件。通常情况下,当上游未使用 CMake 构建或对 CMake 的了解不足以提供包配置文件时,需要使用查找模块。不同于包配置文件,查找模块不会与上游一起运送,而是由下游使用以猜测文件位置的方式查找文件(使用特定于平台的提示)。

与上游提供的包配置文件的情况不同,没有单一的参考点将包标识为已找到,因此<PackageName>_FOUND变量不会自动被find_package()命令设置。然而,仍然可以按照惯例对此进行设置,并且应由查找模块的作者进行设置。同样,没有<PackageName>_DIR变量,但每一个人工制品(例如库位置和标头文件位置)都提供一个单独的缓存变量。

更多有关创建 Find-module 文件的信息,请查看cmake-developer(7)手册。

包布局

config-file 包由包配置文件构成,项目分发时可选择提供包版本文件

包配置文件

考虑安装了下列文件的项目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.cmakefoo-config.cmake的文件。完全的位置集在find_package()命令文档中指定。它查找的一个位置是

<prefix>/lib/cmake/Foo*/

其中Foo*是不区分大小写的全局匹配表达式。在我们的示例中,全局匹配表达式将匹配<prefix>/lib/cmake/foo-1.2,并且包配置文件将被找到。

找到后,包配置文件将立即加载。它与一个包版本文件一起包含项目使用该包所需的所有信息。

包版本文件

find_package()命令找到候选包配置文件时,它在该文件旁边查找版本文件。加载版本文件是为了测试包版本是否是请求的版本的可接受匹配。如果版本文件声明兼容,则包配置文件将被接受。否则,它将被忽略。

包版本文件的名称必须与包配置文件的名称相匹配,但在.cmake扩展名前缀名为-versionVersion。例如,如下

<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

如果版本完全匹配,则为 True

PACKAGE_VERSION_COMPATIBLE

如果版本兼容,则为 True

PACKAGE_VERSION_UNSUITABLE

如果与任何版本都不适合,则为 True

版本文件以嵌套范围加载,因此它们可以在其计算过程中随意设置任何变量。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 文件。此文件设置包的版本。CMake 在调用 find_package() 以确定与请求的版本兼容性并设置一些特定于版本的变量 <PackageName>_VERSION<PackageName>_VERSION_MAJOR<PackageName>_VERSION_MINOR 等时读取此文件。使用 install(EXPORT) 命令导出 ClimbingStatsTargets 导出组中的目标,这些目标先前通过 install(TARGETS) 命令定义。此命令生成 ClimbingStatsTargets.cmake 文件,以包含适合下游使用并安排将其安装到 lib/cmake/ClimbingStatsIMPORTED 目标。生成的 ClimbingStatsConfigVersion.cmakecmake/ClimbingStatsConfig.cmake 将安装到同一位置,完成包创建。

已生成的 IMPORTED 目标具有用于定义其 使用要求(比如 INTERFACE_INCLUDE_DIRECTORIESINTERFACE_COMPILE_DEFINITIONS 及其他相关内置 INTERFACE_ 属性)的适当属性。在 COMPATIBLE_INTERFACE_STRING 中列出的用户自定义属性的 INTERFACE 变体以及其他 兼容界面属性 也将传播到生成的 IMPORTED 目标。在上例中,ClimbingStats_MAJOR_VERSION 被定义为必须与其依赖项的依赖项兼容的字符串。通过在该版本以及 ClimbingStats 的下一个版本中设置此用户自定义属性,cmake(1) 将在尝试将第 3 版与第 4 版一起使用时发出诊断信息。如果软件包的不同主要版本设计为不兼容,则软件包可以选择采用此类模式。

在导出供安装使用的目标时,将用双冒号指定 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 目标是 ClimbingStatsPUBLIC 依赖项,下游必须还找到 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,同时提供一项诊断,表明无法在没有 Stats 包的情况下使用 ClimbingStats 包。

如果在 find_package()

时下游指定了 COMPONENTS,它们将列在 <PackageName>_FIND_COMPONENTS 变量中。如果某个特定组件是非可选的,则 <PackageName>_FIND_REQUIRED_<comp> 将为 true。这可以使用包配置文件中的逻辑进行测试

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> 生成器 表达式 作为安装前缀的占位符,而不会导致包不可重新定位。如果使用了复杂的生成器表达式,这必不可少

target_include_directories(tgt INTERFACE
  # Ok, relocatable:
  $<INSTALL_INTERFACE:$<$<CONFIG:Debug>:$<INSTALL_PREFIX>/include/TgtName>>
)

这也适用于引用外部依赖项的路径。不建议填充可能包含路径的任何属性,例如 INTERFACE_INCLUDE_DIRECTORIESINTERFACE_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_LOCATION 和用法需求属性(例如填充适当的 INTERFACE_INCLUDE_DIRECTORIES)的 IMPORTED targets 来使用这类依赖项。然后,这些导入的目标可搭配 target_link_libraries() 命令用于 ClimbingStats

target_link_libraries(ClimbingStats INTERFACE Foo::Foo Bar::Bar)

采用此方式,软件包仅通过 IMPORTED targets 名称引用外部依赖项。当消费者使用已安装的软件包时,消费者将运行相应的 find_package() 命令(经由上文描述的 find_dependency 宏)来查找依赖项,并用适当的路径在自己的计算机上填充导入的目标。

遗憾的是,与 CMake 搭配使用的许多 modules 尚未提供 IMPORTED targets,因为其开发早于此方法。此问题可能随着时间的推移逐渐得到改善。使用此类 modules 创建可重定位 software 包的解决方法包括

  • 在构建软件包时,将每个 Foo_LIBRARY 缓存条目指定为仅仅库名称,例如 -DFoo_LIBRARY=foo。这种做法会告知相应的查找 modules,只需用 foo 填充 Foo_LIBRARIES,使链接器搜索库,而不是对路径进行硬编码。

  • 或者,在安装软件包内容但尚未为再分发创建软件包安装二进制文件之前,手动使用占位符替换绝对路径,供软件包安装时由安装工具代入。

软件包注册表

CMake 提供了两个集中位置,供任何系统中已构建或已安装的 software 包进行注册

注册表特别有助于项目在非标准安装位置或直接在其自己的构建树中查找 software 包。项目可填充用户或系统注册表(使用其自身的手段,见下文)来引用其位置。在这两种情况下,软件包都应将 软件包配置文件 (<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 允许使用以下变量禁用它们

包注册表示例

为包注册表条目命名的一种简单惯例是使用内容哈希。它们是确定的,并且不太可能发生冲突 (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 就会不提供任何可用于移除引用现有构建树的条目的界面。然而,如果项目从构建树中移除其包配置文件,那么引用该位置的条目将被视为过时的。