vcpkg

8/12/2024 C++

来自 Microsoft 的 C/C++ 依赖管理器,适用于所有平台、构建系统和工作流程 https://vcpkg.io (opens new window)

本文将使用 cmake 作为构建工具

# 安装

clone vcpkg 仓库

git clone --progress https://github.com/microsoft/vcpkg ~/.example-vcpkg
1

将 vcpkg 命令加入系统环境变量

export PATH="$PATH:$HOME/.example-vcpkg"
1

安装(需要联网)

cd ~/.example-vcpkg
./bootstrap-vcpkg.sh
1
2

正确的输出如下

Downloading vcpkg-macos...
vcpkg package management program version 2024-08-01-fd884a0d390d12783076341bd43d77c3a6a15658

See LICENSE.txt for license information.
Telemetry
---------
vcpkg collects usage data in order to help us improve your experience.
The data collected by Microsoft is anonymous.
You can opt-out of telemetry by re-running the bootstrap-vcpkg script with -disableMetrics,
passing --disable-metrics to vcpkg on the command line,
or by setting the VCPKG_DISABLE_METRICS environment variable.

Read more about vcpkg telemetry at docs/about/privacy.md
1
2
3
4
5
6
7
8
9
10
11
12
13

# 使用

vcpkg 提供两种服务模式

  • 经典模式和 pkg-config 区别不大,全局共享同一份依赖库且不支持锁定版本
  • 清单模式类似 npm,将依赖包声明到一个 json 文件中,支持锁定版本

两种模式都以 fmt 包为例演示。

# 搜索软件包

vcpkg search fmt
1

输出如下

vcpkg search fmt            
fmt                      11.0.2           {fmt} is an open-source formatting library providing a fast and safe alter...
...
1
2
3

# 经典模式

# 安装依赖包

vcpkg install fmt[:[arch]-[os]] 架构以及系统参数可省略

vcpkg install fmt:arm64-osx
1

输出如下

Computing installation plan...
...
Installing 3/3 fmt:arm64-osx@11.0.2...
Elapsed time to handle fmt:arm64-osx: 4.99 ms
fmt:arm64-osx package ABI: 1de75c5883a2f083f271769aeeaf0c877b2d95ab703f88676c9b1c890e254b9a
Total install time: 7.89 ms
The package fmt provides CMake targets:

    find_package(fmt CONFIG REQUIRED)
    target_link_libraries(main PRIVATE fmt::fmt)
...
1
2
3
4
5
6
7
8
9
10
11

# 在 cmake 中导入依赖包

按照输出中的示例将依赖导入 cmake target

find_package(fmt CONFIG REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE fmt::fmt)
1
2

# 调用依赖包

#include <fmt/core.h>

int main() {
    fmt::print("Hello World!\n");

    return 0;
}
1
2
3
4
5
6
7

# 编译

编译时需要先指定工具链为 vcpkg,后续编译时就会自动包含第三方包。例如 <fmt/core.h> 实际上位于 ~/.example-vcpkg/installed/arm64-osx/include/fmt/core.h,所有导入该包的软件都共用这一份。

cmake -DCMAKE_TOOLCHAIN_FILE=~/.example-vcpkg/scripts/buildsystems/vcpkg.cmake -B build -S .
cmake --build build --target all -j `nproc`
1
2

# 运行

 ./build/vcpkg 
Hello World!
1
2

# 清单模式

清单模式通过 vcpkg.json 实现

# 初始化清单模式

vcpkg new --application
1

或手动创建 json 文件,基线可以通过 cd ~/.example-vcpkg && git rev-parse HEAD 命令查看。

{
    "name":             "vcpkg",
    "version-string":   "1.0.0",
    "builtin-baseline": "e590c2b30c08caf1dd8d612ec602a003f9784b7d",
    "dependencies":     []
}
1
2
3
4
5
6

# 安装依赖包

清单模式只能手动增加依赖并进行 vcpkg install,vcpkg.json 全文如下

{
    "name":             "vcpkg",
    "version-string":   "1.0.0",
    "builtin-baseline": "e590c2b30c08caf1dd8d612ec602a003f9784b7d",
    "dependencies":     [
        {
            "name":      "fmt",
            "version>=": "11.0.2"
        }
    ]
}
1
2
3
4
5
6
7
8
9
10
11

# 不使用 cmake 获取 vcpkg 依赖

不使用 cmake 工具链,vcpkg 包将会被导入到 ./vcpkg_installed 目录中

vcpkg install
1

# cmake 获取 vcpkg 依赖

vcpkg 包将会被导入到 ./build/vcpkg_installed 目录中

cmake -DCMAKE_TOOLCHAIN_FILE=~/.example-vcpkg/scripts/buildsystems/vcpkg.cmake -B build -S .
1

会先执行 vcpkg install(如果没有则先删除 build 目录再重新执行),输出如下。

-- Running vcpkg install
...
The package fmt provides CMake targets:

find_package(fmt CONFIG REQUIRED)
target_link_libraries(main PRIVATE fmt::fmt)
...
1
2
3
4
5
6
7

# 导入、调用依赖包

与经典模式一致,不再演示

# 编译

一样编译时需要先指定工具链为 vcpkg,但第三方包位置略有不同,例如 <fmt/core.h> 实际上位于 ./build/vcpkg_installed/arm64-osx/include/fmt/core.h,这就是为什么清单模式能够管理依赖的版本。

# 运行

与经典模式一致,不再演示

# 原理

所有的包都在最初 git clone 的目录中,vcpkg search 也只是在本地仓库的 ports 目录中搜索。 例如 ~/.example-vcpkg/ports/fmt/usage 就是 fmt 包的帮助文件。

