46. 12.2 现代C++包管理整合

引言:施工队的”供应链升级”

在上节课(12.1)里,我们的 CMake “施工队长”拿到了一本《标准作业手册》——学会了用 CMake Presets 统一管理配置、构建和测试流程,让团队协作变得井井有条。但不知你想过没有:队长手里的图纸(CMakeLists.txt)再漂亮,如果水泥、钢筋、玻璃这些原材料(第三方库)的供应链出了问题,整个工程照样会停摆。

在早期的 C++ 项目中,”采购”第三方库往往是个体力活:手动下载源码、解压、编译、安装到系统目录,再让 CMake 通过 find_package 去”碰运气”寻找。这种方式就像施工队长每次都要亲自去矿山挖铁矿、去森林砍木头——低效且不可复现

现代 C++ 生态已经有了成熟的”建材供应商”:vcpkgConanCPM.cmake 等工具。它们不是 CMake 的替代品,而是 CMake 的最佳搭档。这节课,我们就来看看如何让施工队长把这些供应商无缝接入自己的工作流,实现从”手动采购”到”供应链自动化”的跃迁。

vcpkg Manifest 模式:声明式依赖清单

如果你在 Windows 或跨平台 C++ 社区里混,一定听说过 vcpkg。这个由微软发起的 C++ 包管理器,经过多年的发展,已经从早期的”全局安装”模式进化出了更现代的 Manifest 模式(清单模式)。

从全局安装到项目级清单

在传统的 vcpkg 用法中,你需要在系统全局安装库:

vcpkg install boost jsoncpp

这会导致两个问题:一是不同项目可能需要不同版本的 Boost,全局只能装一个;二是团队成员每台机器都要手动执行安装命令,极易出现”我电脑上能编,你电脑上找不到包”的尴尬。

Manifest 模式借鉴了 Node.js 的 package.json 和 Rust 的 Cargo.toml 思想:在项目根目录下放一个 vcpkg.json声明项目需要什么依赖,vcpkg 会在构建时自动把它们下载、编译并集成到 CMake 中。

{
  "name": "my-awesome-app",
  "version": "1.0.0",
  "dependencies": [
    "fmt",
    "spdlog",
    {
      "name": "openssl",
      "version&gt=": "3.0.0"
    }
  ],
  "overrides": [
    {
      "name": "fmt",
      "version": "9.1.0"
    }
  ]
}

这段 JSON 就像是一份采购清单:项目需要 fmtspdlog 和版本不低于 3.0 的 openssl,并且不管 vcpkg 库里 fmt 更新到了 10.x,我们都锁定用 9.1.0 版本(通过 overrides)。

与 CMake 的集成

有了清单, CMake 怎么知道去 vcpkg 的仓库里”进货”呢?答案是 工具链文件(Toolchain File)。在配置项目时,指定 vcpkg 提供的工具链:

cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE=[vcpkg-root]/scripts/buildsystems/vcpkg.cmake

如果你使用了 12.1 节学过的 CMake Presets,可以把这行写进 configurePresets 里,让工具链自动加载:

{
  "name": "default",
  "hidden": false,
  "generator": "Ninja",
  "toolchainFile": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake"
}

一旦集成完成, CMakeLists.txt 里就可以像平常一样使用 find_package,vcpkg 会自动处理好头文件路径和库链接,施工队长完全不用操心钢筋从哪个港口运来的。

find_package(fmt CONFIG REQUIRED)
find_package(spdlog CONFIG REQUIRED)

add_executable(my_app main.cpp)
target_link_libraries(my_app PRIVATE fmt::fmt spdlog::spdlog)

Conan 2.x:Pythonic 的依赖工程化

如果说 vcpkg 像是一家”标准化建材超市”,那么 Conan 则更像是一家支持深度定制的工业品供应商。Conan 2.x 在 2023 年正式发布后,与 CMake 的集成方式发生了质的飞跃,彻底告别了 1.x 时代那种繁琐的 conan_basic_setup() 宏。

Conan 2.x 的架构变革

