获取内容

在 3.11 版本加入.

备注

Using Dependencies Guide 提供了对此一般主题的高级介绍。它更广泛地概述了``FetchContent`` 模块在更大范围内的位置,包括它与 find_package() 命令的关系。建议在继续阅读以下详细信息之前先阅读该指南。

概述

该模块允许在配置时通过 ExternalProject 模块支持的任何方法填充内容。鉴于 ExternalProject_Add() 在构建时下载,FetchContent 模块使内容立即可用,允许配置步骤使用命令中的内容,如 add_subdirectory()include() 或 :command:`文件`操作。

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

下面显示了声明某些依赖项的内容详细信息然后确保使用单独的调用填充它们的典型示例:

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() 命令确保指定的依赖项已被填充,无论是通过较早的调用还是通过自己填充它们。执行填充时,如果可能的话,它还会将它们添加到主构建中,以便主构建可以使用填充项目的目标等。有关如何执行这些步骤的信息,请参阅命令的文档。

使用分层项目安排时,层次结构中较高级别的项目能够覆盖项目层次结构中任何较低级别指定的内容的声明详细信息。无论发生在项目层次结构中的哪个位置,为给定依赖项声明的第一个细节优先。类似地,尝试填充依赖项的第一个调用“获胜”,随后的填充将重用第一个的结果而不是再次重复填充。请参阅演示此场景的 Examples

在某些情况下,主项目可能需要对填充进行更精确的控制,或者可能需要以一种仅通过声明的细节无法捕获的方式明确定义填充步骤。对于这种情况,可以使用较低级别的 FetchContent_GetProperties 和 FetchContent_Populate 命令。但是,它们缺少 FetchContent_MakeAvailable() 提供的更丰富的功能,因此直接使用它们应该被视为最后的手段。此类自定义步骤的典型模式如下所示:

# NOTE: Where possible, prefer to use FetchContent_MakeAvailable()
#       instead of custom logic like this

# 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 模块还支持在单个调用中定义和填充内容,而不检查内容是否已在其他地方填充。这不应该在项目中完成,但可能适合在 CMake 的脚本模式下填充内容。有关详细信息,请参阅 FetchContent_Populate()

命令

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

FetchContent_Declare() 函数记录描述如何填充指定内容的选项。如果此类详细信息已在该项目的早期记录(无论在项目层次结构中的何处),则忽略此内容和所有以后对相同内容“<name>”的调用。这种“先记录者获胜”的方法允许分层项目让父项目覆盖子项目的内容细节。

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

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

在大多数情况下,<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 也可以不带任何内容给出,这表明:command:find_package 如果:variable:FETCHCONTENT_TRY_FIND_PACKAGE_MODE 设置为 OPT_IN 或未设置,仍然可以调用。

FIND_PACKAGE_ARGS 关键字之后的所有内容都附加到 find_package() 调用,因此所有其他 <contentOptions> 必须位于 FIND_PACKAGE_ARGS 关键字之前。如果 CMAKE_FIND_PACKAGE_TARGETS_GLOBAL 变量在调用 FetchContent_Declare() 时设置为 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

如果 dependency provider 已经设置并且项目为``<name>`` 依赖调用 find_package()OVERRIDE_FIND_PACKAGE 不会阻止提供者看到那个调用.依赖提供者总是有机会拦截对 find_package() 的任何直接调用,除非该调用包含 BYPASS_PROVIDER 选项。

在 3.25 版本加入:

系统

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

FetchContent_MakeAvailable

在 3.14 版本加入.

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

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

如果未设置 <lowercaseName>_SOURCE_DIR

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

  • 在 3.24 版本加入: 如果允许,将调用 find_package(<name> [<args>...]),其中 <args>... 可能由 FIND_PACKAGE_ARGS 提供 FetchContent_Declare() 中的选项。调用 FetchContent_Declare 时 FETCHCONTENT_TRY_FIND_PACKAGE_MODE 变量的值决定了 FetchContent_MakeAvailable() 是否可以调用 find_package。如果 CMAKE_FIND_PACKAGE_TARGETS_GLOBAL 变量在调用 FetchContent_MakeAvailable() 时设置为 true,它仍然会影响在依次调用 find_package() 时创建的任何导入目标,即使该变量在以下情况下为 false公布了相应的细节。

