FetchContent

在 3.11 版本中添加。

注意

Using Dependencies Guide 提供了关于这个通用主题的高级介绍。它更广泛地概述了 FetchContent 模块在更大的图景中的位置,包括它与 find_package() 命令的关系。 建议在继续阅读以下详细信息之前先阅读该指南。

概述

此模块允许在配置时通过 ExternalProject 模块支持的任何方法填充内容。 虽然 ExternalProject_Add() 在构建时下载,但 FetchContent 模块使内容立即可用,允许配置步骤在诸如 add_subdirectory(), include()file() 操作中使用内容。

内容填充详细信息应与执行实际填充的命令分开定义。 这种分离确保在任何可能尝试使用它们来填充内容之前,所有依赖项详细信息都已定义。 这在更复杂的项目层级结构中尤其重要,在这些层级结构中,依赖项可能在多个项目之间共享。

以下显示了一个典型示例,说明如何声明某些依赖项的内容详细信息,然后通过单独的调用确保它们被填充

FetchContent_Declare(
  googletest
  GIT_REPOSITORY https://github.com/google/googletest.git
  GIT_TAG        703bd9caab50b139428cea1aaff9974ebee5742e # release-1.10.0
)
FetchContent_Declare(
  myCompanyIcons
  URL      https://intranet.mycompany.com/assets/iconset_1.12.tar.gz
  URL_HASH MD5=5588a7b18261c20068beabfb4f530b87
)

FetchContent_MakeAvailable(googletest myCompanyIcons)

FetchContent_MakeAvailable() 命令确保已填充命名的依赖项,无论是通过之前的调用还是通过自身填充。 当执行填充时,如果可能,它还会将它们添加到主构建中,以便主构建可以使用已填充项目的目标等。 有关如何执行这些步骤,请参阅命令的文档。

当使用分层项目安排时,层级结构中较高层的项目能够覆盖在项目层级结构中任何较低位置指定的已声明内容详细信息。 对于给定依赖项声明的第一个详细信息具有优先权,无论它发生在项目层级结构中的哪个位置。 同样,第一个尝试填充依赖项的调用“获胜”,后续填充重用第一个的结果,而不是再次重复填充。 请参阅 示例,其中演示了这种情况。

FetchContent 模块还支持在单个调用中定义和填充内容,而无需检查内容是否已在其他地方填充。 这不应在项目中使用,但可能适用于在 CMake 脚本模式 中填充内容。 有关详细信息,请参阅 FetchContent_Populate()

命令

FetchContent_Declare
FetchContent_Declare(
  <name>
  <contentOptions>...
  [EXCLUDE_FROM_ALL]
  [SYSTEM]
  [OVERRIDE_FIND_PACKAGE |
   FIND_PACKAGE_ARGS args...]
)

FetchContent_Declare() 函数记录描述如何填充指定内容的选项。 如果此类详细信息已在此项目的前期记录(无论在项目层级结构中的哪个位置),则此调用以及所有后续对相同内容 <name> 的调用都将被忽略。 这种“先记录者胜出”的方法允许分层项目让父项目覆盖子项目的内容详细信息。

内容 <name> 可以是任何不带空格的字符串,但最佳实践是仅使用字母、数字和下划线。 名称将不区分大小写地处理,并且对于它代表的内容应该很明显。 它通常是子项目的名称,或赋予其顶级 project() 命令的值(如果它是一个 CMake 项目)。 对于著名的公共项目,名称通常应该是项目的官方名称。 选择一个不寻常的名称使得其他需要相同内容的项目不太可能使用相同的名称,从而导致内容被多次填充。

<contentOptions> 可以是 ExternalProject_Add() 命令理解的任何下载、更新或补丁选项。 配置、构建、安装和测试步骤被显式禁用,因此与这些步骤相关的选项将被忽略。 SOURCE_SUBDIR 选项是一个例外,有关其如何影响行为的详细信息,请参阅 FetchContent_MakeAvailable()

在 3.30 版本中更改: 当策略 CMP0168 设置为 NEW 时,某些与输出和目录相关的选项将被忽略。 有关详细信息,请参阅策略文档。

在大多数情况下,<contentOptions> 将只是一对定义下载方法和特定于方法的详细信息(如提交标签或存档哈希)的选项。 例如

FetchContent_Declare(
  googletest
  GIT_REPOSITORY https://github.com/google/googletest.git
  GIT_TAG        703bd9caab50b139428cea1aaff9974ebee5742e # release-1.10.0
)

FetchContent_Declare(
  myCompanyIcons
  URL      https://intranet.mycompany.com/assets/iconset_1.12.tar.gz
  URL_HASH MD5=5588a7b18261c20068beabfb4f530b87
)

FetchContent_Declare(
  myCompanyCertificates
  SVN_REPOSITORY svn+ssh://svn.mycompany.com/srv/svn/trunk/certs
  SVN_REVISION   -r12345
)

当从远程位置获取内容并且您不控制该服务器时,建议对 GIT_TAG 使用哈希而不是分支或标签名称。 提交哈希更安全,并有助于确认下载的内容是您期望的内容。

在 3.14 版本中更改: 下载、更新或补丁步骤的命令可以访问终端。 这可能是诸如密码提示或实时显示命令进度之类的操作所需要的。

在 3.22 版本中添加: CMAKE_TLS_VERIFY, CMAKE_TLS_CAINFO, CMAKE_NETRC, 和 CMAKE_NETRC_FILE 变量现在为其相应的内容选项提供默认值,就像它们对 ExternalProject_Add() 所做的那样。 以前,FetchContent 模块忽略了这些变量。

