包向基于CMake的构建系统提供依赖性信息。使用find_package()
命令可以找到包。使用find_package()
的结果要么是一组IMPORTED目标,要么是与构建相关信息相对应的一组变量。
略…
配置文件包是上游提供给下游使用的一组文件。CMake在许多位置搜索包配置文件,如find_package()文档中所述。CMake用户告诉CMake(1)在非标准前缀中搜索包的最简单方法是设置CMAKE_PREFIX_PATH缓存变量。
配置文件包由上游供应商作为开发包的一部分提供,也就是说,它们与头文件和任何其他文件一起提供,以帮助下游供应商使用该包。
使用配置文件包时,也会自动设置一组提供包状态信息的变量。_FOUND变量设置为true或false,具体取决于是否找到包。_DIR缓存变量设置为包配置文件的位置。
查找模块是一个包含一组规则的文件,用于查找所需的依赖项,主要是头文件和库。通常,当上游不是用CMake构建的,或者CMake意识不足,无法提供包配置文件时,需要查找模块。与包配置文件不同,它不随上游一起提供,而是由下游使用,通过使用特定于平台的提示猜测文件的位置来查找文件。
与上游提供的包配置文件的情况不同,没有单个引用点将包标识为已找到,因此find_package()命令不会自动设置_FOUND变量。然而,它仍然可以按照惯例设置,并且应该由Find模块的作者设置。类似地,没有<PackageName>_DIR变量,但每个工件(如库位置和头文件位置)都提供了一个单独的缓存变量。
有关创建Find模块文件的更多信息,请参阅cmake开发者(7)手册。
配置文件包包括一个包配置文件和一个随项目分发提供的包版本文件(可选)。
考虑一个安装以下文件的项目Foo:
<prefix>/include/foo-1.2/foo.h
<prefix>/lib/foo-1.2/libfoo.a
它还可以提供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()命令文档中指定了完整的位置集。它看起来的一个地方是:
其中Foo*是不区分大小写的通配符表达式。在我们的示例中,globing表达式将匹配/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
and
<prefix>/lib/cmake/bar-4.2/BarConfig.cmake
<prefix>/lib/cmake/bar-4.2/BarConfigVersion.cmake
是每对包配置文件和相应的包版本文件。
find_package()命令加载版本文件时,首先设置以下变量:
PACKAGE_FIND_VERSION_COUNT
Number of version components, 0 to 4
版本文件必须使用这些变量来检查它是否与请求的版本兼容或完全匹配,并设置以下变量和结果:
版本文件加载在一个嵌套的范围内,因此它们可以自由设置它们希望作为计算的一部分的任何变量。当版本文件完成并检查了输出变量后,find_package命令将清除范围。当版本文件声称与请求的版本匹配时,find_package命令设置以下变量供项目使用:
变量报告实际找到的包的版本。其名称的部分与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()
以确定与所请求的版本的兼容性,并设置某些特定于版本的变量_version、_VISION_MAJOR、_VVISION_MINOR等时,CMake将读取该文件。安装(EXPORT)命令用于导出先前由安装(targets)命令定义的ClimbingStatsTargets导出集中的目标。此命令生成ClimbingStatsTargets.cmake
文件,其中包含适合下游使用的IMPORTED目标,并安排将其安装到lib/cmake/ClimbingStats。生成的ClimbingStatsConfigVersion.cmake
和cmake/ClimbingStatsConfig.cmake
安装在同一位置,完成了软件包。
生成的IMPORTED目标设置了适当的属性,以定义其使用要求,例如INTERFACE_INCUDE_DIRECTORIES、INTERFACE_COMPILE_DEFINITIONS和其他相关的内置INTERFACE属性。COMPATIBLE_INTERFACE_STRING和其他兼容接口属性中列出的用户定义属性的INTERFACE变量也会传播到生成的IMPORTED目标。在上述情况下,ClimbingStats_MAJOR_VERSION被定义为一个字符串,该字符串必须与任何依赖项的依赖项兼容。通过在此版本和下一版本的ClimbingStats中设置此自定义用户属性,如果试图将版本3与版本4一起使用,cmake(1)将发出诊断。如果软件包的不同主要版本设计为不兼容,则软件包可以选择使用此模式。
导出要安装的目标时,指定了带双冒号的NAMESPACE。当下游使用target_link_libraries()命令时,这种双冒号约定给CMake一个提示,即名称是IMPORTED目标。这样,如果尚未找到提供它的包,CMake可以发出诊断。
在这种情况下,当使用安装(目标)时,指定了包括目的地。这将导致IMPORTED目标使用CMAKE_INSTALL_PREFIX
中的INCLUDE目录填充其INTERFACE_ICLUDE_DIRECTORIES。当下游使用IMPORTED目标时,它会自动使用该属性中的条目。
在这种情况下,ClimbingStatsConfig。cmake文件可以简单如下:
include("${CMAKE_CURRENT_LIST_DIR}/ClimbingStatsTargets.cmake")
因为这允许下游使用IMPORTED目标。如果ClimbingStats包应提供任何宏,则它们应位于单独的文件中,该文件与Climbing StatsConfig安装在同一位置。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,并诊断没有Stats包就不能使用Climbing Stats包。
如果在下游使用find_package()时指定了COMPONENTS,则它们将列在
变量中。如果特定组件是非可选的,则
将为true。这可以通过包配置文件中的逻辑进行测试:
include(CMakeFindDependencyMacro)
find_dependency(Stats 2.6.4)
include("${CMAKE_CURRENT_LIST_DIR}/ClimbingStatsTargets.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/ClimbingStatsMacros.cmake")
set(_supported_components Plot Table)
foreach(_comp ${ClimbingStats_FIND_COMPONENTS})
if (NOT ";${_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设置为
可重新定位的包不得引用构建包的计算机上的文件的绝对路径,而这些文件在可能安装包的计算机上将不存在。
通过安装(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>
)
$
target_include_directories(tgt INTERFACE
# Ok, relocatable:
$<INSTALL_INTERFACE:$<$<CONFIG:Debug>:$<INSTALL_PREFIX>/include/TgtName>>
)
这也适用于引用外部依赖项的路径。不建议使用与依赖项相关的路径填充任何可能包含路径的属性,如INTERFACE_INCLUE_DIRECTORIES和INTERFACE_LINK_LIBRARIES。例如,此代码可能无法很好地用于可重新定位的包:
target_link_libraries(ClimbingStats INTERFACE
${Foo_LIBRARIES} ${Bar_LIBRARIES}
)
target_include_directories(ClimbingStats INTERFACE
"$"
)
引用的变量可能包含到库的绝对路径,并包括在生成包的计算机上找到的目录。这将创建一个包含到依赖项的硬编码路径且不适合重新定位的包。
理想情况下,这些依赖项应该通过其自己的IMPORTED目标来使用,这些目标具有自己的IMPARTED_LOCATION和适当填充的使用要求属性,如INTERFACE_INCUDE_DIRECTORIES。然后,这些导入的目标可以与用于ClimbingStats的target_link_libraries()命令一起使用:
target_link_libraries(ClimbingStats INTERFACE Foo::Foo Bar::Bar)
使用这种方法,包仅通过IMPORTED目标的名称引用其外部依赖项。当使用者使用已安装的包时,使用者将运行适当的find_package()命令(通过上述find_dependency宏)来查找依赖项,并在自己的计算机上用适当的路径填充导入的目标。
不幸的是,CMake附带的许多模块尚未提供IMPORTED目标,因为它们的开发早于此方法。随着时间的推移,这可能会逐渐改善。使用这些模块创建可重定位包的解决方法包括:
CMake提供了两个中心位置来注册在系统上任何地方构建或安装的软件包:
注册中心对于帮助项目在非标准安装位置或直接在自己的构建树中查找包特别有用。项目可以填充用户或系统注册表(使用自己的方法,见下文)以引用其位置。无论哪种情况,包都应在注册位置存储包配置文件(Config.cmake)和包版本文件(ConfigVersion.cmake)。
find_package()命令搜索两个包注册表,作为其文档中指定的两个搜索步骤。如果它有足够的权限,它还会删除指向不存在或不包含匹配的包配置文件的目录的过时包注册表项。
用户包注册表存储在每个用户的位置。export(PACKAGE)命令可用于在用户包注册表中注册项目构建树。CMake目前不提供将安装树添加到用户包注册表的接口。如果需要,必须手动教导安装人员注册他们的软件包。
在Windows上,用户包注册表存储在Windows注册表中的HKEY_CURRENT_user项下。
注册表项下可能会出现:
HKEY_CURRENT_USER\Software\Kitware\CMake\Packages\
作为具有任意名称的REG_SZ值,用于指定包含包配置文件的目录。
在UNIX平台上,用户包注册表存储在~/.cmake/packages下的用户主目录中。目录下可能会出现:
~/.cmake/packages/
作为具有任意名称的文件,其内容指定包含包配置文件的目录。
系统包注册表存储在系统范围内的位置。CMake目前不提供可添加到系统包注册表的接口。如果需要,必须手动教导安装人员注册他们的软件包。
在Windows上,系统包注册表存储在Windows注册表中的HKEY_LOCAL_MACHINE项下。注册表项下可能会出现:
HKEY_LOCAL_MACHINE\Software\Kitware\CMake\Packages\
作为具有任意名称的REG_SZ值,用于指定包含包配置文件的目录。
非Windows平台上没有系统包注册表。
在某些情况下,使用包注册表是不可取的。CMake允许使用以下变量禁用它们:
命名包注册表项的一个简单约定是使用内容哈希。它们是确定性的,不太可能发生冲突(导出(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
or
$ cat ~/.cmake/packages/MyPackage/7d1fb77e07ce59a81bed093bbee945bd
/home/me/work/lib/cmake/MyPackage
$ cat ~/.cmake/packages/MyPackage/f92c1db873a1937f3100706657c63e07
/home/me/work/MyPackage-build
then the CMakeLists.txt
code:
find_package(MyPackage)
将在注册位置搜索包配置文件(MyPackageConfig.cmake)。未指定单个包的包注册表项之间的搜索顺序,并且条目名称(本例中的哈希)没有任何意义。注册的位置可能包含包版本文件(MyPackageConfigVersion.cmake),以告知find_package()特定位置是否适合所请求的版本。
包注册表项由它们引用的项目安装单独拥有。包安装程序负责添加自己的条目,相应的卸载程序负责删除该条目。
export(PACKAGE)命令使用项目生成树的位置填充用户包注册表。构建树往往会被开发人员删除,并且没有可能触发删除其条目的“卸载”事件。为了保持注册表的清洁,如果find_package()命令有足够的权限,它会自动删除遇到的过时条目。一旦调用导出(PACKAGE),CMake不提供任何接口来删除引用现有构建树的条目。但是,如果项目从构建树中删除其包配置文件,则引用该位置的条目将被视为过时。