17、CMake Hunter:Hunter包管理器、HunterGate、hunter_add_package

聊到 CMake 的包管理,很多人第一反应是 find_package。但说实话,这玩意儿在跨平台项目里经常让人头疼——你得手动装依赖,版本还容易冲突。我自己就吃过这个亏,有一次在 Windows 上编译一个开源项目,折腾了整整一下午,就为了把 Boost 的路径配对。

后来我遇到了 Hunter。它是个 CMake 原生的包管理器,说白了就是帮你自动下载、编译、缓存第三方库。你只需要在 CMakeLists.txt 里声明一下,剩下的交给它。

Hunter 是什么?

Hunter 是一个用 CMake 脚本写的包管理器。它不依赖 Python、Node.js 这些外部工具,纯 CMake 实现。你想想看,这意味着什么?意味着你只要装了 CMake,就能用 Hunter。

它的工作流程是这样的:

  1. 你在项目里引入 HunterGate(Hunter 的入口)
  2. Hunter 根据你指定的版本,从 GitHub 下载对应的包描述文件
  3. 然后自动下载源码、编译、安装到本地缓存
  4. 最后通过 hunter_add_package 把包暴露给你的项目

整个过程对开发者几乎是透明的。我记得第一次用的时候,心里还嘀咕:这玩意儿靠谱吗?结果跑了一次,真香。

核心优势:Hunter 最大的好处是 可复现构建。你锁定了 Hunter 版本和包的版本,换台机器、换个人编译,结果完全一样。这在团队协作和 CI/CD 里太重要了。

HunterGate:一切的入口

要使用 Hunter,第一步就是在你的顶层 CMakeLists.txt 里引入 HunterGate。它是个 CMake 模块,负责初始化 Hunter 环境。

用法很简单,像这样:

cmake_minimum_required(VERSION 3.2)

# 引入 HunterGate
include("cmake/HunterGate.cmake")

HunterGate(
    URL "https://github.com/cpp-pm/hunter/archive/v0.23.251.tar.gz"
    SHA1 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4"
)

project(MyProject CXX)

这里有两个关键参数:

  • URL:Hunter 发行版的下载地址。我建议用 GitHub 的 release 包,别用 master 分支,否则哪天更新了你的构建就炸了。
  • SHA1:压缩包的校验值。Hunter 会检查下载的文件是否完整,防止中间人攻击或下载损坏。

小技巧:你可以把 HunterGate.cmake 文件直接放在项目里,这样即使 GitHub 连不上,也能本地加载。我个人习惯把它放到 cmake/ 目录下。

hunter_add_package:声明依赖

初始化完 HunterGate 之后,就可以用 hunter_add_package 来添加依赖了。这个命令会告诉 Hunter:我需要哪个包,什么版本。

举个例子,我要用 Boost 的 filesystem 和 system 库:

hunter_add_package(Boost COMPONENTS filesystem system)

find_package(Boost CONFIG REQUIRED COMPONENTS filesystem system)

target_link_libraries(my_app
    Boost::filesystem
    Boost::system
)

注意这里的顺序:

  1. hunter_add_package 让 Hunter 下载并编译 Boost
  2. find_package 找到它生成的 CMake 配置文件
  3. 最后链接到你的目标上

为什么不能直接用 find_package?因为 Hunter 把包安装到了自己的缓存目录里,系统默认路径找不到。Hunter 会通过设置 CMAKE_PREFIX_PATH 来让 find_package 找到它们。

注意hunter_add_package 必须在 project() 命令之后调用。我之前犯过这个错,把顺序搞反了,结果 CMake 报了一堆莫名其妙的错。

实战:一个完整的例子

咱们来看一个完整的项目结构。假设我要写一个图片处理工具,依赖 OpenCV 和 spdlog:

my_project/
├── cmake/
│   └── HunterGate.cmake
├── src/
│   └── main.cpp
├── CMakeLists.txt
└── hunter_config.cmake  (可选)

顶层 CMakeLists.txt 长这样:

