#cmake # 1. Overview - `FetchContent` 在 configure 阶段执行,而 `ExternalProject_Add` 是在 build 阶段下载 - 优势:content 马上可用,在 configure 阶段可以对 content 使用 `add_subdirectory()`,`include()`或者 `file()` 等操作 - 声明与执行 population 是分开的: - `FetchContent_MakeAvailable` :保证依赖可用(之前已经下载过,或者现在下载) ```c++ 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) ``` - lower level API(不优先使用): - `FetchContent_GetProperties()` - `FetchContent_Populate()` ```cpp # 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() ``` -------- # 2. Commands ## FetchContent_Declare ```cpp FetchContent_Declare( <name> <contentOptions>... [SYSTEM] [OVERRIDE_FIND_PACKAGE | FIND_PACKAGE_ARGS args...] ) ``` - 声明了如何获取指定的 content - 如果相同 `name` 的之前已经声明过,则忽略本次调用。 - name **大小写敏感**,不能包含空格 - `<contentOptions>` 可以是 `ExternalProject_Add` 能理解的以下选项 - download - update - patch - configure, build, install 和 test 被禁用,会被忽略。`SOURCE_SUBDIR`选项是个例外。 - 示例: ```cpp 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 ) ``` - 优先使用 commit hash 而非 branch / tag name,这样更安全精确。 > The details are stored in a global property, so they are unaffected by things like variable or directory scope ## FetchContent_MakeAvailable ```cpp FetchContent_MakeAvailable(<name1> [<name2>...]) ``` - 3.14 版本引入 - 调用返回后,可以保证目标依赖已经可用。 - 如果依赖已经下载,会设置以下变量: - `<lowercaseName>_POPULATED` - `<lowercaseName>_SOURCE_DIR` - `<lowercaseName>_BINARY_DIR` - 如果没有下载,会调用 `FetchContent_Populate()` 填充内容 - `FETCHCONTENT_SOURCE_DIR_<uppercaseName>` 可以指定直接使用该目录的 content,不进行下载和更新 - 如果依赖顶级目录有一个 `CMakeLists.txt` 文件,会调用 `add_subdirectory()` 添加它到 main build。 - 3.18 版本支持在 declare 时通过 `SOURCE_SUBDIR` 选项指定 `CMakeLists.txt` 所在的子目录。 - 3.25 版本如果 declare 里有 `SYSTEM` 关键字,也会传递给 `add_subdirectory()` - 应该**先 declare 完所有的依赖**,再调用 `FetchContent_MakeAvailable` - 避免依赖间有依赖关系,例如 A 依赖 B,且 A 也对 B 使用 `FetchContent`,先 declare 可以保证我们的 declare 优先级更高。 - [ ] `CMAKE_VERIFY_INTERFACE_HEADER_SETS` ## FetchContent_Populate - 优先使用 `FetchContent_MakeAvailable` ```cpp FetchContent_Populate(<name>) ``` - 执行时调用 `ExternalProject_Add()` in a private sub-build 来执行 population。 - `ExternalProject_Add()` 可以保证不会重复 populate - configure 阶段如果对相同依赖调用多次 `FetchContent_Populate` 会报错 - 通过一个 global property 来检测 - 应该使用 `FetchContent_GetProperties()` 来检查避免调用多次 - 同 `FetchContent_MakeAvailable` 也会设置变量 - 主要的应用场景时 poplulate 后马上调用 `add_subdirectory` ```cpp FetchContent_Populate(FooBar) add_subdirectory(${foobar_SOURCE_DIR} ${foobar_BINARY_DIR}) ``` ### explicit details - 可以直接指定 content details(而不使用 saved details) ```cpp FetchContent_Populate( <name> [QUIET] [SUBBUILD_DIR <subBuildDir>] [SOURCE_DIR <srcDir>] [BINARY_DIR <binDir>] ... ) ``` - 这样方式不会检查是否已经 polulated 过 - 也不会通知 global property 已经 polulated 过 - `<lowercaseName>_SOURCE_DIR` 和 `<lowercaseName>_BINARY_DIR` 变量只在 calling scope 内有效,而非整个 project。 - 无法识别的 options 会传递给 `ExternalProject_Add()`,禁止的 options - `CONFIGURE_COMMAND` - `BUILD_COMMAND` - `INSTALL_COMMAND` - `TEST_COMMAND` ## FetchContent_GetProperties - 对 saved content details 进行 popluate 后会记录信息到 global property,随后可以被查询 ```cpp FetchContent_GetProperties( <name> [SOURCE_DIR <srcDirVar>] [BINARY_DIR <binDirVar>] [POPULATED <doneVar>] ) ``` - 使用 `FetchContent_MakeAvailable()` 后很少需要调用它,更多是跟 `FetchContent_Populate()` 一起使用 ```cpp # 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 版本引入。 ------ # 3. Variables - 许多 cache variables 会影响 populate 的行为。 - **FETCHCONTENT_BASE_DIR** - 默认的下载目录是 `${CMAKE_BINARY_DIR}/_deps`, 一般不需要更改 - **FETCHCONTENT_QUIET** - 默认 `ON` - 因此 polulate 过程中的输出 - **FETCHCONTENT_FULLY_DISCONNECTED** - 默认 OFF,开启后不会尝试 download 和 update 任何 content - 场景:已经手动下载好了 - **FETCHCONTENT_UPDATES_DISCONNECTED** - 只禁止 update - **FETCHCONTENT_TRY_FIND_PACKAGE_MODE** - 3.24新引入 - **`FETCHCONTENT_SOURCE_DIR_<uppercaseName>`** - 设定后不会对特定的 content 执行 download / update,而是使用指定的目录 - 场景:修改依赖 - **`FETCHCONTENT_UPDATES_DISCONNECTED_<uppercaseName>`** - 禁止对指定 content 的 update ----- # 4. Examples ## Typical Case ```cpp 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() 集成 - [ ] 3.24 后 ## 设置 CMakeLists.txt 的目录 ```cpp 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) ``` ## 复杂的依赖层次结构 - A 依赖 B,C; B,C 都依赖 D ```cpp 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) ``` ## Populating Content Without Adding It To The Build ```cpp 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) ``` - configure ```shell cmake -DCMAKE_TOOLCHAIN_FILE=_deps/mycom_toolchains-src/toolchain_arm.cmake /path/to/src ``` - 执行时先下载和解压 tarball,`CMAKE_TOOLCHAIN_FILE` 直到到达 `project()` 命令时才使用。 ## Populating Content In CMake Script Mode ```cpp # 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 ) ``` -----