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_package() 命令为 <PackageName> 调用时加载的 Find<PackageName>.cmake 文件。

查找模块的主要任务是确定软件包是否可用,设置 <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

编译使用该模块的代码时要使用的编译定义。这实际上不应包括诸如 -DHAS_JPEG 之类的选项,客户端源代码文件使用这些选项来决定是否 #include <jpeg.h>

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 版可能会将变量 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

如果模块提供多个库或在其他模块也可能提供同名库的情况下,建议使用此形式来指定在何处查找使用模块提供的库 Yy 的头文件。同样,将其用作 find_path() 命令的结果变量是合适的。

为了防止用户被配置设置淹没,请尝试将尽可能多的选项保留在缓存之外,至少保留一个选项,该选项可用于禁用模块的使用,或定位未找到的库(例如 Xxx_ROOT_DIR)。出于同样的原因,将大多数缓存选项标记为高级。对于同时提供调试和发布二进制文件的软件包,通常会创建带有 _LIBRARY_<CONFIG> 后缀的缓存变量,例如 Foo_LIBRARY_RELEASEFoo_LIBRARY_DEBUGSelectLibraryConfigurations 模块对于这种情况可能很有用。

虽然这些是标准变量名,但您应为任何实际使用的旧名称提供向后兼容性。确保将它们注释为已弃用,这样就不会有人开始使用它们。

一个示例查找模块

我们将描述如何为库 Foo 创建一个简单的查找模块。

模块的顶部应以许可证声明开始,后跟一个空行,然后是 Bracket Comment。注释应以 .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 will define the following variables:

``Foo_FOUND``
  True if the system has the Foo library.
``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(PKG_CONFIG_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(尽管请注意,查找模块传统上使用 Foo_VERSION_STRING,因此您可能需要同时设置两者)。否则,请尝试使用来自 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 不完全匹配时选择该变体。

大多数缓存变量应在 ccmake 界面中隐藏,除非用户明确要求编辑它们。

mark_as_advanced(
  Foo_INCLUDE_DIR
  Foo_LIBRARY
)

如果此模块替换了旧版本,则应设置兼容性变量,以尽可能减少中断。

# compatibility variables
set(Foo_VERSION_STRING ${Foo_VERSION})