在 3.24 版本中添加

FIND_PACKAGE_ARGS

此选项适用于 FetchContent_MakeAvailable() 命令可能首先尝试调用 find_package() 来满足 <name> 的依赖项的场景。 默认情况下,这样的调用将只是 find_package(<name>),但 FIND_PACKAGE_ARGS 可用于提供要附加在 <name> 之后的其他参数。 也可以在 FIND_PACKAGE_ARGS 之后不提供任何内容,这表明如果 FETCHCONTENT_TRY_FIND_PACKAGE_MODE 设置为 OPT_IN 或未设置,则仍然可以调用 find_package()

通常不适合在 FIND_PACKAGE_ARGS 之后指定 REQUIRED 作为附加参数之一。 这样做意味着 find_package() 调用必须成功,因此在 FetchContent_Declare() 调用中指定的其他详细信息将没有机会用作后备方案。

FIND_PACKAGE_ARGS 关键字之后的所有内容都将附加到 find_package() 调用,因此所有其他 <contentOptions> 都必须在 FIND_PACKAGE_ARGS 关键字之前出现。 如果在调用 FetchContent_Declare()CMAKE_FIND_PACKAGE_TARGETS_GLOBAL 变量设置为 true,则如果 GLOBAL 关键字尚未指定,则会将其附加到 find_package() 参数。 如果未给出 FIND_PACKAGE_ARGS,但 FETCHCONTENT_TRY_FIND_PACKAGE_MODE 设置为 ALWAYS,也会附加该关键字。

当给出 FIND_PACKAGE_ARGS 时,不能使用 OVERRIDE_FIND_PACKAGE

依赖提供程序 讨论了可以重定向 FetchContent_MakeAvailable() 调用的另一种方式。 FIND_PACKAGE_ARGS 旨在用于项目控制,而依赖提供程序允许用户覆盖项目行为。

OVERRIDE_FIND_PACKAGE

FetchContent_Declare(<name> ...) 调用包含此选项时,后续对 find_package(<name> ...) 的调用将确保已调用 FetchContent_MakeAvailable(<name>),然后使用 CMAKE_FIND_PACKAGE_REDIRECTS_DIR 目录中的配置包文件(通常由 FetchContent_MakeAvailable() 创建)。 这有效地使 FetchContent_MakeAvailable() 覆盖命名依赖项的 find_package(),允许前者满足后者的包需求。 当给出 OVERRIDE_FIND_PACKAGE 时,不能使用 FIND_PACKAGE_ARGS

如果已设置 依赖提供程序 并且项目为 <name> 依赖项调用 find_package(),则 OVERRIDE_FIND_PACKAGE 不会阻止提供程序看到该调用。 依赖提供程序始终有机会拦截对 find_package() 的任何直接调用,除非该调用包含 BYPASS_PROVIDER 选项。

在 3.25 版本中添加

SYSTEM

如果提供了 SYSTEM 参数,则 SYSTEM 目录属性,该属性由 FetchContent_MakeAvailable() 添加的子目录设置,将设置为 true。 这将影响作为该命令一部分创建的非导入目标。 有关效果的更详细讨论,请参阅 SYSTEM 目标属性文档。

在 3.28 版本中添加

EXCLUDE_FROM_ALL

如果提供了 EXCLUDE_FROM_ALL 参数,则默认情况下,FetchContent_MakeAvailable() 添加的子目录中的目标将不包含在 ALL 目标中,并且可能会从 IDE 项目文件中排除。 有关效果的详细讨论,请参阅目录属性 EXCLUDE_FROM_ALL 的文档。

FetchContent_MakeAvailable

在 3.14 版本中添加。

FetchContent_MakeAvailable(<name1> [<name2>...])

此命令确保在返回时,每个命名的依赖项都可供项目使用。 必须为每个依赖项调用 FetchContent_Declare(),并且第一个此类调用将控制如何使该依赖项可用,如下所述。

如果未设置 <lowercaseName>_SOURCE_DIR

  • 在 3.24 版本中添加: 如果设置了 依赖提供程序,请使用 FETCHCONTENT_MAKEAVAILABLE_SERIAL 作为第一个参数调用提供程序的命令,后跟对 FetchContent_Declare() 的第一次调用 的参数(对于 <name>)。 如果 SOURCE_DIRBINARY_DIR 不是原始声明参数的一部分,则将使用其默认值添加它们。 如果在声明详细信息时 FETCHCONTENT_TRY_FIND_PACKAGE_MODE 设置为 NEVER,则将省略任何 FIND_PACKAGE_ARGSOVERRIDE_FIND_PACKAGE 关键字也始终省略。 如果提供程序满足了请求,FetchContent_MakeAvailable() 将认为该依赖项已处理,跳过下面的剩余步骤,并继续处理列表中的下一个依赖项。

  • 在 3.24 版本中添加: 如果允许,将调用 find_package(<name> [<args>...]),其中 <args>... 可以由 FetchContent_Declare() 中的 FIND_PACKAGE_ARGS 选项提供。 调用 FetchContent_Declare()FETCHCONTENT_TRY_FIND_PACKAGE_MODE 变量的值确定 FetchContent_MakeAvailable() 是否可以调用 find_package()。 如果在调用 FetchContent_MakeAvailable()CMAKE_FIND_PACKAGE_TARGETS_GLOBAL 变量设置为 true,即使在声明相应详细信息时该变量为 false,它仍然会影响在该变量转而调用 find_package() 时创建的任何导入目标。

