强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

第 15 章:依赖管理

第 15 章:依赖管理

15.1 依赖管理方式概览

方式使用场景优点缺点
find_package系统已安装的库简单、标准需预先安装
FetchContentCMake 项目依赖自动下载、无缝集成配置时下载、CMake 专属
ExternalProject外部构建的项目灵活、支持非 CMake构建时下载、复杂
vcpkgC/C++ 包管理跨平台、微软维护需额外安装
ConanC/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_REPOSITORYGit 仓库 URL
GIT_TAGGit 标签/分支/提交
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 区别

特性FetchContentExternalProject
执行时机配置时构建时
集成方式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 对比

特性vcpkgConan
开发者MicrosoftJFrog
配置文件vcpkg.jsonconanfile.txt/py
CMake 集成工具链文件生成器/预设
版本管理baseline + port 版本recipe 版本
二进制缓存vcpkg binary cachingConan binary cache
包仓库vcpkg registryConanCenter
自定义包Custom portsLocal 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 →