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() 命令确保已填充指定的依赖项,这可以通过之前的调用或自行填充来实现。在执行填充时,如果可能,它还将这些依赖项添加到主构建中,以便主构建可以使用已填充项目的 target 等。有关如何执行这些步骤的详细信息,请参阅该命令的文档。

在使用分层项目设置时,层次结构中级别的项目能够重写项目层级更低位置处指定的声明的详细信息。为给定依赖项声明的第一个详细信息具有优先权,无论在项目层级结构中的何处出现。类似的,第一个尝试填充依赖项的调用“胜出”,后续的填充会重复使用第一个结果,而不是重新重复填充。请参见演示这种情况的示例

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 关键字之前。如果在调用 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()以用于命名的依赖项,从而允许前者满足后者的包要求。FIND_PACKAGE_ARGS在提供OVERRIDE_FIND_PACKAGE时不能使用。

如果已设置依赖关系提供程序并且项目调用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 运行之前清除了。如果在之前的步骤中预填充依赖项后仍然没有找到配置文件,那就需要编写一个最小的配置文件,而 includes 任何 <lowercaseName>-extra.cmake<name>Extra.cmake 文件,并带有 OPTIONAL 标记(所以这些文件可以缺失,并且不会生成警告)。类似地,如果没有任何 config 版本文件,那么就撰写一个非常简洁的文件来设置 PACKAGE_VERSION_COMPATIBLEPACKAGE_VERSION_EXACT 为真。这就确保所有未来的对 find_package() 的调用都会针对该依赖项使用重定向的配置文件,而不管任何版本要求。CMake 无法自动判断任何依赖项的版本,因此无法设置 PACKAGE_VERSION。当在下一步中通过 add_subdirectory() 引入依赖项时,它可能会选择用包含 PACKAGE_VERSION 的配置文件重写 CMAKE_FIND_PACKAGE_REDIRECTS_DIR 中生成 config 版本文件。依赖项还可以写入 <lowercaseName>-extra.cmake<name>Extra.cmake 文件来执行自定义处理,或者定义其常规的(已安装)包配置文件通常会定义的任何变量(许多项目不会执行任何自定义处理或设置任何变量,因此无需这样做)。如果需要,主项目可以自己编写这些文件,如果依赖项项目没有这么做的话。这样主项目就能添加旧依赖项中缺少的详细信息,这些依赖项尚未更新或无法更新以支持此功能。请参见 与 find_package() 集成 以获取示例。

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

    在 3.18 版本中添加: 可以在声明的详细信息中指定 SOURCE_SUBDIR 选项,以查看顶级目录之下的某个位置(即,SOURCE_SUBDIRExternalProject_Add() 命令使用的方式相同)。使用 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 版本中添加: CMAKE_EXPORT_FIND_PACKAGE_NAME 在调用 add_subdirectory() 之前设置为依赖项名称。

项目应力求在为其中的任何一个调用 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 明确设置为假,并在命令返回之前恢复为其原始值。开发人员通常只想验证主项目的头文件集,而不是任何依赖项中的头文件集。对 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 版中已更改: 设置了策略 CMP0168NEW 的情况下,QUIET 选项和 FETCHCONTENT_QUIET 变量没有任何影响。在这种情况下,输出仍默认安静,但详细程度由消息日志级别控制(见 CMAKE_MESSAGE_LOG_LEVEL--log-level)。

SUBBUILD_DIR

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

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

SOURCE_DIRBINARY_DIR

参数 SOURCE_DIRBINARY_DIRExternalProject_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 脚本模式 中,这允许在不使用任何构建工具或 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>) 是一个错误。请参阅 FetchContent_GetProperties() 了解如何在当前运行中确定是否已填充 <name>

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_DISCONNECTEDOFF

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() 调用中包含 FIND_PACKAGE_ARGS 关键字时,才会调用 FetchContent_Declare() 调用。如果未设置 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 是顶层项目,它直接依赖于项目 projBprojC。`projB` 和 `projC` 都可以独立构建,并且它们还都依赖于另一个项目 `projD`。`projB` 还依赖于 `projE`。此示例假设所有五个项目均位于公司 git 服务器上。每个项目的 CMakeLists.txt 可能有如下几部分

projA
include(FetchContent)
FetchContent_Declare(
  projB
  GIT_REPOSITORY [email protected]:git/projB.git
  GIT_TAG        4a89dc7e24ff212a7b5167bef7ab079d
)
FetchContent_Declare(
  projC
  GIT_REPOSITORY [email protected]:git/projC.git
  GIT_TAG        4ad4016bd1d8d5412d135cf8ceea1bb9
)
FetchContent_Declare(
  projD
  GIT_REPOSITORY [email protected]:git/projD.git
  GIT_TAG        origin/integrationBranch
)
FetchContent_Declare(
  projE
  GIT_REPOSITORY [email protected]: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 [email protected]:git/projD.git
  GIT_TAG        20b415f9034bbd2a2e8216e9a5c9e632
)
FetchContent_Declare(
  projE
  GIT_REPOSITORY [email protected]:git/projE.git
  GIT_TAG        68e20f674a48be38d60e129f600faf7d
)

FetchContent_MakeAvailable(projD projE)
projC
include(FetchContent)
FetchContent_Declare(
  projD
  GIT_REPOSITORY [email protected]:git/projD.git
  GIT_TAG        7d9a17ad2c962aa13e2fbb8043fb6b8a
)

FetchContent_MakeAvailable(projD)

以上内容应注意以下几个要点

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

  • projAFetchContent_MakeAvailable() 的调用中,`projD` 列在 `projB` 和 `projC` 之前,因此它将在 `projB` 或 `projC` 之前填充。没有要求 `projA` 这样做,这样做可确保 `projA` 完全控制将 `projD` 构建到构建中时的环境(目录属性尤为重要)。

  • 虽然 projA 规定了 projE 的内容详细信息,但是它不需要明确调用 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() 的调用指定了所有内容详细信息,而解压后的固件将被放置在当前工作目录下目录 firm ware 中。

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
)