如果依赖项未被提供程序或 find_package() 调用满足,则 FetchContent_MakeAvailable() 然后使用以下逻辑使依赖项可用

  • 如果依赖项已在此运行的前期填充,请以与调用 FetchContent_GetProperties() 相同的方式设置 <lowercaseName>_POPULATED<lowercaseName>_SOURCE_DIR<lowercaseName>_BINARY_DIR 变量,然后跳过下面的剩余步骤,并继续处理列表中的下一个依赖项。

  • 使用之前调用 FetchContent_Declare() 记录的详细信息填充依赖项。 如果未记录此类详细信息,则以致命错误停止。 FETCHCONTENT_SOURCE_DIR_<uppercaseName> 可用于覆盖声明的详细信息,并使用指定位置提供的内容。

  • 在 3.24 版本中添加: 确保 CMAKE_FIND_PACKAGE_REDIRECTS_DIR 目录包含 <lowercaseName>-config.cmake<lowercaseName>-config-version.cmake 文件(或等效地,<name>Config.cmake<name>ConfigVersion.cmake)。 CMAKE_FIND_PACKAGE_REDIRECTS_DIR 变量指向的目录在每次 CMake 运行开始时都会被清除。 如果在上一步填充依赖项后不存在配置文件,则将写入一个最小的文件,该文件 include 任何带有 OPTIONAL 标志的 <lowercaseName>-extra.cmake<name>Extra.cmake 文件(因此这些文件可能缺失并且不会生成警告)。 类似地,如果不存在配置文件版本文件,则将写入一个非常简单的文件,该文件将 PACKAGE_VERSION_COMPATIBLEPACKAGE_VERSION_EXACT 设置为 true。 这确保了将来对依赖项的所有 find_package() 调用都将使用重定向的配置文件,而不管任何版本要求。 CMake 无法自动确定任意依赖项的版本,因此它无法设置 PACKAGE_VERSION。 当通过下一步中的 add_subdirectory() 拉入依赖项时,它可以选择覆盖 CMAKE_FIND_PACKAGE_REDIRECTS_DIR 中生成的配置文件版本文件,并使用也设置 PACKAGE_VERSION 的文件。 依赖项还可以写入 <lowercaseName>-extra.cmake<name>Extra.cmake 文件以执行自定义处理,或定义其正常的(已安装的)包配置文件通常会定义的任何变量(许多项目不执行任何自定义处理或设置任何变量,因此无需执行此操作)。 如果需要,如果依赖项项目不这样做,则主项目可以改为写入这些文件。 这允许主项目从尚未或无法更新以支持此功能的旧依赖项中添加缺失的详细信息。 有关示例,请参阅 与 find_package() 集成

  • 如果已填充内容的最顶层目录包含 CMakeLists.txt 文件,则调用 add_subdirectory() 将其添加到主构建中。如果不存在 CMakeLists.txt 文件,这不是错误,这允许该命令用于那些在已知位置提供下载内容,但不需要或不支持直接添加到构建中的依赖项。

    3.18 版本新增: 可以在声明的详细信息中提供 SOURCE_SUBDIR 选项,以便在顶层目录下的某个位置查找(即与 ExternalProject_Add() 命令使用 SOURCE_SUBDIR 的方式相同)。SOURCE_SUBDIR 提供的路径必须是相对路径,它将被视为相对于顶层目录。它也可以指向不包含 CMakeLists.txt 文件的目录,甚至可以指向不存在的目录。这可以用于避免添加在其顶层目录中包含 CMakeLists.txt 文件的项目。

    3.25 版本新增: 如果在调用 FetchContent_Declare() 时包含了 SYSTEM 关键字,则 SYSTEM 关键字将被添加到 add_subdirectory() 命令中。

    3.28 版本新增: 如果在调用 FetchContent_Declare() 时包含了 EXCLUDE_FROM_ALL 关键字,则 EXCLUDE_FROM_ALL 关键字将被添加到 add_subdirectory() 命令中。

    3.29 版本新增: 在调用 add_subdirectory() 之前,CMAKE_EXPORT_FIND_PACKAGE_NAME 会被设置为依赖项名称。

项目应力求在调用任何依赖项的 FetchContent_MakeAvailable() 之前声明它们可能使用的所有依赖项的详细信息。这确保了如果任何依赖项也是一个或多个其他依赖项的子依赖项,主项目仍然控制将要使用的详细信息(因为它将在依赖项有机会这样做之前首先声明它们)。在以下代码示例中,假设 uses_other 依赖项也使用 FetchContent 在内部添加 other 依赖项

# WRONG: Should declare all details first
FetchContent_Declare(uses_other ...)
FetchContent_MakeAvailable(uses_other)

FetchContent_Declare(other ...)    # Will be ignored, uses_other beat us to it
FetchContent_MakeAvailable(other)  # Would use details declared by uses_other
# CORRECT: All details declared first, so they will take priority
FetchContent_Declare(uses_other ...)
FetchContent_Declare(other ...)
FetchContent_MakeAvailable(uses_other other)

请注意,进入 FetchContent_MakeAvailable() 时,CMAKE_VERIFY_INTERFACE_HEADER_SETS 会被显式设置为 false,并在命令返回之前恢复为其原始值。开发人员通常只想验证主项目的头文件集,而不是任何依赖项的头文件集。 CMAKE_VERIFY_INTERFACE_HEADER_SETS 变量的本地操作提供了这种直观的行为。您可以使用诸如 CMAKE_PROJECT_INCLUDECMAKE_PROJECT_<PROJECT-NAME>_INCLUDE 之类的变量,为所有或部分依赖项重新启用验证。您还可以设置单个目标的 VERIFY_INTERFACE_HEADER_SETS 属性。

