FetchContent

3.11 版本新增。

此模块提供命令,用于在配置时或作为调用脚本的一部分来填充内容。

使用 CMake 加载此模块

include(FetchContent)

注意

使用依赖项指南》对这个一般性主题进行了高层次的介绍。它更全面地概述了 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_VERIFYCMAKE_TLS_CAINFOCMAKE_NETRCCMAKE_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 关键字之前。FIND_PACKAGE_ARGS 关键字如果 CMAKE_FIND_PACKAGE_TARGETS_GLOBAL 变量在调用 FetchContent_Declare() 时设置为 true,则会在 find_package() 参数中追加 GLOBAL 关键字(如果尚未指定)。如果 FIND_PACKAGE_ARGS 未给出,但 FETCHCONTENT_TRY_FIND_PACKAGE_MODE 设置为 ALWAYS,则也会追加。

OVERRIDE_FIND_PACKAGE 不能与 FIND_PACKAGE_ARGS 一起使用。

依赖项提供者 讨论了 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()(对于命名的依赖项),允许前者满足后者的包需求。FIND_PACKAGE_ARGS 不能与 OVERRIDE_FIND_PACKAGE 一起使用。

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

3.25 版中已添加

SYSTEM

如果提供了 SYSTEM 参数,则 SYSTEM 目录属性将被设置为 true(对于由 FetchContent_MakeAvailable() 添加的子目录)。这将影响作为该命令一部分创建的非导入目标。有关影响的更详细讨论,请参阅 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 作为第一个参数,然后是 <name> 的第一个 FetchContent_Declare() 调用的参数。如果 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_TRY_FIND_PACKAGE_MODE 的值(在调用 FetchContent_Declare() 时)决定了 FetchContent_MakeAvailable() 是否可以调用 find_package()。如果在调用 CMAKE_FIND_PACKAGE_TARGETS_GLOBAL 时将变量设置为 true,则它仍然会影响在调用 find_package() 时创建的任何导入目标,即使该变量在相应详细信息声明时为 false。

如果依赖项未被提供者或 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 运行开始时都会被清除。如果在上一步填充依赖项后不存在配置文件,则会写入一个最小的配置,该配置将 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 版中已添加:如果 SYSTEM 关键字包含在对 FetchContent_Declare() 的调用中,则 SYSTEM 关键字将被添加到 add_subdirectory() 命令中。

    3.28 版中已添加:如果 EXCLUDE_FROM_ALL 关键字包含在对 FetchContent_Declare() 的调用中,则 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_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

SOURCE_DIRBINARY_DIR 参数受 ExternalProject_Add() 支持,但 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 脚本模式 中,这允许 FetchContent_Populate() 在没有构建工具或 CMake 生成器的情况下调用。

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 运行中多次调用 FetchContent_Populate(<name>)(对于相同的 <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 只阻止更新步骤在 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() 将仅在 FetchContent_Declare() 调用包含 FIND_PACKAGE_ARGS 关键字时调用 find_package()。如果未设置 FETCHCONTENT_TRY_FIND_PACKAGE_MODE,这也是默认行为。

ALWAYS

find_package() 可以由 FetchContent_MakeAvailable() 调用,无论 FetchContent_Declare() 调用是否包含 FIND_PACKAGE_ARGS 关键字。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() 集成

对于上面的示例,如果用户想在下载和构建它们之前,先通过 find_package() 来查找 googletestCatch2,他们可以将 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
)