FetchContent

3.11 版本新增。

注意

使用依赖项指南 对此一般主题进行了高级介绍。它更广泛地概述了 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()

通常不适合将 REQUIRED 指定为 FIND_PACKAGE_ARGS 之后的附加参数之一。这样做将意味着 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

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

3.25 版本新增

SYSTEM

如果提供了 SYSTEM 参数,则由 FetchContent_MakeAvailable() 添加的子目录的 SYSTEM 目录属性将设置为 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_ARGS 都将被省略。OVERRIDE_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,它仍然会影响在该变量调用 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 运行开始时都会被清除。如果在上一步中填充依赖项后不存在配置文件,则会写入一个最小的配置文件,该文件 包含 任何 <lowercaseName>-extra.cmake<name>Extra.cmake 文件,并带有 OPTIONAL 标志(因此文件可以缺失,不会生成警告)。类似地,如果不存在配置版本文件,则会写入一个非常简单的文件,将 PACKAGE_VERSION_COMPATIBLEPACKAGE_VERSION_EXACT 设置为 true。这确保了未来对依赖项的所有 find_package() 调用都将使用重定向的配置文件,无论任何版本要求如何。CMake 无法自动确定任意依赖项的版本,因此无法设置 PACKAGE_VERSION。当通过 add_subdirectory() 在下一步中引入依赖项时,它可能会选择用一个也设置 PACKAGE_VERSION 的文件覆盖 CMAKE_FIND_PACKAGE_REDIRECTS_DIR 中生成的配置版本文件。依赖项还可以写入 <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_GENERATORCMAKE_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 相比,这是一个不那么严格的下载/更新控制。它不是绕过所有下载和更新逻辑,而是仅阻止更新步骤在使用 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

无论 FetchContent_Declare() 调用是否包含 FIND_PACKAGE_ARGS 关键字,FetchContent_MakeAvailable() 都可以调用 find_package()。如果未提供 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 中的任何一个匹配,但由更高层级的项目来确保它定义的详细信息对于子项目仍然有意义。

  • projAFetchContent_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 文件时,它会将 tar 包下载并解压到相对于构建目录的 _deps/mycompany_toolchains-src 中。CMAKE_TOOLCHAIN_FILE 变量直到到达 project() 命令时才使用,此时 CMake 会查找相对于构建目录的指定工具链文件。因为此时 tar 包已经下载并解压,所以即使是第一次在构建目录中运行 cmake,工具链文件也会到位。

在 CMake 脚本模式下填充内容

最后一个示例演示了如何使用 CMake 的脚本模式下载和解压固件 tar 包。对 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
)