FetchContent_Populate

FetchContent_Populate() 命令是一个独立的调用,可以用于执行内容填充作为隔离操作。它很少是正确的命令,项目几乎总是应该使用 FetchContent_Declare()FetchContent_MakeAvailable() 来代替。FetchContent_Populate() 的主要用例是在 CMake 脚本模式 中,作为实现一些其他更高级别的自定义功能的一部分。

FetchContent_Populate(
  <name>
  [QUIET]
  [SUBBUILD_DIR <subBuildDir>]
  [SOURCE_DIR <srcDir>]
  [BINARY_DIR <binDir>]
  ...
)

<name> 之后必须至少指定一个选项,否则该调用将被以不同的方式解释(请参阅 下方)。FetchContent_Populate() 支持的选项与 FetchContent_Declare() 的选项相同,但有一些例外。以下选项与使用 FetchContent_Populate() 填充内容无关,因此不支持

  • EXCLUDE_FROM_ALL

  • SYSTEM

  • OVERRIDE_FIND_PACKAGE

  • FIND_PACKAGE_ARGS

上面签名中显示的少数选项要么是 FetchContent_Populate() 特有的,要么它们的行为与 ExternalProject_Add() 处理它们的方式略有不同

QUIET

可以提供 QUIET 选项来隐藏与填充指定内容相关的输出。如果填充失败,则无论是否给出了此选项,都将显示输出,以便可以诊断失败原因。FETCHCONTENT_QUIET 变量对直接提供内容详细信息的这种形式的 FetchContent_Populate() 调用没有影响。

3.30 版本更改: 当策略 CMP0168 设置为 NEW 时,QUIET 选项和 FETCHCONTENT_QUIET 变量无效。在这种情况下,输出仍然默认为静默,但详细程度由消息日志级别控制(请参阅 CMAKE_MESSAGE_LOG_LEVEL--log-level)。

SUBBUILD_DIR

可以提供 SUBBUILD_DIR 参数来更改为执行填充而创建的子构建的位置。默认值为 ${CMAKE_CURRENT_BINARY_DIR}/<lowercaseName>-subbuild,通常不需要覆盖此默认值。如果指定了相对路径,它将被解释为相对于 CMAKE_CURRENT_BINARY_DIR。此选项不应与 SOURCE_SUBDIR 选项混淆,后者仅影响 FetchContent_MakeAvailable() 命令。

3.30 版本更改: 当策略 CMP0168 设置为 NEW 时,SUBBUILD_DIR 将被忽略,因为在这种情况下没有子构建。

SOURCE_DIR, BINARY_DIR

ExternalProject_Add() 支持 SOURCE_DIRBINARY_DIR 参数,但 FetchContent_Populate() 使用不同的默认值。SOURCE_DIR 默认为 ${CMAKE_CURRENT_BINARY_DIR}/<lowercaseName>-srcBINARY_DIR 默认为 ${CMAKE_CURRENT_BINARY_DIR}/<lowercaseName>-build。如果指定了相对路径,它将被解释为相对于 CMAKE_CURRENT_BINARY_DIR

除了上述显式选项外,任何其他无法识别的选项都将未经修改地传递给 ExternalProject_Add() 以设置下载、补丁和更新步骤。以下选项被明确禁止(它们被 FetchContent_Populate() 命令禁用)

  • CONFIGURE_COMMAND

  • BUILD_COMMAND

  • INSTALL_COMMAND

  • TEST_COMMAND

使用此形式时,FETCHCONTENT_FULLY_DISCONNECTEDFETCHCONTENT_UPDATES_DISCONNECTED 变量以及策略 CMP0170 将被忽略。

当这种形式的 FetchContent_Populate() 返回时,以下变量将在调用者的作用域中设置

<lowercaseName>_SOURCE_DIR

填充内容返回后可以找到的位置。

<lowercaseName>_BINARY_DIR

最初旨在用作相应构建目录的目录,但在使用此命令形式时不太可能相关。

如果在 CMake 脚本模式 中使用 FetchContent_Populate(),请注意,该实现设置了一个子构建,因此需要 CMake 生成器和构建工具可用。如果默认情况下找不到这些工具,则需要在调用脚本的命令行上适当地设置 CMAKE_GENERATOR 以及可能的 CMAKE_MAKE_PROGRAM 变量。

3.30 版本更改: 如果策略 CMP0168 设置为 NEW,则不使用子构建。在 CMake 脚本模式 中,这允许在没有任何构建工具或 CMake 生成器的情况下调用 FetchContent_Populate()

3.18 版本新增: 添加了对 DOWNLOAD_NO_EXTRACT 选项的支持。

该命令支持另一种形式,尽管不应再使用它

FetchContent_Populate(<name>)

3.30 版本更改: 此形式已弃用。策略 CMP0169 为仍然需要使用此形式的项目提供向后兼容性,但项目应更新为改用 FetchContent_MakeAvailable()