如果提供者或 find_package() 调用不满足依赖关系,则 FetchContent_MakeAvailable() 会使用以下逻辑使依赖关系可用:

  • 如果在本次运行的早期已经填充了依赖项,请以与调用相同的方式设置 <lowercaseName>_POPULATED<lowercaseName>_SOURCE_DIR<lowercaseName>_BINARY_DIR 变量: command:FetchContent_GetProperties,然后跳过下面剩余的步骤并转到列表中的下一个依赖项。

  • 调用 FetchContent_Populate 以使用之前调用 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 运行开始时被清除。如果 FetchContent_Populate() 返回时不存在配置文件,将写入一个最小的配置文件,其中 includes 任何 <lowercaseName>-extra.cmake<name>Extra。带有 ``OPTIONAL 标志的 cmake`` 文件(因此文件可能会丢失并且不会生成警告)。同样,如果不存在配置版本文件,将编写一个非常简单的文件,将“PACKAGE_VERSION_COMPATIBLE”和“PACKAGE_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() 命令中。

项目应该旨在声明他们可能使用的所有依赖项的详细信息,然后再为它们中的任何一个调用“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)

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

FetchContent_Populate

备注

在可能的情况下,更喜欢使用 FetchContent_MakeAvailable 而不是使用此命令手动实现填充。

FetchContent_Populate(<name>)

在大多数情况下,提供给 FetchContent_Populate() 的唯一参数是 <name>。以这种方式使用时,该命令假定内容详细信息已通过先前调用 FetchContent_Declare 记录。详细信息存储在全局属性中,因此它们不受变量或目录范围之类的影响。因此,只要在调用 FetchContent_Populate() 之前声明了详细信息,那么之前在项目中的哪个位置声明都没有关系。然后使用这些保存的详细信息在私有子构建中构造对 ExternalProject_Add 的调用,以立即执行内容填充。 ExternalProject_Add() 的实现确保如果内容已经在之前的 CMake 运行中填充,该内容将被重用而不是再次重新填充它们。对于人口涉及下载内容的常见情况,下载费用仅支付一次。

内部全局属性记录何时处理了特定的内容填充请求。如果在配置运行中多次调用 FetchContent_Populate() 以获取相同的内容名称,则第二次调用将因错误而停止。项目可以而且应该在调用 FetchContent_Populate() 之前检查是否已经使用 FetchContent_GetProperties 命令处理了内容填充。

FetchContent_Populate() 将在调用者的范围内设置三个变量:

<lowercaseName>_POPULATED

这将始终被调用设置为“真”。

<lowercaseName>_SOURCE_DIR

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

<lowercaseName>_BINARY_DIR

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

<lowercaseName>_SOURCE_DIR<lowercaseName>_BINARY_DIR 变量的主要用例是在填充后立即调用:command:add_subdirectory

FetchContent_Populate(FooBar)
add_subdirectory(${foobar_SOURCE_DIR} ${foobar_BINARY_DIR})

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

FetchContent_Populate() 命令还支持允许直接指定内容详细信息而不是使用任何保存的详细信息的语法。这是较低级别的,通常应避免使用此表单,以支持使用上面概述的已保存内容详细信息。然而,在某些情况下,将内容填充作为一个独立的操作调用可能很有用(通常作为实现其他一些更高级别功能的一部分或在脚本模式下使用 CMake 时):

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

这种形式与仅提供 <name> 的形式有许多关键区别:

  • 假定已在调用“FetchContent_Populate()”时直接提供了所有必需的填充详细信息。 <name> 的任何已保存详细信息都将被忽略。

  • 不检查 <name> 的内容是否已经被填充。

  • 没有设置全局属性来记录人口已经发生。

  • 没有全局属性记录用于填充内容的源目录或二进制目录。

  • FETCHCONTENT_FULLY_DISCONNECTEDFETCHCONTENT_UPDATES_DISCONNECTED 缓存变量被忽略。

<lowercaseName>_SOURCE_DIR<lowercaseName>_BINARY_DIR 变量仍然返回给调用者,但是由于在使用这种形式时这些位置没有存储为全局属性,它们只对调用范围可用和下面而不是整个项目层次结构。使用这种形式在调用者的作用域中没有设置 <lowercaseName>_POPULATED 变量。

FetchContent_Populate() 支持的选项与 FetchContent_Declare() 的选项相同。上面显示的那几个选项要么特定于“FetchContent_Populate()”,要么它们的行为与 ExternalProject_Add 对待它们的方式略有修改:

安静

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

SUBBUILD_DIR

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

SOURCE_DIRBINARY_DIR

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

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

  • CONFIGURE_COMMAND

  • 构建命令

  • 安装命令

  • 测试命令

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

