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() 命令的 HINTSPATHS 选项在 Windows 平台上提供了查询注册表的可能性。

使用包含扩展的 BNF 符号指定的注册表查询的正式语法如下

registry_query  ::= '[' sep_definition? root_key
                    ((key_separator sub_key)? (value_separator value_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_separator element)*
key_separator   ::= '/' | '\\'
value_separator ::= element | ';'
value_name      ::= element | '(default)'
element         ::= character\+
character       ::= <any character except key_separator and value_separator>

可选的 sep_definition 项提供了指定用于分隔 sub_keyvalue_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_DIR

Xxx 安装的基目录,可以由查找模块在找到 Xxx 时选择性地设置。这对于需要引用相对于公共基(或根)目录的许多文件的较大包很有用。不要与从外部为查找模块设置的 Xxx_ROOT 提示变量混淆,以便查找模块知道在哪里查找 Xxx

Xxx_VERSION_VV

此形式的变量指定提供的 Xxx 模块是该模块的 VV 版本。对于给定的模块,不应设置多于一个此形式的变量为 true。例如,一个名为 Barry 的模块可能在多年来经历了许多不同的主版本。Barry 模块的 3.0 版本可能将变量 Barry_VERSION_3 设置为 true,而该模块的旧版本可能将 Barry_VERSION_2 设置为 true。将 Barry_VERSION_3Barry_VERSION_2 都设置为 true 是一个错误。

Xxx_WRAP_YY

当此形式的变量设置为 false 时,表示不应使用相关的包装命令。包装命令取决于模块,它可能由模块名称隐含,或者可能由变量的 YY 部分指定。

Xxx_Yy_FOUND

对于此形式的变量,Yy 是模块的一个组件名称。它必须完全匹配可能传递给模块的 find_package() 命令的有效组件名称之一。如果此形式的变量设置为 false,则表示模块 XxxYy 组件未找到或不可用。此形式的变量通常用于可选组件,以便调用者可以检查可选组件是否可用。

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_RELEASEFoo_LIBRARY_DEBUGSelectLibraryConfigurations 模块可用于此类情况。

虽然这些是标准变量名,但对于替换提供了旧命名约定的变量的旧模块版本的查找模块,应提供向后兼容性。旧变量名应被记录为已弃用,以阻止进一步使用。

一个示例查找模块

我们将描述如何为库 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})