在这种形式中,传递给 FetchContent_Populate() 的唯一参数是 <name>。当以这种方式使用时,该命令假定内容详细信息已通过先前对 FetchContent_Declare() 的调用记录下来。详细信息存储在全局属性中,因此它们不受变量或目录作用域等因素的影响。因此,详细信息先前在项目中的哪个位置声明并不重要,只要它们在调用 FetchContent_Populate() 之前已声明即可。然后,这些保存的详细信息将用于使用基于 ExternalProject_Add() 的方法填充内容(有关如何执行此操作的重要行为方面,请参阅策略 CMP0168)。

当这种形式的 FetchContent_Populate() 返回时,以下变量将在调用者的作用域中设置

<lowercaseName>_POPULATED

此变量将始终由此调用设置为 TRUE

<lowercaseName>_SOURCE_DIR

填充内容返回后可以找到的位置。

<lowercaseName>_BINARY_DIR

旨在用作相应构建目录的目录。

这三个变量的值也可以使用 FetchContent_GetProperties() 命令从项目层次结构中的任何位置检索。

该实现确保如果在之前的 CMake 运行中已填充内容,则将重用该内容,而不会再次重新填充。对于填充涉及下载内容的常见情况,下载成本仅支付一次。但请注意,在单个 CMake 运行中多次使用相同的 <name> 调用 FetchContent_Populate(<name>) 是错误的。有关如何确定是否已在当前运行中执行了 <name> 的填充,请参阅 FetchContent_GetProperties()

FetchContent_GetProperties

当使用保存的内容详细信息时,调用 FetchContent_MakeAvailable()FetchContent_Populate() 会在全局属性中记录信息,这些信息可以随时查询。此信息可能包括与内容关联的源目录和二进制目录,以及内容填充是否已在当前配置运行期间处理。

FetchContent_GetProperties(
  <name>
  [SOURCE_DIR <srcDirVar>]
  [BINARY_DIR <binDirVar>]
  [POPULATED <doneVar>]
)

SOURCE_DIRBINARY_DIRPOPULATED 选项可用于指定应检索哪些属性。每个选项都接受一个值,该值是存储该属性的变量的名称。但在大多数情况下,只给出 <name>,在这种情况下,调用将设置与调用 FetchContent_MakeAvailable(name)FetchContent_Populate(name) 相同的变量。请注意,如果调用由 依赖项提供程序 满足,则 SOURCE_DIRBINARY_DIR 的值可能为空。

当使用 FetchContent_MakeAvailable() 时,很少需要此命令。它更常用于实现已弃用的 FetchContent_Populate() 模式的一部分,该模式确保无论是否已在项目的其他位置执行填充,相关变量都将始终被定义

# WARNING: This pattern is deprecated, don't use it!
#
# Check if population has already been performed
FetchContent_GetProperties(depname)
if(NOT depname_POPULATED)
  # Fetch the content using previously declared details
  FetchContent_Populate(depname)

  # Set custom variables, policies, etc.
  # ...

  # Bring the populated content into the build
  add_subdirectory(${depname_SOURCE_DIR} ${depname_BINARY_DIR})
endif()
FetchContent_SetPopulated

3.24 版本新增。

注意

此命令应仅由 依赖项提供程序 调用。在任何其他上下文中调用它都不受支持,并且未来的 CMake 版本在这种情况下可能会因致命错误而停止。

FetchContent_SetPopulated(
  <name>
  [SOURCE_DIR <srcDir>]
  [BINARY_DIR <binDir>]
)

如果提供程序命令满足 FETCHCONTENT_MAKEAVAILABLE_SERIAL 请求,则必须在返回之前调用此函数。SOURCE_DIRBINARY_DIR 参数可用于指定 FetchContent_GetProperties() 应为其对应参数返回的值。仅当 SOURCE_DIRBINARY_DIR 的含义与内置 FetchContent_MakeAvailable() 实现填充时的含义相同时,才提供它们。

变量

许多缓存变量会影响使用来自 FetchContent_Declare() 调用的详细信息来填充内容的行为。

注意

所有这些变量都旨在供开发人员自定义行为。它们通常不应由项目设置。

FETCHCONTENT_BASE_DIR

在大多数情况下,保存的详细信息未指定与内部子构建、最终源和构建区域使用的目录相关的任何选项。通常最好将这些决定留给 FetchContent 模块代表项目处理。FETCHCONTENT_BASE_DIR 缓存变量控制收集所有内容填充目录的点,但在大多数情况下,开发人员不需要更改此变量。默认位置是 ${CMAKE_BINARY_DIR}/_deps,但如果开发人员更改此值,他们应力求保持路径简短,并保持在构建树的顶层之下,以避免在 Windows 上遇到路径长度问题。

FETCHCONTENT_QUIET

填充期间的日志输出可能非常冗长,使配置阶段非常嘈杂。此缓存选项(默认情况下为 ON)隐藏所有填充输出,除非遇到错误。如果遇到下载挂起的问题,临时关闭此选项可能有助于诊断哪个内容填充导致了问题。

3.30 版本更改: 如果策略 CMP0168 设置为 NEW,则 FETCHCONTENT_QUIET 将被忽略。在这种情况下,输出仍然默认为静默,但详细程度由消息日志级别控制(请参阅 CMAKE_MESSAGE_LOG_LEVEL--log-level)。

FETCHCONTENT_FULLY_DISCONNECTED

启用此选项后,不会尝试下载或更新任何内容。假定所有内容已在之前的运行中填充,或者源目录已指向开发人员手动提供的现有内容(使用下面进一步描述的选项)。当开发人员知道任何内容详细信息都没有更改时,将此选项设置为 ON 可以加快配置阶段。默认情况下为 OFF

注意