cmake_minimum_required(VERSION 3.10)

include("cmake/HunterGate.cmake")

HunterGate(
    URL "https://github.com/cpp-pm/hunter/archive/v0.23.251.tar.gz"
    SHA1 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4"
)

project(ImageTool CXX)

# 设置 C++ 标准
set(CMAKE_CXX_STANDARD 17)

# 添加依赖
hunter_add_package(OpenCV)
find_package(OpenCV CONFIG REQUIRED)

hunter_add_package(spdlog)
find_package(spdlog CONFIG REQUIRED)

# 构建目标
add_executable(image_tool src/main.cpp)

target_link_libraries(image_tool
    OpenCV::opencv_core
    OpenCV::opencv_imgproc
    spdlog::spdlog
)

然后 main.cpp 里直接 include 头文件就行:

#include <opencv2/core.hpp>
#include <spdlog/spdlog.h>

int main() {
    spdlog::info("OpenCV version: {}", CV_VERSION);
    cv::Mat img(100, 100, CV_8UC3);
    spdlog::info("Image created: {}x{}", img.rows, img.cols);
    return 0;
}

第一次编译时,Hunter 会下载 OpenCV 和 spdlog 的源码,然后编译。这个过程可能有点慢,但第二次就快了,因为缓存了。

Hunter 的版本锁定机制

Hunter 用 HunterGate 的 URL/SHA1 来锁定整个工具链的版本。每个 Hunter 版本对应一组已知的包版本。比如 Hunter v0.23.251 里,OpenCV 是 4.5.1,spdlog 是 1.8.2。

如果你想覆盖某个包的版本,可以在 hunter_config.cmake 里指定:

hunter_config(OpenCV VERSION 4.5.2)

然后把这个文件放在项目根目录,Hunter 会自动读取。嗯,这里要注意:覆盖版本可能导致兼容性问题,除非你很清楚自己在做什么。

SVG 流程图:Hunter 的工作流程

下面这张图展示了 Hunter 从初始化到构建完成的完整流程:

Hunter 包管理流程 CMakeLists.txt include HunterGate HunterGate 初始化 下载 Hunter 发行版 下载包描述文件 (hunter/packages/*) hunter_add_package 下载源码 → 编译 → 安装到缓存 find_package 找到缓存 target_link_libraries

避坑指南

用 Hunter 这几年,我踩过不少坑。挑几个典型的说说:

  • 网络问题:Hunter 需要从 GitHub 下载东西。如果你在墙内,建议配置代理或者用镜像。我曾经在客户现场编译,没网,Hunter 直接罢工。后来我提前把 Hunter 的压缩包和所有依赖的源码都下载好,放到本地缓存里。
  • 缓存路径:Hunter 默认缓存到 ~/.hunter。如果你用 CI,记得把这个目录缓存起来,否则每次构建都要重新编译所有依赖,那酸爽...
  • 版本冲突:如果两个项目用了不同版本的 Hunter,它们的缓存是隔离的。这其实是好事,但会多占磁盘空间。我一般会在公司内部统一 Hunter 版本。

我的建议:对于新项目,直接上 Hunter 是个明智的选择。它省去了手动管理依赖的麻烦,而且构建可复现。但对于已有的大型项目,迁移成本可能比较高,需要评估一下。

总结

Hunter 给我的感觉是:它把 CMake 的包管理能力提升了一个档次。你不用再写一堆 find_libraryfind_path 的脚本,也不用担心用户机器上没装某个库。声明一下,剩下的交给 Hunter。

当然,它也不是万能的。有些冷门库可能不在 Hunter 的包列表里,这时候你还是得手动处理。但常用的库,比如 Boost、OpenCV、fmt、spdlog、GTest 等等,Hunter 都支持得很好。

我个人觉得,如果你在做一个跨平台的开源项目,或者团队里新人比较多,Hunter 绝对值得一试。它能让你的 CMakeLists.txt 干净很多,也让构建过程更可控。


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