在 3.18 版本加入: 添加了对 DOWNLOAD_NO_EXTRACT 选项的支持。

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_MakeAvailable>` 或 FetchContent_Populate 相同的变量(名称)<FetchContent_Populate>()。请注意,如果调用由依赖提供者 <dependency_providers> 完成,则 SOURCE_DIRBINARY_DIR 的值可以为空。

使用 FetchContent_MakeAvailable 时很少需要此命令。它更常被用作以下模式的一部分:command:FetchContent_Populate,它确保相关变量将始终被定义,无论是否已经在项目的其他地方执行了填充:

# 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 版本加入.

备注

此命令只能由 dependency providers 调用。不支持在任何其他上下文中调用它,在这种情况下,未来的 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”)隐藏所有填充输出,除非遇到错误。如果遇到挂起下载问题,暂时关闭此选项可能有助于诊断导致问题的内容群体。

FETCHCONTENT_FULLY_DISCONNECTED

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

FETCHCONTENT_UPDATES_DISCONNECTED

与 FETCHCONTENT_FULLY_DISCONNECTED 相比,这是一个不太严格的下载/更新控制。 FETCHCONTENT_UPDATES_DISCONNECTED 不是绕过所有下载和更新逻辑,而是仅禁用更新阶段。因此,如果之前没有下载过内容,启用此选项后仍会下载内容。这可以加快配置阶段,但不如 FETCHCONTENT_FULLY_DISCONNECTED 快。默认情况下为“关闭”。

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() 如果 FetchContent_Declare() 调用包含 FIND_PACKAGE_ARGS 关键字。如果未设置“FETCHCONTENT_TRY_FIND_PACKAGE_MODE”,这也是默认行为。

总是

find_package() 可以由 FetchContent_MakeAvailable() 调用,无论 FetchContent_Declare() 调用是否包含 FIND_PACKAGE_ARGS 关键字。如果没有给出 FIND_PACKAGE_ARGS 关键字,其行为就像提供了 FIND_PACKAGE_ARGS 一样,后面没有其他参数。

从来没有

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”,则将禁用指定内容的更新。禁用个别内容的更新对于细节很少更改的内容很有用,同时仍然保留其他频繁更改的内容启用更新。

例子

典型

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

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,他们可以设置:variable:` 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)

对于 Catch2find_package() 不需要额外的参数,因此在 FIND_PACKAGE_ARGS 关键字后没有提供额外的参数。对于 googletest,它的包通常称为 GTest,因此添加参数以支持通过该名称找到它。

如果用户想禁止 FetchContent_MakeAvailable 调用任何依赖项的 find_package,即使它在声明的细节中提供了 FIND_PACKAGE_ARGS,他们可以将 FETCHCONTENT_TRY_FIND_PACKAGE_MODE 设置为 从来没有

如果项目想要指示应该从源下载和构建这两个依赖项,并且应该重定向 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 缓存变量以避免策略问题:policy:`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 可能包含如下部分:

项目
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)
项目B
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)
项目
include(FetchContent)
FetchContent_Declare(
  projD
  GIT_REPOSITORY git@mycompany.com:git/projD.git
  GIT_TAG        7d9a17ad2c962aa13e2fbb8043fb6b8a
)

# This particular version of projD requires workarounds
FetchContent_GetProperties(projD)
if(NOT projd_POPULATED)
  FetchContent_Populate(projD)

  # Copy an additional/replacement file into the populated source
  file(COPY someFile.c DESTINATION ${projd_SOURCE_DIR}/src)

  add_subdirectory(${projd_SOURCE_DIR} ${projd_BINARY_DIR})
endif()

上面需要注意几个关键点:

  • projB 和``projC`` 为``projD`` 定义了不同的内容细节,但是``projA`` 也为``projD`` 定义了一组内容细节。因为 projA 将首先定义它们,所以不会使用 projBprojC 的细节。 projA 定义的覆盖细节不需要与 projBprojC 中的任何一个相匹配,但要由更高级别的项目来确保它定义的细节仍然使对子项目有意义。

  • 在调用 FetchContent_MakeAvailable 的 projA 中,projD 列在 projBprojC 之前,以确保 projA 控制如何 ` projD` 已填充。

  • 虽然 projA 定义了 projE 的内容细节,但它不需要显式调用 FetchContent_MakeAvailable(projE)FetchContent_Populate(projD) 本身。相反,它把它留给了孩子 projB。对于更高级别的项目,通常只定义覆盖内容详细信息并将实际填充留给子项目就足够了。这样可以避免在项目层次结构的每个级别不必要地重复相同的事情。

填充内容而不将其添加到构建中

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

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 的 script mode 下载和解压缩固件 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
)