FETCHCONTENT_FULLY_DISCONNECTED 变量不是防止在构建目录中的首次运行时进行任何网络访问的适当方法。这样做可能会破坏项目,导致误导性的错误消息,并隐藏细微的填充失败。此变量专门用于仅在 CMake 首次运行之后打开。如果您想防止即使在首次运行时也进行网络访问,请使用 依赖项提供程序 并从本地内容填充依赖项。

3.30 版本更改: 现在强制执行当 FETCHCONTENT_FULLY_DISCONNECTED 为 true 时,源目录已填充的约束。请参阅策略 CMP0170

FETCHCONTENT_UPDATES_DISCONNECTED

FETCHCONTENT_FULLY_DISCONNECTED 相比,这是一个不太严格的下载/更新控制。FETCHCONTENT_UPDATES_DISCONNECTED 不会像 FETCHCONTENT_FULLY_DISCONNECTED 那样绕过所有下载和更新逻辑,而只是阻止更新步骤在使用 git 或 hg 下载方法时连接到远程服务器。如果更新步骤的详细信息发生更改,更新仍然会发生,但更新尝试仅使用本地已有的信息进行(因此,切换到本地已获取的不同标签或提交将成功,但切换到未知的提交哈希值将失败)。下载步骤不受影响,因此如果之前没有下载内容,则启用此选项后仍会下载内容。这可以加快配置步骤,但不如 FETCHCONTENT_FULLY_DISCONNECTED 快。FETCHCONTENT_UPDATES_DISCONNECTED 默认值为 OFF

FETCHCONTENT_TRY_FIND_PACKAGE_MODE

3.24 版本新增。

此变量修改 FetchContent_Declare() 为给定依赖项记录的详细信息。虽然它最终控制 FetchContent_MakeAvailable() 的行为,但起作用的是调用 FetchContent_Declare() 时变量的值。当调用 FetchContent_MakeAvailable() 时,变量设置为什么值没有区别。由于该变量应该只由用户设置,而不是由项目直接设置,因此它通常在任何时候都具有相同的值,因此这种区别通常不明显。

FETCHCONTENT_TRY_FIND_PACKAGE_MODE 最终控制 FetchContent_MakeAvailable() 是否允许调用 find_package() 来满足依赖项。该变量可以设置为以下值之一

OPT_IN

仅当 FetchContent_MakeAvailable() 调用包含 FIND_PACKAGE_ARGS 关键字时,find_package() 才会被调用。FetchContent_Declare() 调用中包含 FIND_PACKAGE_ARGS 关键字时,find_package() 才会被调用。如果未设置 FETCHCONTENT_TRY_FIND_PACKAGE_MODE,这也是默认行为。

ALWAYS

无论 find_package() 调用是否包含 FIND_PACKAGE_ARGS 关键字,FetchContent_MakeAvailable() 都可以调用 FetchContent_Declare()。如果没有给出 FIND_PACKAGE_ARGS 关键字,其行为将如同提供了 FIND_PACKAGE_ARGS,并且其后没有其他参数。

NEVER

FetchContent_MakeAvailable() 将不会调用 find_package()。任何在 FetchContent_Declare() 调用中给出的 FIND_PACKAGE_ARGS 都将被忽略。

作为一种特殊情况,如果依赖项的 FETCHCONTENT_SOURCE_DIR_<uppercaseName> 变量具有非空值,则假定用户正在覆盖所有其他使该依赖项可用的方法。FETCHCONTENT_TRY_FIND_PACKAGE_MODE 对该依赖项无效,并且 FetchContent_MakeAvailable() 将不会尝试为其调用 find_package()

除了上述内容外,还为每个内容名称定义了以下变量

FETCHCONTENT_SOURCE_DIR_<uppercaseName>

如果设置了此变量,则不会对指定内容执行下载或更新步骤,并且返回给调用方的 <lowercaseName>_SOURCE_DIR 变量将指向此位置。这为开发人员提供了一种拥有内容单独检出的方法,他们可以自由修改该内容,而不会受到构建的干扰。构建系统只是使用现有的源,但它仍然定义 <lowercaseName>_BINARY_DIR 以指向其自身的构建区域。强烈建议开发人员使用此机制,而不是编辑默认位置填充的源,因为当项目更改内容填充详细信息时,对默认位置源的更改可能会丢失。

FETCHCONTENT_UPDATES_DISCONNECTED_<uppercaseName>

这是 FETCHCONTENT_UPDATES_DISCONNECTED 的每个内容等效选项。如果全局选项或此选项为 ON,则 git 和 hg 方法的更新将不会为命名内容联系任何远程服务器。它们将仅使用本地已有的信息。禁用单个内容的更新对于详细信息很少更改的内容可能很有用,同时仍然使其他频繁更改的内容保持启用更新状态。

示例

典型案例

第一个相当简单的示例确保了一些流行的测试框架可用于主构建

include(FetchContent)
FetchContent_Declare(
  googletest
  GIT_REPOSITORY https://github.com/google/googletest.git
  GIT_TAG        703bd9caab50b139428cea1aaff9974ebee5742e # release-1.10.0
)
FetchContent_Declare(
  Catch2
  GIT_REPOSITORY https://github.com/catchorg/Catch2.git
  GIT_TAG        605a34765aa5d5ecbf476b4598a862ada971b0cc # v3.0.1
)

# After the following call, the CMake targets defined by googletest and
# Catch2 will be available to the rest of the build
FetchContent_MakeAvailable(googletest Catch2)

