CMake 从入门到精通:完整教程 / 第 15 章:依赖管理
第 15 章:依赖管理
15.1 依赖管理方式概览
| 方式 | 使用场景 | 优点 | 缺点 |
|---|
find_package | 系统已安装的库 | 简单、标准 | 需预先安装 |
FetchContent | CMake 项目依赖 | 自动下载、无缝集成 | 配置时下载、CMake 专属 |
ExternalProject | 外部构建的项目 | 灵活、支持非 CMake | 构建时下载、复杂 |
| vcpkg | C/C++ 包管理 | 跨平台、微软维护 | 需额外安装 |
| Conan | C/C++ 包管理 | 版本锁定、二进制缓存 | 学习曲线 |
15.2 FetchContent
15.2.1 基本用法
include(FetchContent)
FetchContent_Declare(
fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG 10.2.1
)
FetchContent_MakeAvailable(fmt)
# 直接使用目标
add_executable(app main.cpp)
target_link_libraries(app PRIVATE fmt::fmt)
15.2.2 FetchContent 声明方式
# Git 仓库
FetchContent_Declare(mylib
GIT_REPOSITORY https://github.com/example/mylib.git
GIT_TAG v2.0.0
GIT_SHALLOW TRUE # 浅克隆
GIT_SUBMODULES "" # 不拉取子模块
)
# Git 分支
FetchContent_Declare(mylib
GIT_REPOSITORY https://github.com/example/mylib.git
GIT_TAG main
GIT_PROGRESS TRUE
)
# URL 下载
FetchContent_Declare(mylib
URL https://github.com/example/mylib/archive/v2.0.0.tar.gz
URL_HASH SHA256=abc123def456...
)
# URL 自动检测哈希
FetchContent_Declare(mylib
URL https://github.com/example/mylib/archive/v2.0.0.tar.gz
URL_HASH AUTO
)
# 本地路径(开发模式)
FetchContent_Declare(mylib
SOURCE_DIR /home/user/dev/mylib
)
15.2.3 FetchContent 属性
| 属性 | 说明 |
|---|
GIT_REPOSITORY | Git 仓库 URL |
GIT_TAG | Git 标签/分支/提交 |
GIT_SHALLOW | 浅克隆 |
GIT_SUBMODULES | 子模块列表 |
GIT_PROGRESS | 显示进度 |
URL | 下载 URL |
URL_HASH | 文件哈希 |
SOURCE_DIR | 本地源码目录 |
PATCH_COMMAND | 打补丁命令 |
FIND_PACKAGE_ARGS | 先尝试 find_package(CMake 3.24+) |
SYSTEM | 标记为系统头文件(CMake 3.25+) |
15.2.4 控制行为
# 先尝试 find_package,找不到再下载
FetchContent_Declare(fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG 10.2.1
FIND_PACKAGE_ARGS
)
FetchContent_MakeAvailable(fmt)
# 等价于:先 find_package(fmt),失败才 FetchContent
# 先尝试 find_package 并指定版本
FetchContent_Declare(fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG 10.2.1
FIND_PACKAGE_ARGS 10.0
)
15.2.5 仅声明不立即使用
FetchContent_Declare(fmt ...)
FetchContent_Declare(spdlog ...)
# 延迟获取
FetchContent_GetProperties(fmt)
if(NOT fmt_POPULATED)
FetchContent_Populate(fmt)
add_subdirectory(${fmt_SOURCE_DIR} ${fmt_BINARY_DIR})
endif()
# 或者批量获取
FetchContent_MakeAvailable(fmt spdlog)
15.2.6 SYSTEM 头文件(CMake 3.25+)
# 将 FetchContent 的依赖标记为系统头文件,避免警告
FetchContent_Declare(fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG 10.2.1
SYSTEM # 新特性
)
15.3 ExternalProject
15.3.1 基本用法
include(ExternalProject)
ExternalProject_Add(
my_external
GIT_REPOSITORY https://github.com/example/somelib.git
GIT_TAG v1.0.0
CMAKE_ARGS
-DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/external
-DCMAKE_BUILD_TYPE=Release
BUILD_COMMAND ${CMAKE_COMMAND} --build <BINARY_DIR> --config Release
INSTALL_COMMAND ${CMAKE_COMMAND} --install <BINARY_DIR>
)
15.3.2 ExternalProject 与 FetchContent 区别
| 特性 | FetchContent | ExternalProject |
|---|
| 执行时机 | 配置时 | 构建时 |
| 集成方式 | add_subdirectory | 独立构建 |
| 目标访问 | 可直接链接目标 | 需手动链接 |
| 使用场景 | CMake 项目依赖 | 任何项目依赖 |
| 性能 | 配置时下载 | 构建时下载 |
15.3.3 使用 ExternalProject 查找已安装的库
include(ExternalProject)
ExternalProject_Add(
zlib_ext
URL https://zlib.net/zlib-1.3.1.tar.gz
URL_HASH SHA256=...
CMAKE_ARGS
-DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/deps
-DCMAKE_BUILD_TYPE=Release
)
# 创建导入目标
add_library(ZLIB::ZLIB SHARED IMPORTED)
set_target_properties(ZLIB::ZLIB PROPERTIES
IMPORTED_LOCATION ${CMAKE_BINARY_DIR}/deps/lib/libz.so
INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_BINARY_DIR}/deps/include
)
add_dependencies(ZLIB::ZLIB zlib_ext)
15.4 vcpkg
15.4.1 安装 vcpkg
# 克隆
git clone https://github.com/microsoft/vcpkg.git
cd vcpkg
./bootstrap-vcpkg.sh
# 设置环境变量
export VCPKG_ROOT=/path/to/vcpkg
export PATH=$VCPKG_ROOT:$PATH
15.4.2 vcpkg.json 配置
{
"name": "myproject",
"version": "1.0.0",
"dependencies": [
"fmt",
"spdlog",
{
"name": "openssl",
"version>=": "3.0.0"
},
{
"name": "boost",
"features": ["filesystem", "system", "thread"]
}
],
"builtin-baseline": "a42af01b72c28a8e1d7b48107b33e4f286a55ef6"
}
15.4.3 使用 vcpkg 工具链
# 命令行方式
cmake -S . -B build \
-DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake
# CMakePresets.json 方式
{
"version": 6,
"configurePresets": [
{
"name": "vcpkg",
"toolchainFile": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
}
]
}
15.4.4 vcpkg 常用命令
# 安装包
vcpkg install fmt
vcpkg install spdlog:x64-linux
# 查找包
vcpkg search openssl
# 列出已安装
vcpkg list
# 版本管理
vcpkg x-update-ports # 更新 ports
vcpkg x-update-baseline # 更新 baseline
15.4.5 自定义端口
custom-ports/
└── mylib/
├── portfile.cmake
└── vcpkg.json
{
"name": "mylib",
"version": "2.0.0",
"dependencies": []
}
# portfile.cmake
vcpkg_from_github(
OUT_SOURCE_PATH SOURCE_PATH
REPO example/mylib
REF v2.0.0
SHA512 ...
)
vcpkg_cmake_configure(SOURCE_PATH ${SOURCE_PATH})
vcpkg_cmake_install()
vcpkg_cmake_config_fixup()
15.5 Conan
15.5.1 安装 Conan
pip install conan
# 创建默认配置
conan profile detect
15.5.2 conanfile.txt
[requires]
fmt/10.2.1
spdlog/1.13.0
openssl/3.2.1
[generators]
CMakeDeps
CMakeToolchain
[layout]
cmake_layout
15.5.3 conanfile.py
from conan import ConanFile
from conan.tools.cmake import cmake_layout
class MyProjectConan(ConanFile):
name = "myproject"
version = "1.0.0"
settings = "os", "compiler", "build_type", "arch"
requires = (
"fmt/10.2.1",
"spdlog/1.13.0",
)
generators = "CMakeDeps", "CMakeToolchain"
def layout(self):
cmake_layout(self)
def configure(self):
if self.settings.os == "Linux":
self.options["spdlog"].shared = True
15.5.4 使用 Conan
# 安装依赖
conan install . --build=missing
# 使用生成的工具链
cmake -S . -B build \
-DCMAKE_TOOLCHAIN_FILE=build/Release/generators/conan_toolchain.cmake \
-DCMAKE_BUILD_TYPE=Release
# 或使用 CMakePresets.json
# Conan 会生成 CMakeUserPresets.json
cmake --preset conan-release
15.5.5 Conan 的 CMakePresets 集成
# Conan 2 自动生成 CMakeUserPresets.json
conan install . --output-folder=build --build=missing
# 直接使用预设
cmake --preset default
15.5.6 vcpkg vs Conan 对比
| 特性 | vcpkg | Conan |
|---|
| 开发者 | Microsoft | JFrog |
| 配置文件 | vcpkg.json | conanfile.txt/py |
| CMake 集成 | 工具链文件 | 生成器/预设 |
| 版本管理 | baseline + port 版本 | recipe 版本 |
| 二进制缓存 | vcpkg binary caching | Conan binary cache |
| 包仓库 | vcpkg registry | ConanCenter |
| 自定义包 | Custom ports | Local recipes |
| 学习曲线 | 较低 | 中等 |
| 社区包数 | 2000+ | 1500+ |
15.6 混合策略
15.6.1 查找优先级
# 优先级:系统包 > vcpkg/Conan > FetchContent
# 1. 先尝试系统查找
find_package(fmt QUIET)
# 2. 再尝试 vcpkg/Conan(通过 CMAKE_PREFIX_PATH)
if(NOT fmt_FOUND)
find_package(fmt CONFIG QUIET)
endif()
# 3. 最后使用 FetchContent
if(NOT fmt_FOUND)
include(FetchContent)
FetchContent_Declare(fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG 10.2.1
)
FetchContent_MakeAvailable(fmt)
endif()
# 或使用 CMake 3.24+ 的 FIND_PACKAGE_ARGS
FetchContent_Declare(fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG 10.2.1
FIND_PACKAGE_ARGS
)
FetchContent_MakeAvailable(fmt)
15.6.2 选项控制
option(USE_SYSTEM_FMT "使用系统 fmt 库" OFF)
option(USE_VCPKG "使用 vcpkg 管理依赖" OFF)
if(USE_SYSTEM_FMT)
find_package(fmt REQUIRED)
elseif(USE_VCPKG)
# vcpkg 工具链会自动处理
find_package(fmt REQUIRED)
else()
include(FetchContent)
FetchContent_Declare(fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG 10.2.1
)
FetchContent_MakeAvailable(fmt)
endif()
15.7 业务场景
场景:选择依赖管理策略
项目规模评估
├── 小型项目(< 5 个依赖)
│ └── 推荐:FetchContent(最简单)
├── 中型项目(5-20 个依赖)
│ └── 推荐:vcpkg 或 Conan
├── 大型项目(> 20 个依赖)
│ └── 推荐:Conan + 私有仓库
└── 跨团队共享
└── 推荐:vcpkg + 自定义注册表
场景:CI 中的依赖管理
# GitHub Actions 示例
# .github/workflows/build.yml
jobs:
build:
steps:
- uses: actions/checkout@v4
# vcpkg 方式
- name: Setup vcpkg
uses: lukka/run-vcpkg@v11
with:
vcpkgGitCommitId: 'a42af01b72c28a8e1d7b48107b33e4f286a55ef6'
- name: Configure
run: cmake --preset vcpkg
- name: Build
run: cmake --build --preset vcpkg
15.8 注意事项
| 问题 | 说明 |
|---|
| FetchContent 配置时间 | 大量依赖会增加配置时间 |
| 版本锁定 | 使用 tag 或 hash 锁定版本 |
| 缓存利用 | vcpkg 和 Conan 支持二进制缓存 |
| 网络问题 | CI 环境可能需要代理或离线模式 |
| 依赖冲突 | 不同依赖可能需要同一库的不同版本 |
15.9 扩展阅读
上一章:第 14 章 — 模块系统 | 下一章:第 16 章 — Docker 与 CI/CD →