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_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创建一个简单的查找模块。

模块的顶部应以许可证声明开头,后跟一个空行,然后后跟一个方括号注释。注释应以.rst:开头,表示其其余内容是reStructuredText格式的文档。例如

# Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
# file Copyright.txt 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:注释块来记录它们。

find 模块的实现可能从文档块下方开始。现在需要找到实际的库等等。此处的代码显然会因模块而异(毕竟处理这种情况是 find 模块的目的),但库通常具有一定的通用模式。

首先,我们尝试使用pkg-config来查找库。请注意,我们不能依赖它,因为它可能不可用,但它提供了一个良好的起点。

find_package(PkgConfig)
pkg_check_modules(PC_Foo QUIET Foo)

这应该定义一些以PC_Foo_开头的变量,这些变量包含来自Foo.pc文件的信息。

现在我们需要找到库和包含文件;我们使用来自pkg-config的信息为 CMake 提供有关在哪里查找的提示。

find_path(Foo_INCLUDE_DIR
  NAMES foo.h
  PATHS ${PC_Foo_INCLUDE_DIRS}
  PATH_SUFFIXES Foo
)
find_library(Foo_LIBRARY
  NAMES foo
  PATHS ${PC_Foo_LIBRARY_DIRS}
)

或者,如果库可以使用多种配置,则可以使用SelectLibraryConfigurations自动设置Foo_LIBRARY变量

find_library(Foo_LIBRARY_RELEASE
  NAMES foo
  PATHS ${PC_Foo_LIBRARY_DIRS}/Release
)
find_library(Foo_LIBRARY_DEBUG
  NAMES foo
  PATHS ${PC_Foo_LIBRARY_DIRS}/Debug
)

include(SelectLibraryConfigurations)
select_library_configurations(Foo)

如果您有良好的方法来获取版本(例如,从头文件中),则可以使用该信息设置Foo_VERSION(尽管请注意,find 模块传统上使用Foo_VERSION_STRING,因此您可能希望同时设置这两个变量)。否则,尝试使用来自pkg-config的信息

set(Foo_VERSION ${PC_Foo_VERSION})

现在我们可以使用FindPackageHandleStandardArgs为我们完成大部分剩余的工作

include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Foo
  FOUND_VAR Foo_FOUND
  REQUIRED_VARS
    Foo_LIBRARY
    Foo_INCLUDE_DIR
  VERSION_VAR Foo_VERSION
)

这将检查REQUIRED_VARS是否包含值(不以-NOTFOUND结尾)并适当地设置Foo_FOUND。它还会缓存这些值。如果设置了Foo_VERSION,并且将所需版本传递给了find_package(),它将检查请求的版本与Foo_VERSION中的版本。它还会根据需要打印消息;请注意,如果找到了包,它将打印第一个必需变量的内容以指示它在哪里找到。

此时,我们必须为 find 模块的用户提供一种链接到已找到的库或库的方法。如上面“查找模块”部分所述,有两种方法。

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})