与 find_package() 集成

对于前面的示例,如果用户希望在尝试从源代码下载和构建 googletestCatch2 之前,先尝试通过 find_package() 查找它们,则可以将 FETCHCONTENT_TRY_FIND_PACKAGE_MODE 变量设置为 ALWAYS。 这也会影响整个项目中对 FetchContent_Declare() 的任何其他调用,这可能是不可接受的。可以通过将 FIND_PACKAGE_ARGS 添加到声明的详细信息中,并将 FETCHCONTENT_TRY_FIND_PACKAGE_MODE 设置为未设置或设置为 OPT_IN,来仅为这两个依赖项启用此行为

include(FetchContent)
FetchContent_Declare(
  googletest
  GIT_REPOSITORY https://github.com/google/googletest.git
  GIT_TAG        703bd9caab50b139428cea1aaff9974ebee5742e # release-1.10.0
  FIND_PACKAGE_ARGS NAMES GTest
)
FetchContent_Declare(
  Catch2
  GIT_REPOSITORY https://github.com/catchorg/Catch2.git
  GIT_TAG        605a34765aa5d5ecbf476b4598a862ada971b0cc # v3.0.1
  FIND_PACKAGE_ARGS
)

# This will try calling find_package() first for both dependencies
FetchContent_MakeAvailable(googletest Catch2)

对于 Catch2,不需要 find_package() 的其他参数,因此在 FIND_PACKAGE_ARGS 关键字之后没有提供其他参数。 对于 googletest,其软件包更常被称为 GTest,因此添加了参数以支持通过该名称找到它。

如果用户想要禁用 FetchContent_MakeAvailable() 为任何依赖项调用 find_package(),即使它在其声明的详细信息中提供了 FIND_PACKAGE_ARGS,他们也可以将 FETCHCONTENT_TRY_FIND_PACKAGE_MODE 设置为 NEVER

如果项目想要指示应从源代码下载和构建这两个依赖项,并且应重定向 find_package() 调用以使用构建的依赖项,则在声明内容详细信息时应使用 OVERRIDE_FIND_PACKAGE 选项

include(FetchContent)
FetchContent_Declare(
  googletest
  GIT_REPOSITORY https://github.com/google/googletest.git
  GIT_TAG        703bd9caab50b139428cea1aaff9974ebee5742e # release-1.10.0
  OVERRIDE_FIND_PACKAGE
)
FetchContent_Declare(
  Catch2
  GIT_REPOSITORY https://github.com/catchorg/Catch2.git
  GIT_TAG        605a34765aa5d5ecbf476b4598a862ada971b0cc # v3.0.1
  OVERRIDE_FIND_PACKAGE
)

# The following will automatically forward through to FetchContent_MakeAvailable()
find_package(googletest)
find_package(Catch2)

CMake 提供了一个 FindGTest 模块,该模块定义了一些旧项目可能使用的变量,而不是链接到导入的目标。为了支持这些情况,我们可以提供一个额外的文件。为了与 FetchContent 的“先定义者胜出”理念保持一致,我们仅在其他内容尚未执行此操作时才写出该文件。

FetchContent_MakeAvailable(googletest)

