cmake-developer(7)¶
简介¶
本手册旨在供使用 cmake-language(7) 代码的开发者参考,无论是编写自己的模块、撰写自己的构建系统,还是从事 CMake 本身的工作。
请参阅 https://cmake.com.cn/get-involved/ 以参与 CMake 上游的开发。其中包含指向贡献说明的链接,这些说明又指向 CMake 本身的开发者指南。
访问 Windows 注册表¶
CMake 提供了一些在 Windows 平台上访问注册表的工具。
查询 Windows 注册表¶
在 3.24 版本中添加。
命令 cmake_host_system_information() 提供了查询本地计算机注册表的可能性。有关更多信息,请参阅 cmake_host_system(QUERY_WINDOWS_REGISTRY)。
使用 Windows 注册表查找¶
在 3.24 版本中已更改。
find_file()、find_library()、find_path()、find_program() 和 find_package() 命令的 HINTS 和 PATHS 选项在 Windows 平台上提供了查询注册表的可能性。
使用包含扩展的 BNF 符号指定的注册表查询的正式语法如下
registry_query ::= '['sep_definition?root_key((key_separatorsub_key)? (value_separatorvalue_name_)?)? ']' sep_definition ::= '{'value_separator'}' root_key ::= 'HKLM' | 'HKEY_LOCAL_MACHINE' | 'HKCU' | 'HKEY_CURRENT_USER' | 'HKCR' | 'HKEY_CLASSES_ROOT' | 'HKCC' | 'HKEY_CURRENT_CONFIG' | 'HKU' | 'HKEY_USERS' sub_key ::=element(key_separatorelement)* key_separator ::= '/' | '\\' value_separator ::=element| ';' value_name ::=element| '(default)' element ::=character\+ character ::= <any character exceptkey_separatorandvalue_separator>
可选的 sep_definition 项提供了指定用于分隔 sub_key 和 value_name 项的字符串的可能性。如果未指定,则使用字符 ;。可以在路径的一部分中指定多个 registry_query 项。
# example using default separator
find_file(... PATHS "/root/[HKLM/Stuff;InstallDir]/lib[HKLM\\\\Stuff;Architecture]")
# example using different specified separators
find_library(... HINTS "/root/[{|}HKCU/Stuff|InstallDir]/lib[{@@}HKCU\\\\Stuff@@Architecture]")
如果未指定 value_name 项或其特殊名称为 (default),则将返回默认值的内容(如果存在)。value_name 支持的类型包括:
REG_SZ.REG_EXPAND_SZ。将展开返回的数据。REG_DWORD.REG_QWORD.
当注册表查询失败时,通常是因为键不存在或数据类型不受支持,字符串 /REGISTRY-NOTFOUND 将被替换为 [] 查询表达式。
查找模块¶
“查找模块”是 Find<PackageName>.cmake 文件,当调用 <PackageName> 时,由 find_package() 命令加载。
查找模块的主要任务是确定包是否可用,设置 <PackageName>_FOUND 变量以反映这一点,并提供使用该包所需的任何变量、宏和导入的目标。当上游库不提供 配置文件包 时,查找模块会很有用。
传统方法是使用变量来处理所有内容,包括库和可执行文件:请参阅下面的 标准变量名 部分。CMake 提供的现有查找模块大多如此。
更现代的方法是尽可能像 配置文件包 文件一样工作,通过提供 导入的目标。这具有将 使用要求 传播给消费者的优点。
无论哪种情况(甚至在同时提供变量和导入目标时),查找模块都应为旧版本提供向后兼容性。
FindFoo.cmake 模块通常会由以下命令加载:
find_package(Foo [major[.minor[.patch[.tweak]]]]
[EXACT] [QUIET] [REQUIRED]
[[COMPONENTS] [components...]]
[OPTIONAL_COMPONENTS components...]
[NO_POLICY_SCOPE])
请参阅 find_package() 文档,了解为查找模块设置的变量。其中大部分通过使用 FindPackageHandleStandardArgs 来处理。
简而言之,模块应仅查找与请求版本兼容的包版本,如 Foo_FIND_VERSION 系列变量所述。如果 Foo_FIND_QUIETLY 设置为 true,则应避免打印消息,包括任何抱怨找不到包的消息。如果 Foo_FIND_REQUIRED 设置为 true,则在找不到包时,模块应发出 FATAL_ERROR。如果两者均未设置为 true,则在找不到包时应打印非致命消息。
查找多个半独立部分的包(例如库包)时,如果设置了 Foo_FIND_COMPONENTS,则应搜索其中列出的组件,并且仅当搜索到的每个组件 <c> 未找到且 Foo_FIND_REQUIRED_<c> 未设置为 true 时,才将 Foo_FOUND 设置为 true。可以使用 find_package_handle_standard_args() 的 HANDLE_COMPONENTS 参数来实现这一点。
如果未设置 Foo_FIND_COMPONENTS,则搜索和必需的模块取决于查找模块,但应进行记录。
对于内部实现,普遍接受的约定是以下划线开头的变量仅用于临时用途。
标准变量名¶
对于采用设置变量方法(代替或补充创建导入的目标)的 FindXxx.cmake 模块,应使用以下变量名来保持查找模块之间的一致性。请注意,所有变量均以 Xxx_ 开头,(除非另有说明)这必须与 FindXxx.cmake 文件名完全匹配,包括大小写。变量名中的此前缀可确保它们不会与其他查找模块的变量发生冲突。对于查找模块定义的任何宏、函数和导入的目标,也应遵循相同的模式。
Xxx_INCLUDE_DIRS最终的包含目录集,在一个变量中列出供客户端代码使用。这不应是缓存条目(请注意,这意味着此变量不应用于
find_path()命令的结果变量 - 请参阅下面的Xxx_INCLUDE_DIR)。Xxx_LIBRARIES与模块一起使用的库。这些可以是 CMake 目标、指向库二进制文件的完整绝对路径,或链接器必须在搜索路径中找到的库名称。这不应是缓存条目(请注意,这意味着此变量不应用于
find_library()命令的结果变量 - 请参阅下面的Xxx_LIBRARY)。Xxx_DEFINITIONS编译使用该模块的代码时使用的编译定义。这实际上不应包含客户端源代码文件用于决定是否
#include <jpeg.h>的选项,例如-DHAS_JPEG。Xxx_EXECUTABLE可执行文件的完整绝对路径。在这种情况下,
Xxx可能不是模块的名称,而是工具的名称(通常转换为全大写),假设该工具具有如此广为人知的名称,以至于不太可能存在同名的其他工具。将其用作find_program()命令的结果变量是合适的。Xxx_YYY_EXECUTABLE与
Xxx_EXECUTABLE类似,但这里Xxx始终是模块名称,而YYY是工具名称(同样,通常是全大写)。如果工具名称不太为人所知或可能与其他工具冲突,则倾向于使用此形式。为了获得更大的一致性,如果模块提供多个可执行文件,也倾向于使用此形式。Xxx_LIBRARY_DIRS可选地,最终的库目录集,在一个变量中列出供客户端代码使用。这不应是缓存条目。
Xxx_ROOT_DIRXxx安装的基目录,可以由查找模块在找到Xxx时选择性地设置。这对于需要引用相对于公共基(或根)目录的许多文件的较大包很有用。不要与从外部为查找模块设置的Xxx_ROOT提示变量混淆,以便查找模块知道在哪里查找Xxx。Xxx_VERSION_VV此形式的变量指定提供的
Xxx模块是该模块的VV版本。对于给定的模块,不应设置多于一个此形式的变量为 true。例如,一个名为Barry的模块可能在多年来经历了许多不同的主版本。Barry模块的 3.0 版本可能将变量Barry_VERSION_3设置为 true,而该模块的旧版本可能将Barry_VERSION_2设置为 true。将Barry_VERSION_3和Barry_VERSION_2都设置为 true 是一个错误。Xxx_WRAP_YY当此形式的变量设置为 false 时,表示不应使用相关的包装命令。包装命令取决于模块,它可能由模块名称隐含,或者可能由变量的
YY部分指定。Xxx_Yy_FOUND对于此形式的变量,
Yy是模块的一个组件名称。它必须完全匹配可能传递给模块的find_package()命令的有效组件名称之一。如果此形式的变量设置为 false,则表示模块Xxx的Yy组件未找到或不可用。此形式的变量通常用于可选组件,以便调用者可以检查可选组件是否可用。Xxx_FOUND当
find_package()命令返回给调用者时,如果模块被认为已成功找到,则此变量将设置为 true。Xxx_NOT_FOUND_MESSAGE在
Xxx_FOUND设置为 FALSE 的情况下,应由配置文件设置。包含的消息将由find_package()命令和find_package_handle_standard_args()打印,以告知用户问题所在。使用此变量而不是直接调用message()来报告找不到模块或包的原因。Xxx_RUNTIME_LIBRARY_DIRS可选地,运行时库搜索路径,用于链接到共享库的可执行文件的运行时。用户代码应使用此列表来创建 Windows 上的
PATH或 UNIX 上的LD_LIBRARY_PATH。这不应是缓存条目。Xxx_VERSION找到的包的完整版本字符串(如果存在)。请注意,一些现有模块也可能提供
Xxx_VERSION_STRING,因为在当前命名约定之前,它传统上被使用。Xxx_VERSION_MAJOR找到的包的主版本(如果存在)。
Xxx_VERSION_MINOR找到的包的次版本(如果存在)。
Xxx_VERSION_PATCH找到的包的补丁版本(如果存在)。
以下名称通常不应在 CMakeLists.txt 文件中使用。它们旨在供查找模块用于指定和缓存特定文件或目录的位置。用户通常可以设置和编辑这些变量来控制查找模块的行为(例如手动输入库路径)。
Xxx_LIBRARY库的路径。仅当模块提供单个库时才使用此形式。将其用作
find_library()命令的结果变量是合适的。Xxx_Yy_LIBRARY模块
Xxx提供的库Yy的路径。当模块提供多个库或其他模块也可能提供同名库时,请使用此形式。将其用作find_library()命令的结果变量也是合适的。Xxx_INCLUDE_DIR当模块仅提供单个库时,此变量可用于指定查找使用该库的头文件的位置(或者更准确地说,消费者应将其添加到头文件搜索路径中的路径)。将其用作
find_path()命令的结果变量是合适的。Xxx_Yy_INCLUDE_DIR如果模块提供多个库,或者其他模块也可能提供同名库,则建议使用此形式来指定查找使用模块
Xxx提供的库Yy的头文件的位置。同样,将其用作find_path()命令的结果变量是合适的。
为避免用户被大量配置选项淹没,请尽量将尽可能多的选项排除在缓存之外,至少保留一个可以用于禁用模块使用或定位未找到库(例如 Xxx_ROOT_DIR)的选项。出于同样的原因,将大多数缓存选项标记为高级。对于同时提供调试和发布二进制文件的包,通常会创建带有 _LIBRARY_<CONFIG> 后缀的缓存变量,例如 Foo_LIBRARY_RELEASE 和 Foo_LIBRARY_DEBUG。 SelectLibraryConfigurations 模块可用于此类情况。
虽然这些是标准变量名,但对于替换提供了旧命名约定的变量的旧模块版本的查找模块,应提供向后兼容性。旧变量名应被记录为已弃用,以阻止进一步使用。
一个示例查找模块¶
我们将描述如何为库 Foo 创建一个简单的查找模块。
模块的开头应包含许可证通知,然后是一个空行,然后是一个 方括号注释。注释应以 .rst: 开头,以指示其其余内容为 reStructuredText 格式的文档。例如:
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file LICENSE.rst or https://cmake.com.cn/licensing for details.
#[=======================================================================[.rst:
FindFoo
-------
Finds the Foo library.
Imported Targets
^^^^^^^^^^^^^^^^
This module provides the following imported targets, if found:
``Foo::Foo``
The Foo library
Result Variables
^^^^^^^^^^^^^^^^
This module defines the following variables:
``Foo_FOUND``
Boolean indicating whether (the requested version of) Foo was found.
``Foo_VERSION``
The version of the Foo library which was found.
``Foo_INCLUDE_DIRS``
Include directories needed to use Foo.
``Foo_LIBRARIES``
Libraries needed to link to Foo.
Cache Variables
^^^^^^^^^^^^^^^
The following cache variables may also be set:
``Foo_INCLUDE_DIR``
The directory containing ``foo.h``.
``Foo_LIBRARY``
The path to the Foo library.
#]=======================================================================]
模块文档包括:
一个下划线标题,指定模块名称。
一个简单的描述,说明模块查找的内容。某些包可能需要更多描述。如果存在用户应注意的注意事项或其他详细信息,请在此处说明。
一个列出模块提供的导入目标的节(如果存在)。
一个列出模块提供的结果变量的节。
可选地,一个列出模块使用的缓存变量的节(如果存在)。
如果包提供了任何宏或函数,它们应列在附加节中,但可以通过紧邻这些宏或函数定义块上方的其他 .rst: 注释块进行文档化。
查找模块实现可以放在文档块下方。现在必须实际找到库等。这里的代码显然会因模块而异(毕竟,处理这些是查找模块的要点),但对于库而言,通常存在一个通用模式。
首先,我们尝试使用 pkg-config 来查找库。请注意,我们不能依赖它,因为它可能不可用,但它提供了一个很好的起点。
find_package(PkgConfig)
if(PkgConfig_FOUND)
pkg_check_modules(PC_Foo QUIET Foo)
endif()
这应该定义一些以 PC_Foo_ 开头的变量,其中包含来自 Foo.pc 文件的信息。
现在我们需要找到库和包含文件;我们使用来自 pkg-config 的信息来向 CMake 提供有关在哪里查找的提示,然后再检查其他默认路径。
find_path(Foo_INCLUDE_DIR
NAMES foo.h
HINTS ${PC_Foo_INCLUDE_DIRS}
PATH_SUFFIXES Foo
)
find_library(Foo_LIBRARY
NAMES foo
HINTS ${PC_Foo_LIBRARY_DIRS}
)
或者,如果库具有多种配置,则可以使用 SelectLibraryConfigurations 模块来自动设置 Foo_LIBRARY 变量,而不是
find_library(Foo_LIBRARY_RELEASE
NAMES foo
HINTS ${PC_Foo_LIBRARY_DIRS}/Release
)
find_library(Foo_LIBRARY_DEBUG
NAMES foo
HINTS ${PC_Foo_LIBRARY_DIRS}/Debug
)
include(SelectLibraryConfigurations)
select_library_configurations(Foo)
如果有一种可靠的方法可以获取版本信息(例如,从头文件中),则该信息可用于设置 Foo_VERSION。否则,尝试使用来自 pkg-config 的信息。
set(Foo_VERSION ${PC_Foo_VERSION})
现在我们可以使用 FindPackageHandleStandardArgs 模块来处理剩余的大部分步骤
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Foo
REQUIRED_VARS
Foo_LIBRARY
Foo_INCLUDE_DIR
VERSION_VAR Foo_VERSION
)
这将检查 REQUIRED_VARS 是否包含值(不以 -NOTFOUND 结尾),并相应地设置 Foo_FOUND。它还将缓存这些值。如果设置了 Foo_VERSION,并且向 find_package() 传递了必需的版本,它将把请求的版本与 Foo_VERSION 中的版本进行比较。它还将酌情打印消息;请注意,如果找到了包,它将打印第一个必需变量的内容以指示找到的位置。
此时,我们必须提供一种方式供查找模块的用户链接到已找到的库。有两种方法,如上面 查找模块 部分所述。传统的变量方法如下所示:
if(Foo_FOUND)
set(Foo_LIBRARIES ${Foo_LIBRARY})
set(Foo_INCLUDE_DIRS ${Foo_INCLUDE_DIR})
set(Foo_DEFINITIONS ${PC_Foo_CFLAGS_OTHER})
endif()
如果找到了多个库,则所有库都应包含在这些变量中(有关更多信息,请参阅 标准变量名 部分)。
提供导入目标时,应对其进行命名空间化(因此有 Foo:: 前缀);CMake 将识别传递给 target_link_libraries() 的包含 :: 的值是导入目标(而不是仅库名),并且如果目标不存在,它将产生适当的诊断消息(请参阅策略 CMP0028)。
if(Foo_FOUND AND NOT TARGET Foo::Foo)
add_library(Foo::Foo UNKNOWN IMPORTED)
set_target_properties(Foo::Foo PROPERTIES
IMPORTED_LOCATION "${Foo_LIBRARY}"
INTERFACE_COMPILE_OPTIONS "${PC_Foo_CFLAGS_OTHER}"
INTERFACE_INCLUDE_DIRECTORIES "${Foo_INCLUDE_DIR}"
)
endif()
关于这一点需要注意的一点是,INTERFACE_INCLUDE_DIRECTORIES 和类似属性应仅包含有关目标本身的信息,而不包含其任何依赖项。相反,这些依赖项也应是目标,并且 CMake 应被告知它们是此目标的依赖项。然后 CMake 将自动组合所有必要的信息。
add_library() 命令创建的 IMPORTED 目标的类型可以始终指定为 UNKNOWN 类型。这简化了代码,因为如果找到静态或共享变体,CMake 将通过检查文件来确定类型。
如果库具有多种配置,还应填充 IMPORTED_CONFIGURATIONS 目标属性。
if(Foo_FOUND)
if (NOT TARGET Foo::Foo)
add_library(Foo::Foo UNKNOWN IMPORTED)
endif()
if (Foo_LIBRARY_RELEASE)
set_property(TARGET Foo::Foo APPEND PROPERTY
IMPORTED_CONFIGURATIONS RELEASE
)
set_target_properties(Foo::Foo PROPERTIES
IMPORTED_LOCATION_RELEASE "${Foo_LIBRARY_RELEASE}"
)
endif()
if (Foo_LIBRARY_DEBUG)
set_property(TARGET Foo::Foo APPEND PROPERTY
IMPORTED_CONFIGURATIONS DEBUG
)
set_target_properties(Foo::Foo PROPERTIES
IMPORTED_LOCATION_DEBUG "${Foo_LIBRARY_DEBUG}"
)
endif()
set_target_properties(Foo::Foo PROPERTIES
INTERFACE_COMPILE_OPTIONS "${PC_Foo_CFLAGS_OTHER}"
INTERFACE_INCLUDE_DIRECTORIES "${Foo_INCLUDE_DIR}"
)
endif()
RELEASE 变体应首先列在属性中,以便在用户使用与任何列出的 IMPORTED_CONFIGURATIONS 不完全匹配的配置时选择该变体。
大多数缓存变量应标记为高级,以便在 GUI 界面(如 cmake-gui(1) 或 ccmake(1))中隐藏它们,除非用户明确选择显示和修改它们。
mark_as_advanced(
Foo_INCLUDE_DIR
Foo_LIBRARY
)
如果此查找模块替换了提供了变量(在当前标准变量命名约定之前)的旧模块版本,则还应提供向后兼容性变量,以最大程度地减少干扰。例如:
# Backward compatibility variables
set(Foo_VERSION_STRING ${Foo_VERSION})