7、依赖覆盖与缓存:FETCHCONTENT_UPDATES_DISCONNECTED、FETCHCONTENT_QUIET、SOURCE_DIR与BINARY_DIR自定义

依赖管理做到一定程度,你就会发现——网络不是万能的

我见过太多团队,每次 cmake 配置都要去 GitHub 拉一遍源码。项目一多,依赖一重,光下载就能耗掉十分钟。更别提网络不稳定的时候,构建直接卡死。嗯,这节课我们就来解决这个问题。

7.1 离线模式:FETCHCONTENT_UPDATES_DISCONNECTED

这个变量名字很长,但意思很简单——断开更新

说白了,就是告诉 CMake:「别去网上拉了,用本地已经下载好的那份就行。」

核心用法:

# 在调用 FetchContent 之前设置
set(FETCHCONTENT_UPDATES_DISCONNECTED ON)

# 或者通过命令行传入
# cmake -DFETCHCONTENT_UPDATES_DISCONNECTED=ON ..

我个人习惯在 CI 环境里把这个开关打开。因为 CI 每次都是全新构建,依赖一旦下载好,后续的构建就不需要再联网了。你想想看,每次构建都去拉一遍 googletest,这不是浪费带宽吗?

小技巧:如果你在开发机上网络不稳定,可以先在能联网的时候跑一次完整构建,之后把这个开关打开。后续的 cmake 配置会直接复用本地缓存,速度飞快。

7.2 静默模式:FETCHCONTENT_QUIET

默认情况下,FetchContent 会在终端打印一大堆日志——下载进度、解压信息、补丁应用情况……

说实话,这些信息在调试时很有用。但日常构建时,看着满屏的「Downloading...」「Extracting...」,其实挺烦的。

# 开启静默模式
set(FETCHCONTENT_QUIET ON)

# 这样 FetchContent 就不会打印下载相关的日志了
# 但错误信息仍然会输出

我曾经在一个大型项目里,依赖数量超过 20 个。每次 cmake 配置,终端输出能刷上千行。后来我加了这个开关,输出瞬间清爽了。只有真正出错了,它才会说话。

注意:FETCHCONTENT_QUIET 只影响 FetchContent 自身的日志。你项目中其他 CMake 代码的 message() 输出不受影响。

7.3 自定义目录:SOURCE_DIR 与 BINARY_DIR

默认情况下,FetchContent 会把源码下载到 CMAKE_BINARY_DIR/_deps/ 下面。这个路径其实挺深的,而且每次清理构建目录,依赖也得重新下载。

我个人更倾向于把依赖放到项目外部,这样多个项目可以共享同一份源码。

7.3.1 自定义 SOURCE_DIR

FetchContent_Declare(
  mylogger
  GIT_REPOSITORY https://github.com/example/mylogger.git
  GIT_TAG v1.2.0
  SOURCE_DIR ${CMAKE_SOURCE_DIR}/third_party/mylogger
)

这样做的好处很明显:源码就在项目目录下,你可以直接查看、修改,甚至用 IDE 打开调试。我有个项目就是这么干的,团队成员可以直接在源码里加断点,排查问题方便多了。

7.3.2 自定义 BINARY_DIR

FetchContent_Declare(
  mylogger
  GIT_REPOSITORY https://github.com/example/mylogger.git
  GIT_TAG v1.2.0
  BINARY_DIR ${CMAKE_BINARY_DIR}/deps_build/mylogger
)

BINARY_DIR 控制的是构建产物放在哪。默认情况下,每个依赖的构建目录都在 _deps/ 下面,结构比较乱。自定义之后,你可以把不同依赖的构建产物分门别类放好。

最佳实践组合:

# 把源码放到项目外部共享目录
set(FETCHCONTENT_BASE_DIR ${CMAKE_SOURCE_DIR}/../shared_deps)

# 开启离线模式,避免重复下载
set(FETCHCONTENT_UPDATES_DISCONNECTED ON)

# 静默模式,减少日志干扰
set(FETCHCONTENT_QUIET ON)

FetchContent_Declare(
  googletest
  GIT_REPOSITORY https://github.com/google/googletest.git
  GIT_TAG release-1.12.0
)

7.4 知识体系图

下面这张图帮你理清这几个变量的关系和作用场景:

依赖覆盖与缓存核心逻辑 FetchContent 依赖管理 FETCHCONTENT_UPDATES_DISCONNECTED 断开网络更新,复用本地缓存 FETCHCONTENT_QUIET 减少日志输出,只显示错误信息 SOURCE_DIR / BINARY_DIR 自定义 控制源码存放位置和构建产物目录 三者可独立使用,也可组合使用,互不冲突

7.5 避坑指南

这几个变量虽然简单,但用不好也会踩坑。我分享几个亲身经历:

  • FETCHCONTENT_UPDATES_DISCONNECTED 的陷阱:我曾经在开发新功能时,改了依赖的版本号,但忘了关这个开关。结果 CMake 一直用旧版本,我排查了整整两个小时。后来养成了习惯——改版本号之前,先看一眼这个开关是不是开着。
  • SOURCE_DIR 的路径问题:如果你把 SOURCE_DIR 指向了一个已经存在的目录,CMake 不会清空它。这意味着如果你手动修改过里面的文件,下次构建时这些修改仍然存在。嗯,有时候这是好事,有时候是噩梦。
  • BINARY_DIR 的清理:自定义 BINARY_DIR 之后,make clean 可能不会清理这个目录。你需要手动删除,或者写一个自定义的 clean 目标。

我的建议:在项目根目录放一个 CMakePresets.json,把常用的组合配置写成 preset。这样团队成员可以直接用,不用每个人手动设变量。

7.6 实际项目中的组合用法

最后,我分享一个我在生产项目中实际使用的配置。这个项目有 15 个依赖,分布在不同的 Git 仓库:

# 项目级别的 CMakeLists.txt 开头
# 开发环境:使用本地缓存,减少网络请求
if(NOT DEFINED FETCHCONTENT_UPDATES_DISCONNECTED)
  set(FETCHCONTENT_UPDATES_DISCONNECTED ON)
endif()

# 静默模式:除非调试,否则不打印下载日志
if(NOT DEFINED FETCHCONTENT_QUIET)
  set(FETCHCONTENT_QUIET ON)
endif()

# 统一存放依赖源码到项目外部
set(FETCHCONTENT_BASE_DIR ${CMAKE_SOURCE_DIR}/../deps_cache)

# 然后正常声明依赖
FetchContent_Declare(
  spdlog
  GIT_REPOSITORY https://github.com/gabime/spdlog.git
  GIT_TAG v1.11.0
)

FetchContent_Declare(
  nlohmann_json
  GIT_REPOSITORY https://github.com/nlohmann/json.git
  GIT_TAG v3.11.2
)

# ... 其他依赖

FetchContent_MakeAvailable(spdlog nlohmann_json ...)

这套配置跑下来,首次构建需要下载所有依赖,大概 3-5 分钟。后续的构建,只要依赖版本不变,配置时间基本在 10 秒以内。你想想看,这效率提升有多大?

好了,关于依赖覆盖和缓存,就聊到这里。记住这三个变量的用途,你的 CMake 构建会变得更可控、更高效。


公众号:蓝海资料掘金营,微信 deep3321