Conan 2.x 采用了更清晰的分层设计。它不再直接修改你的 CMakeLists.txt,而是通过生成器(Generators)输出标准的 CMake 配置文件,让 CMake “自然而然”地找到包。最常用的一对组合是:

  • CMakeDeps:为每个依赖生成 XXX-config.cmake 文件,供 find_package 使用。
  • CMakeToolchain:生成一个工具链文件,处理编译器标志、标准库、构建类型等配置。

conanfile.py 与 CMake 的”双剑合璧”

在 Conan 2.x 中,推荐用 Python 脚本 conanfile.py 来描述依赖,而不是简单的 conanfile.txt。这样可以精细控制选项(options)、设置(settings)和依赖版本。

from conan import ConanFile
from conan.tools.cmake import cmake_layout, CMakeToolchain, CMakeDeps

class MyAppConan(ConanFile):
    name = "my_app"
    version = "1.0"
    settings = "os", "compiler", "build_type", "arch"
    requires = "fmt/10.1.1", "spdlog/1.12.0", "openssl/3.1.2"
    
    def generate(self):
        deps = CMakeDeps(self)
        deps.generate()
        tc = CMakeToolchain(self)
        tc.generate()
    
    def layout(self):
        cmake_layout(self)

这份 conanfile.py 就是我们的”采购合同书”:它规定了要哪些库、什么版本,并且通过 generate() 方法告诉 Conan 输出 CMake 能读懂的配置文件。

构建流程变成了两步:

  1. 安装依赖并生成 CMake 配置:
    conan install . --build=missing
  2. 使用 Conan 生成的工具链配置 CMake:
    cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE=build/generators/conan_toolchain.cmake

CMakeLists.txt 依然保持干净,只需按惯例使用 find_package

cmake_minimum_required(VERSION 3.23)
project(my_app CXX)

find_package(fmt REQUIRED)
find_package(spdlog REQUIRED)
find_package(OpenSSL REQUIRED)

add_executable(${PROJECT_NAME} src/main.cpp)
target_link_libraries(${PROJECT_NAME} PRIVATE fmt::fmt spdlog::spdlog OpenSSL::SSL)

这种方式的最大优势是极致的灵活性。Conan 支持自定义设置(比如针对嵌入式 ARM 交叉编译)、私有仓库(Artifactory),以及精细的二进制包管理(同样的源码在不同编译器/选项下会生成不同的二进制包并缓存)。对于企业级项目,这相当于给施工队配备了一套完整的 ERP 采购系统。

CPM.cmake:极简主义的”快读通道”

前面两位(vcpkg 和 Conan)都是重量级的”供应商”,需要单独安装客户端、维护仓库索引。但对于很多中小型项目,或者快速原型开发,你可能只想在 CMakeLists.txt 里写几行代码,就能拉下 GitHub 上的某个库直接使用。这时候,CPM.cmake 就是最佳选择了。

FetchContent 的”升级皮肤”

还记得我们在 4.3 节学过的 FetchContent 吗?CPM.cmake 本质上是对 FetchContent 的一层轻量级封装,但它提供了更简洁的 API 和版本管理语义。

使用 CPM 只需要两步:先下载 CPM.cmake 脚本(通常放在 cmake/CPM.cmake),然后在 CMakeLists.txt 中引入:

# 必须在 add_subdirectory 或 find_package 之前
include(cmake/CPM.cmake)

# 像"点外卖"一样添加依赖
CPMAddPackage(
  NAME fmt
  GITHUB_REPOSITORY fmtlib/fmt
  GIT_TAG 10.1.1
)

CPMAddPackage(
  NAME spdlog
  GITHUB_REPOSITORY gabime/spdlog
  GIT_TAG v1.12.0
  OPTIONS
    "SPDLOG_FMT_EXTERNAL ON"  # 让 spdlog 使用外部的 fmt
)

add_executable(my_app main.cpp)
target_link_libraries(my_app PRIVATE fmt::fmt spdlog::spdlog)