if(NOT EXISTS ${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/googletest-extra.cmake AND
   NOT EXISTS ${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/googletestExtra.cmake)
  file(WRITE ${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/googletest-extra.cmake
[=[
if("${GTEST_LIBRARIES}" STREQUAL "" AND TARGET GTest::gtest)
  set(GTEST_LIBRARIES GTest::gtest)
endif()
if("${GTEST_MAIN_LIBRARIES}" STREQUAL "" AND TARGET GTest::gtest_main)
  set(GTEST_MAIN_LIBRARIES GTest::gtest_main)
endif()
if("${GTEST_BOTH_LIBRARIES}" STREQUAL "")
  set(GTEST_BOTH_LIBRARIES ${GTEST_LIBRARIES} ${GTEST_MAIN_LIBRARIES})
endif()
]=])
endif()

项目也可能正在使用 find_package(GTest) 而不是 find_package(googletest),但可以使用 CMAKE_FIND_PACKAGE_REDIRECTS_DIR 区域将后者作为前者的依赖项引入。这可能足以满足典型的 find_package(GTest) 调用。

FetchContent_MakeAvailable(googletest)

if(NOT EXISTS ${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/gtest-config.cmake AND
   NOT EXISTS ${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/GTestConfig.cmake)
  file(WRITE ${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/gtest-config.cmake
[=[
include(CMakeFindDependencyMacro)
find_dependency(googletest)
]=])
endif()

if(NOT EXISTS ${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/gtest-config-version.cmake AND
   NOT EXISTS ${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/GTestConfigVersion.cmake)
  file(WRITE ${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/gtest-config-version.cmake
[=[
include(${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/googletest-config-version.cmake OPTIONAL)
if(NOT PACKAGE_VERSION_COMPATIBLE)
  include(${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/googletestConfigVersion.cmake OPTIONAL)
endif()
]=])
endif()

覆盖 CMakeLists.txt 文件的查找位置

如果子项目的 CMakeLists.txt 文件不在其源树的顶层,则可以使用 SOURCE_SUBDIR 选项来告诉 FetchContent 在哪里找到它。以下示例展示了如何使用该选项,并且还在将子项目拉入主构建之前设置了一个对子项目有意义的变量(设置为 INTERNAL 缓存变量以避免策略 CMP0077 的问题)

include(FetchContent)
FetchContent_Declare(
  protobuf
  GIT_REPOSITORY https://github.com/protocolbuffers/protobuf.git
  GIT_TAG        ae50d9b9902526efd6c7a1907d09739f959c6297 # v3.15.0
  SOURCE_SUBDIR  cmake
)
set(protobuf_BUILD_TESTS OFF CACHE INTERNAL "")
FetchContent_MakeAvailable(protobuf)

复杂的依赖关系层级

在更复杂的项目层级结构中,依赖关系可能更复杂。考虑一个层级结构,其中 projA 是顶层项目,它直接依赖于项目 projBprojCprojBprojC 都可以独立构建,并且它们都依赖于另一个项目 projDprojB 另外还依赖于 projE。此示例假定所有五个项目都可以在公司 git 服务器上找到。每个项目的 CMakeLists.txt 可能具有如下部分

projA
include(FetchContent)
FetchContent_Declare(
  projB
  GIT_REPOSITORY git@mycompany.com:git/projB.git
  GIT_TAG        4a89dc7e24ff212a7b5167bef7ab079d
)
FetchContent_Declare(
  projC
  GIT_REPOSITORY git@mycompany.com:git/projC.git
  GIT_TAG        4ad4016bd1d8d5412d135cf8ceea1bb9
)
FetchContent_Declare(
  projD
  GIT_REPOSITORY git@mycompany.com:git/projD.git
  GIT_TAG        origin/integrationBranch
)
FetchContent_Declare(
  projE
  GIT_REPOSITORY git@mycompany.com:git/projE.git
  GIT_TAG        v2.3-rc1
)

# Order is important, see notes in the discussion further below
FetchContent_MakeAvailable(projD projB projC)
projB
include(FetchContent)
FetchContent_Declare(
  projD
  GIT_REPOSITORY git@mycompany.com:git/projD.git
  GIT_TAG        20b415f9034bbd2a2e8216e9a5c9e632
)
FetchContent_Declare(
  projE
  GIT_REPOSITORY git@mycompany.com:git/projE.git
  GIT_TAG        68e20f674a48be38d60e129f600faf7d
)

FetchContent_MakeAvailable(projD projE)
projC
include(FetchContent)
FetchContent_Declare(
  projD
  GIT_REPOSITORY git@mycompany.com:git/projD.git
  GIT_TAG        7d9a17ad2c962aa13e2fbb8043fb6b8a
)

FetchContent_MakeAvailable(projD)

在上面应该注意几个关键点

  • projBprojCprojD 定义了不同的内容详细信息,但是 projA 也为 projD 定义了一组内容详细信息。由于 projA 将首先定义它们,因此不会使用来自 projBprojC 的详细信息。projA 定义的覆盖详细信息不需要与来自 projBprojC 的详细信息匹配,但是由更高级别的项目来确保其定义的详细信息对于子项目仍然有意义。

  • projA 调用 FetchContent_MakeAvailable() 时,projD 列在 projBprojC 之前,因此它将在 projBprojC 之前填充。这不是 projA 必须执行的操作,这样做可以确保 projA 完全控制将 projD 引入构建的环境(目录属性尤其相关)。

  • 虽然 projAprojE 定义了内容详细信息,但它不需要显式调用 FetchContent_MakeAvailable(projE)FetchContent_Populate(projD) 本身。相反,它将此操作留给子项目 projB。对于更高级别的项目,仅定义覆盖内容详细信息并将实际填充留给子项目通常就足够了。这可以避免在项目层级的每个级别不必要地重复相同的事情,但这只有在依赖项设置的目录属性预计不会影响共享依赖项(在本例中为 projE)的填充时才应执行。

在不添加到构建系统的情况下填充内容

项目并不总是需要将填充的内容添加到构建系统中。有时项目只是想在可预测的位置使下载的内容可用。下一个示例确保一组标准的公司工具链文件(甚至可能包括工具链二进制文件本身)尽早可用,以便用于同一构建。

cmake_minimum_required(VERSION 3.14)

include(FetchContent)
FetchContent_Declare(
  mycom_toolchains
  URL  https://intranet.mycompany.com//toolchains_1.3.2.tar.gz
)
FetchContent_MakeAvailable(mycom_toolchains)

project(CrossCompileExample)

项目可以配置为使用其中一个下载的工具链,如下所示

cmake -DCMAKE_TOOLCHAIN_FILE=_deps/mycom_toolchains-src/toolchain_arm.cmake /path/to/src

当 CMake 处理 CMakeLists.txt 文件时,它会将 tarball 下载并解压缩到相对于构建目录的 _deps/mycompany_toolchains-src 中。CMAKE_TOOLCHAIN_FILE 变量直到到达 project() 命令时才使用,此时 CMake 会在相对于构建目录的位置查找指定的工具链文件。因为那时 tarball 已经下载并解压缩,所以即使是第一次在构建目录中运行 cmake,工具链文件也将到位。

在 CMake 脚本模式下填充内容

最后一个示例演示了如何使用 CMake 的 脚本模式 下载和解压缩固件 tarball。FetchContent_Populate() 的调用指定了所有内容详细信息,解压缩后的固件将放置在当前工作目录下的 firmware 目录中。

getFirmware.cmake
# NOTE: Intended to be run in script mode with cmake -P
include(FetchContent)
FetchContent_Populate(
  firmware
  URL        https://mycompany.com/assets/firmware-1.23-arm.tar.gz
  URL_HASH   MD5=68247684da89b608d466253762b0ff11
  SOURCE_DIR firmware
)