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_VERIFY
、CMAKE_TLS_CAINFO
、CMAKE_NETRC
和CMAKE_NETRC_FILE
变量现在为其相应的内容选项提供默认值,就像它们对ExternalProject_Add()
所做的那样。以前,FetchContent
模块会忽略这些变量。3.24 版本新增
FIND_PACKAGE_ARGS
此选项适用于
FetchContent_MakeAvailable()
命令可能首先尝试调用find_package()
来满足<name>
依赖项的场景。默认情况下,此类调用将简单地是find_package(<name>)
,但可以使用FIND_PACKAGE_ARGS
提供附加参数,以附加在<name>
之后。FIND_PACKAGE_ARGS
也可以在后面不跟任何内容地给出,这表示如果FETCHCONTENT_TRY_FIND_PACKAGE_MODE
设置为OPT_IN
,或未设置,则仍可以调用find_package()
。通常不适合将
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_DIR
或BINARY_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_COMPATIBLE
和PACKAGE_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_INCLUDE
或CMAKE_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_DIR
和BINARY_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
BUILD_COMMAND
INSTALL_COMMAND
TEST_COMMAND
在这种形式下,
FETCHCONTENT_FULLY_DISCONNECTED
和FETCHCONTENT_UPDATES_DISCONNECTED
变量以及策略CMP0170
将被忽略。当这种形式的
FetchContent_Populate()
返回时,以下变量将在调用者范围内设置<lowercaseName>_SOURCE_DIR
返回时可找到填充内容的位置。
<lowercaseName>_BINARY_DIR
一个最初用于作为相应构建目录的目录,但在使用此形式的命令时可能不相关。
如果在 CMake 脚本模式中使用
FetchContent_Populate()
,请注意,该实现设置了一个子构建,因此需要提供 CMake 生成器和构建工具。如果默认情况下找不到这些工具,则可能需要在调用脚本的命令行上适当地设置CMAKE_GENERATOR
和CMAKE_MAKE_PROGRAM
变量。3.30 版本更改:如果策略
CMP0168
设置为NEW
,则不使用子构建。在 CMake 脚本模式中,这允许在没有任何构建工具或 CMake 生成器的情况下调用FetchContent_Populate()
。3.18 版本新增:添加了对
DOWNLOAD_NO_EXTRACT
选项的支持。
该命令支持另一种形式,尽管它不应再使用
FetchContent_Populate(<name>)3.30 版本更改:此形式已弃用。策略
CMP0169
为仍需要使用此形式的项目提供了向后兼容性,但项目应更新为使用FetchContent_MakeAvailable()
。在这种形式中,给定
FetchContent_Populate()
的唯一参数是<name>
。当以这种方式使用时,该命令假定内容详细信息已由先前对FetchContent_Declare()
的调用记录。详细信息存储在全局属性中,因此它们不受变量或目录范围等事物的影响。因此,内容详细信息之前在项目中的何处声明无关紧要,只要它们在调用FetchContent_Populate()
之前已声明。然后,这些保存的详细信息将用于使用基于ExternalProject_Add()
的方法填充内容(有关如何执行此操作的重要行为方面,请参阅策略CMP0168
)。当这种形式的
FetchContent_Populate()
返回时,以下变量将在调用者范围内设置
<lowercaseName>_POPULATED
此调用将始终设置为
TRUE
。<lowercaseName>_SOURCE_DIR
返回时可找到填充内容的位置。
<lowercaseName>_BINARY_DIR
一个用于作为相应构建目录的目录。
这三个变量的值也可以使用
FetchContent_GetProperties()
命令从项目层次结构中的任何位置检索。如果内容已在之前的 CMake 运行中填充,则此实现可确保重用该内容,而不是再次重新填充。对于填充涉及下载内容的常见情况,下载成本只需支付一次。但请注意,在单个 CMake 运行中多次使用相同的
<name>
调用FetchContent_Populate(<name>)
是错误的。有关如何确定是否已在当前运行中执行<name>
的填充,请参阅FetchContent_GetProperties()
。
- FetchContent_GetProperties¶
当使用保存的内容详细信息时,对
FetchContent_MakeAvailable()
或FetchContent_Populate()
的调用会在全局属性中记录信息,可以随时查询。此信息可能包括与内容相关的源目录和二进制目录,以及在当前配置运行期间是否已处理内容填充。FetchContent_GetProperties( <name> [SOURCE_DIR <srcDirVar>] [BINARY_DIR <binDirVar>] [POPULATED <doneVar>] )
SOURCE_DIR
、BINARY_DIR
和POPULATED
选项可用于指定应检索哪些属性。每个选项都接受一个值,该值是要存储该属性的变量的名称。但大多数情况下,只给定<name>
,在这种情况下,调用将设置与调用FetchContent_MakeAvailable(name)
或FetchContent_Populate(name)
相同的变量。请注意,如果调用由 依赖项提供程序 完成,则SOURCE_DIR
和BINARY_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_DIR
和BINARY_DIR
参数可用于指定FetchContent_GetProperties()
应该为其相应参数返回的值。仅当SOURCE_DIR
和BINARY_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() 集成¶
对于前面的示例,如果用户希望在尝试从源代码下载和构建 googletest
和 Catch2
之前,首先尝试通过 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
是顶层项目,它直接依赖于项目 projB
和 projC
。 projB
和 projC
都可以独立构建,并且它们都依赖于另一个项目 projD
。 projB
另外依赖于 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)
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
)
FetchContent_MakeAvailable(projD)
以上几点需要注意
projB
和projC
为projD
定义了不同的内容详细信息,但projA
也为projD
定义了一组内容详细信息。由于projA
将首先定义它们,因此来自projB
和projC
的详细信息将不被使用。projA
定义的覆盖详细信息不需要与projB
或projC
中的任何一个匹配,但由更高层级的项目来确保它定义的详细信息对于子项目仍然有意义。在
projA
对FetchContent_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()
的调用指定了所有内容详细信息,解压后的固件将放置在当前工作目录下的 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
)