25、依赖管理:FetchContent模块,ExternalProject_Add,vcpkg与Conan集成

依赖管理,说白了就是解决「别人的代码怎么放进我的项目里」这个问题。

我刚入行那会儿,C++项目的依赖管理基本靠手动下载、解压、配置路径。项目小还好说,一旦涉及三五个第三方库,那简直就是噩梦。版本冲突、路径写死、同事环境不一样……这些坑我全都踩过。

CMake 发展到今天,已经给了我们好几套方案。我个人把它们分成两类:构建时拉取包管理器集成。今天咱们就把这两类讲透。

一、FetchContent:轻量级的内置方案

FetchContent 是 CMake 3.11 引入的模块。它的思路很简单——在配置阶段就把源码下载下来,然后直接 add_subdirectory 引入。

我特别喜欢它的地方在于:零额外工具。只要你有 CMake,就能用。

核心用法:

include(FetchContent)

FetchContent_Declare(
  fmt
  GIT_REPOSITORY https://github.com/fmtlib/fmt.git
  GIT_TAG        10.1.0
)

FetchContent_MakeAvailable(fmt)

# 之后直接使用 fmt::fmt 即可
target_link_libraries(my_app PRIVATE fmt::fmt)

嗯,这里要注意:FetchContent_MakeAvailable 会帮你处理下载、配置、构建的全过程。下载的内容默认放在 ${CMAKE_BINARY_DIR}/_deps 下。

我的习惯:如果项目依赖不超过 5 个,且都是开源小库,我会优先用 FetchContent。它简单、直接,不需要额外学习成本。

我曾经在一个嵌入式项目里用过它。当时需要引入一个 JSON 解析库,团队里有人想用 vcpkg,但嵌入式工具链不支持。最后我用 FetchContent 三行代码搞定,大家都很满意。

二、ExternalProject_Add:更强大的构建时下载

ExternalProject_Add 比 FetchContent 更早出现(CMake 2.8 就有了)。它的特点是:在构建阶段执行下载和编译

这意味着什么?意味着你可以用它处理那些不支持 CMake 的项目,或者需要自定义构建步骤的库。

include(ExternalProject)

ExternalProject_Add(
  project_zlib
  GIT_REPOSITORY https://github.com/madler/zlib.git
  GIT_TAG        v1.2.13
  CMAKE_ARGS     -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/install
  BUILD_COMMAND  make
  INSTALL_COMMAND make install
)

避坑指南:ExternalProject_Add 的变量作用域是独立的。你没法直接在外部 target 里链接它生成的库。通常需要配合 add_library(imported) 或者设置 BUILD_BYPRODUCTS 来打通。

我曾经在一个项目里用 ExternalProject_Add 编译 OpenSSL。那玩意儿构建系统用的是 Perl + Makefile,CMake 根本管不了。但 ExternalProject_Add 可以——我只需要告诉它「下载、运行配置脚本、make、make install」就行了。

特性 FetchContent ExternalProject_Add
执行阶段 配置阶段 构建阶段
变量作用域 与主项目共享 独立作用域
适用场景 CMake 友好的库 任意构建系统
依赖传递 自动处理 需手动配置

三、vcpkg:微软出品的包管理器

vcpkg 是微软维护的 C++ 包管理器。它的理念是「一次安装,全局可用」。

我个人觉得 vcpkg 最大的优势是生态丰富——目前有超过 2000 个库。而且它支持 Windows、Linux、macOS,甚至交叉编译。

使用 vcpkg 的典型流程:

  1. 安装 vcpkg 并集成到 CMake
  2. vcpkg install fmt 安装库
  3. 在 CMake 中通过 find_package 使用
# 在 CMakeLists.txt 中
find_package(fmt CONFIG REQUIRED)
target_link_libraries(my_app PRIVATE fmt::fmt)

我的建议:团队开发时,可以把 vcpkg 的 vcpkg.json 文件纳入版本控制。这样所有成员都能用同一套依赖版本,避免「我机器上能编译」的尴尬。

vcpkg 的 manifest 模式长这样:

{
  "name": "my-project",
  "version": "1.0.0",
  "dependencies": [
    "fmt",
    "spdlog",
    "nlohmann-json"
  ]
}

然后执行 cmake --preset=default,vcpkg 会自动读取这个文件并安装依赖。

四、Conan:去中心化的包管理方案

Conan 是另一个流行的 C++ 包管理器。和 vcpkg 不同,Conan 是去中心化的——你可以搭建自己的私有仓库。

Conan 的核心概念是 profilerecipe。Profile 描述编译环境(编译器、架构、标准库等),Recipe 描述如何构建一个库。

# conanfile.txt
[requires]
fmt/10.1.0
spdlog/1.12.0

[generators]
CMakeDeps
CMakeToolchain

然后在 CMake 中:

# 由 CMakeDeps 生成
find_package(fmt CONFIG REQUIRED)
find_package(spdlog CONFIG REQUIRED)

target_link_libraries(my_app PRIVATE fmt::fmt spdlog::spdlog)

Conan 的独特优势:

  • 支持多配置(Debug/Release 共存)
  • 可以自定义构建选项
  • 私有仓库适合企业级项目

我曾经在一个跨平台项目里用 Conan。项目需要在 Windows 上用 MSVC,在 Linux 上用 GCC,在 macOS 上用 Clang。Conan 的 profile 机制让我可以轻松切换——每个平台一个 profile 文件,构建时指定即可。

五、如何选择?

你想想看,这四种方案各有侧重。我一般这样选:

  • 个人项目或小团队:FetchContent 就够了,简单直接
  • 需要编译非 CMake 项目:ExternalProject_Add 是唯一选择
  • 团队协作,统一环境:vcpkg 的 manifest 模式很合适
  • 企业级项目,需要私有仓库:Conan 是首选

其实没有绝对的好坏。我见过有人用 FetchContent 管理上百个依赖,也见过用 Conan 管理三个库的小项目。关键是适合你的场景

最后提醒一句:不管用哪种方案,一定要把依赖版本锁定。我曾经因为用了 GIT_TAG master,结果某天上游库改了接口,整个项目编译失败。从那以后,我所有依赖都指定具体版本号。

CMake 依赖管理方案对比 FetchContent 配置阶段下载 ExternalProject 构建阶段下载 vcpkg 全局包管理 Conan 去中心化管理 ✅ 零额外工具 ✅ 自动传递依赖 ⚠️ 仅支持CMake库 ✅ 任意构建系统 ⚠️ 变量独立作用域 ⚠️ 需手动链接 ✅ 2000+ 库 ✅ Manifest模式 ✅ 跨平台支持 ✅ 私有仓库 ✅ Profile机制 ✅ 多配置共存 推荐场景 个人项目 小团队 非CMake项目 自定义构建 团队协作 统一环境 企业级项目 私有仓库

依赖管理这件事,说白了就是把不确定性变成确定性。不管用哪种工具,目标都一样:让项目在任何机器上都能可复现地构建。选一个你顺手的,用熟它,比什么都强。

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