26. 与 FetchContent 结合:在 Presets 中配置外部依赖管理
说实话,CMake 的 FetchContent 模块是我这几年用得最多的功能之一。以前做项目,最头疼的就是第三方库的管理——要么手动下载源码,要么用 git submodule 搞得一团糟。后来有了 FetchContent,直接在构建时拉取依赖,干净利落。
但问题来了:如果团队里每个人都各自写 FetchContent 的配置,版本不一致怎么办?有人用 v1.0,有人用 master 分支,这项目迟早要炸。嗯,这时候 Presets 就派上用场了。
为什么要把 FetchContent 放进 Presets?
我个人习惯把外部依赖的版本、URL、更新策略这些信息统一放在 Presets 里。原因很简单:
- 版本统一:所有人用同一个 Presets 文件,依赖版本自然一致
- 构建可复现:换台机器,拉下来就能编译,不用到处找依赖
- 团队协作:新同事入职,直接跑 cmake --preset=dev,依赖自动搞定
你想想看,如果每个开发者都在自己的 CMakeLists.txt 里写死版本号,哪天想升级依赖,得挨个通知所有人改代码。这效率,我反正是受不了。
基础用法:在 Presets 中定义依赖版本
最简单的做法,是把 FetchContent 的版本号定义在 Presets 的 cacheVariables 里。举个例子:
{
"version": 6,
"configurePresets": [
{
"name": "dev",
"displayName": "开发配置",
"binaryDir": "${sourceDir}/build/dev",
"cacheVariables": {
"FETCHCONTENT_QUIET": "OFF",
"SPDLOG_VERSION": "v1.12.0",
"NLOHMANN_JSON_VERSION": "v3.11.2",
"CURL_VERSION": "7.88.1"
}
},
{
"name": "release",
"displayName": "发布配置",
"binaryDir": "${sourceDir}/build/release",
"cacheVariables": {
"FETCHCONTENT_QUIET": "ON",
"SPDLOG_VERSION": "v1.11.0",
"NLOHMANN_JSON_VERSION": "v3.10.5",
"CURL_VERSION": "7.86.0"
}
}
]
}
然后在 CMakeLists.txt 里这样用:
include(FetchContent)
# spdlog
set(SPDLOG_VERSION "v1.12.0" CACHE STRING "spdlog 版本")
FetchContent_Declare(
spdlog
GIT_REPOSITORY https://github.com/gabime/spdlog.git
GIT_TAG ${SPDLOG_VERSION}
)
FetchContent_MakeAvailable(spdlog)
# nlohmann_json
set(NLOHMANN_JSON_VERSION "v3.11.2" CACHE STRING "json 版本")
FetchContent_Declare(
nlohmann_json
GIT_REPOSITORY https://github.com/nlohmann/json.git
GIT_TAG ${NLOHMANN_JSON_VERSION}
)
FetchContent_MakeAvailable(nlohmann_json)
注意看,我在 CMakeLists.txt 里用了 CACHE STRING 来定义变量。这样 Presets 里的值会覆盖默认值,而如果没指定 Presets,也会有个合理的默认版本。
进阶:用 Presets 控制依赖的获取策略
FetchContent 有几个关键变量可以控制行为:
| 变量名 | 作用 | 推荐值 |
|---|---|---|
| FETCHCONTENT_QUIET | 是否静默下载 | 开发时 OFF,发布时 ON |
| FETCHCONTENT_UPDATES_DISCONNECTED | 是否禁用更新检查 | 发布时 ON,加速构建 |
| FETCHCONTENT_SOURCE_DIR_xxx | 指定本地源码路径 | 离线环境或调试时用 |
我曾经在 CI 环境里遇到过一个问题:每次构建都重新拉取依赖,慢得要命。后来在 Presets 里加了 FETCHCONTENT_UPDATES_DISCONNECTED 为 ON,构建时间直接砍半。
{
"configurePresets": [
{
"name": "ci",
"displayName": "CI 构建",
"cacheVariables": {
"FETCHCONTENT_QUIET": "ON",
"FETCHCONTENT_UPDATES_DISCONNECTED": "ON",
"SPDLOG_VERSION": "v1.11.0",
"NLOHMANN_JSON_VERSION": "v3.10.5"
}
}
]
}
实战:多依赖管理的 Presets 设计
我参与过一个嵌入式项目,依赖了 8 个第三方库。如果每个库都单独写 FetchContent,CMakeLists.txt 会变得又臭又长。我的做法是:
- 在 Presets 里定义所有依赖的版本和 URL
- 在 CMakeLists.txt 里写一个通用的宏来批量处理
- 不同 Presets 可以切换依赖的版本组合
Presets 配置示例:
{
"configurePresets": [
{
"name": "stable",
"cacheVariables": {
"DEP_SPDLOG_VERSION": "v1.11.0",
"DEP_JSON_VERSION": "v3.10.5",
"DEP_CURL_VERSION": "7.86.0",
"DEP_OPENSSL_VERSION": "1.1.1t"
}
},
{
"name": "bleeding",
"cacheVariables": {
"DEP_SPDLOG_VERSION": "v1.12.0",
"DEP_JSON_VERSION": "v3.11.2",
"DEP_CURL_VERSION": "7.88.1",
"DEP_OPENSSL_VERSION": "3.1.0"
}
}
]
}
CMakeLists.txt 里用宏简化:
macro(fetch_dep name repo tag_var)
set(${tag_var} "main" CACHE STRING "${name} 版本")
FetchContent_Declare(
${name}
GIT_REPOSITORY ${repo}
GIT_TAG ${${tag_var}}
)
FetchContent_MakeAvailable(${name})
endmacro()
fetch_dep(spdlog https://github.com/gabime/spdlog.git DEP_SPDLOG_VERSION)
fetch_dep(nlohmann_json https://github.com/nlohmann/json.git DEP_JSON_VERSION)
fetch_dep(curl https://github.com/curl/curl.git DEP_CURL_VERSION)
这样,切换 Presets 就能一键切换所有依赖的版本组合。团队里有人想升级依赖,只需要改 Presets 文件,然后大家同步一下就行。
SVG 流程图:Presets + FetchContent 协作关系
避坑指南:我踩过的几个坑
用 FetchContent + Presets 组合,有几个地方特别容易出问题:
- 版本号写死 vs 变量引用:千万别在 CMakeLists.txt 里写死版本号,否则 Presets 根本控制不了。一定要用
CACHE STRING变量。 - 依赖冲突:两个库依赖同一个第三方库的不同版本怎么办?我遇到过 spdlog 和 fmt 的版本冲突。解决方案是用
FetchContent_GetProperties检查是否已下载。 - 网络问题:内网环境拉不了 GitHub。可以在 Presets 里设置
FETCHCONTENT_SOURCE_DIR_xxx指向本地镜像。
核心要点:Presets 负责「声明」依赖的版本和策略,CMakeLists.txt 负责「执行」下载和构建。两者各司其职,团队协作时只需要维护 Presets 文件即可。
说实话,这套组合拳打好了,团队里再也不会出现「我本地能编译,你那边怎么不行」的尴尬情况。依赖版本统一了,构建环境统一了,剩下的就是安心写代码。