注意这里的美妙之处:CPM 会自动处理 add_subdirectory,把源码拉下来并构建。如果多个依赖都依赖了同一个库(比如 spdlog 和另一个库都用了 fmt),CPM 还会自动做去重(Dedup),避免重复拉取。

对于 CMake 版本较新(3.24+)的项目,CPM 甚至能直接利用 FetchContentFIND_PACKAGE_ARGS,优先寻找系统已安装的包,找不到再自动下载,兼具灵活性与便捷性。

何时选择 CPM

CPM 最适合以下场景:

  • 小型到中型项目:不想引入额外的包管理器安装,保持”纯 CMake”环境。
  • 源码级依赖优先:希望直接编译依赖的源码,而不是预编译的二进制包(方便调试和跨平台)。
  • 快速原型与教学:几行代码就能跑起来,没有复杂的仓库配置。

但它也有短板:没有内置的二进制包缓存机制,每次 CI/CD 都可能重新下载编译;也不如 Conan/vcpkg 那样有庞大的中心化仓库和严格的版本锁定能力。

选型指南:没有银弹,只有场景

看到这里,你可能有点晕:三个工具看起来都不错,我到底该用哪个?别急,我们来一张”施工场景对照表”,帮你做决策:

方案横向对比

维度 vcpkg (Manifest) Conan 2.x CPM.cmake
安装成本 需安装 vcpkg 工具 需安装 Python + Conan 仅需 CMake(单文件引入)
依赖声明 vcpkg.json conanfile.py 直接在 CMakeLists.txt
二进制缓存 支持(Binary Caching) 强大(内置 Artifactory 支持) 无(每次通常重新编译)
版本锁定 overrides + baseline lockfile(conan.lock) Git Tag 或版本号
私有仓库 支持 Registry 完美支持(私有 Artifactory) 任意 Git 仓库即可
CMake 集成 Toolchain 文件 CMakeDeps + CMakeToolchain add_subdirectory
最佳场景 Windows/跨平台开源项目 企业级、多配置复杂项目 中小型项目、快速原型

选型建议

  • 如果你是个人开发者或开源项目维护者,追求简单和跨平台一致性,vcpkg Manifest 是很好的起点。它与 Visual Studio 集成度极高,社区包数量庞大。
  • 如果你在企业环境中工作,有严格的合规要求(必须用私有仓库、精确控制二进制包、多编译器矩阵构建),Conan 2.x 几乎是工业标准。它的 Python 脚本化能力也让复杂依赖逻辑变得可控。
  • 如果你在写一个小工具、教学示例,或者项目依赖不超过 10 个且都是 Git 上的热门库CPM.cmake 能让你保持极简,避免在包管理器上花费过多运维成本。

当然,现实世界中并不是单选题。很多大型项目会混合使用:用 vcpkg/Conan 管理大型基础库(Boost、OpenSSL),用 CPM 拉取几个小众的 Header-only 库。施工队长最重要的能力,不是只用一把锤子,而是知道哪面墙该用锤子、哪面墙该用冲击钻。

小结

这节课,我们给 CMake “施工队长”升级了三条现代化的”供应链”:

  1. vcpkg Manifest 用声明式 JSON 清单解决了”全局安装地狱”,配合 Toolchain 文件与 CMake 无缝衔接。
  2. Conan 2.x 通过 CMakeDepsCMakeToolchain 实现了企业级的依赖工程化,是复杂项目的首选。
  3. CPM.cmake 在极简与功能之间找到了平衡点,让中小型项目也能享受自动拉取依赖的便利。

工具的选择永远取决于项目的规模、团队的运维能力和部署环境。但无论你选择哪条路,核心理念都是一致的:让 CMake 专注构建,让包管理器专注依赖,各司其职,才能盖出稳固的高楼。

在下节课(12.3)中,我们将把目光投向未来——看看 CMake 4.x 会带来哪些新特性,以及 C++20 Modules 这场”模块化革命”将如何重塑我们的构建系统。敬请期待!

请登录后发表评论

    没有回复内容

正在唤醒异次元光景……