因此在原理上,你可以在同一个系统维护多份不同的 vcpkg 版本的仓库

The package fmt provides CMake targets:

    find_package(fmt CONFIG REQUIRED)
    target_link_libraries(main PRIVATE fmt::fmt)

    # Or use the header-only version
    find_package(fmt CONFIG REQUIRED)
    target_link_libraries(main PRIVATE fmt::fmt-header-only)
1
2
3
4
5
6
7
8

# 依赖包文件

  • 经典模式下依赖包会被下载到仓库中 /repo/installed 被整个系统共用。
  • 清单模式下依赖包会被下载到项目根目录的 /project/vcpkg_installed 目录下,或 cmake 构建目录 /project/build/vcpkg_installed

无论通过哪种方式拉取依赖包到本地,vcpkg 都会进行现场编译并生成链接库

# 更新软件包

获取新增包、更新包版本、包简介等

git pull
1

更新 Vcpkg 仓库

vcpkg upgrade --no-dry-run
vcpkg list --x-json --vcpkg-root=~/.example-vcpkg
vcpkg update
1
2
3

# 综合案例

通过 libpcap 和 drogon 进行综合案例演示。并将涉及到前文未提及但生产中常用的 pkg-config 和 Ninja 技术。

主体功能如下

  1. 使用 drogon 启动 web server 并阻塞在主线程
  2. 子线程通过 libpcap 进行抓包和统计包数量
  3. 在 drogon 注册一个路由,打印抓包的统计信息(包含实际抓取包数、libpcap 接收包数,丢弃包数 等指标)

# vcpkg.json

安装 drogon 和 libpcap 两个包

{
    "name":             "vcpkg",
    "version-string":   "1.0.0",
    "builtin-baseline": "e590c2b30c08caf1dd8d612ec602a003f9784b7d",
    "dependencies":     [
        {
            "name":      "libpcap",
            "version>=": "1.10.4#1"
        }, {
            "name":      "drogon",
            "version>=": "1.9.6"
        }
    ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# CMakeLists.txt

libpcap 是通过 pkg-config 管理的,所以要通过 cmake 的 pkg-config 插件来管理 link 和 include

cmake_minimum_required(VERSION 3.29)
project(vcpkg)

set(CMAKE_CXX_STANDARD 20)
set(ENV{PKG_CONFIG_PATH} "${CMAKE_CACHEFILE_DIR}/installed/arm64-osx/lib/pkgconfig:$ENV{PKG_CONFIG_PATH}")

add_executable(${PROJECT_NAME} main.cpp)

find_package(Drogon CONFIG REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE Drogon::Drogon)

find_package(PkgConfig REQUIRED)
pkg_search_module(PKG_LIBPCAP REQUIRED libpcap)
target_link_libraries(${PROJECT_NAME} PRIVATE ${PKG_LIBPCAP_LIBRARIES})
target_link_directories(${PROJECT_NAME} PRIVATE ${PKG_LIBPCAP_LIBRARY_DIRS})
target_include_directories(${PROJECT_NAME} PRIVATE ${PKG_LIBPCAP_INCLUDE_DIRS})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

main.cpp

#include <iostream>
#include <thread>
#include <pcap/pcap.h>
#include <drogon/drogon.h>

using namespace drogon;

int pkt_num = 0;
char errbuf[PCAP_ERRBUF_SIZE];
pcap_t* pc = pcap_open_live("en0", 65535, 1, 0, errbuf);

void capture() {
    std::cout << "snapshot: " << pcap_snapshot(pc) << std::endl;

    if (pcap_setnonblock(pc, 1, errbuf) != 0) {
        exit(1);
    }

    std::cout << "run on nonblock mode" << std::endl;
    while (true) {
        pcap_pkthdr* pkt_header;
        const u_char* pkt_data;

        if (pcap_next_ex(pc, &pkt_header, &pkt_data) != 1) {
            continue;
        }

        pkt_num++;
    }
}

void capture_stats_handler(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr&)>&& callback) {
    pcap_stat stats;
    pcap_stats(pc, &stats);

    Json::Value json;
    json["result"] = "ok";
    json["captured"] = pkt_num;
    json["received by filter"] = stats.ps_recv;
    json["dropped by interface"] = stats.ps_ifdrop;
    json["dropped by kernel"] = stats.ps_drop;

    callback(HttpResponse::newHttpJsonResponse(json));
}

int main() {
    std::thread c(capture);

    app()
        .setLogLevel(trantor::Logger::kWarn)
        .addListener("localhost", 3000)
        .registerHandler("/", &capture_stats_handler, {Get, "LoginFilter"})
        .run();

    return 0;
} 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

# 通过 Ninja 编译运行

使用 Ninja 进行 Release 模式构建

cmake -G Ninja -S . -B build \
	-DCMAKE_BUILD_TYPE=Release \
	-DCMAKE_MAKE_PROGRAM=`which ninja` \
	-DCMAKE_TOOLCHAIN_FILE=~/.example-vcpkg/scripts/buildsystems/vcpkg.cmake
cmake --build build --target all -j `nproc`
1
2
3
4
5

运行并请求 http://localhost:3000

./build/vcpkg
snapshot: 65535
run on nonblock mode
1
2
3

输出符合预期,且多次刷新值会更新

{
    "captured":             1040,
    "dropped by interface": 0,
    "dropped by kernel":    0,
    "received by filter":   1040,
    "result":               "ok"
}
1
2
3
4
5
6
7

# 将包加入 vcpkg

参考微软的官方教程:打包 GitHub 存储库示例:libogg (opens new window)

# 与 clion 集成

参考 Clion 官方文档:Vcpkg integration (opens new window)

Last Updated: 8/12/2024, 8:09:16 AM