#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
)
```
-----