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

C/C++ Linux 开发教程(GCC + CMake) / CMake 包管理(find_package/FetchContent/Conan)

CMake 包管理(find_package/FetchContent/Conan)

现代 C++ 项目通常依赖大量第三方库。CMake 提供了多种方式管理这些依赖。本文讲解 find_package、FetchContent、Conan 和 vcpkg 等包管理方案。

find_package 工作原理

两种查找模式

CMake 的 find_package() 有两种工作模式:

模式说明配置文件
Config 模式库自己提供的配置文件<Lib>Config.cmake, <lib>-config.cmake
Module 模式CMake 内置或自定义的查找模块Find<Lib>.cmake
# 查找流程:
# 1. Module 模式: 在 CMAKE_MODULE_PATH 中查找 FindXxx.cmake
# 2. Config 模式: 在标准路径中查找 XxxConfig.cmake
# 3. 两种模式都失败则报错

基本使用

# Config 模式(推荐)
find_package(OpenSSL REQUIRED)
find_package(Boost 1.70 REQUIRED COMPONENTS filesystem system)

# Module 模式
find_package(Threads REQUIRED)

# 可选依赖
find_package(CUDA QUIET)
if(CUDA_FOUND)
    message(STATUS "CUDA support enabled")
    add_definitions(-DHAVE_CUDA)
endif()

# 查找后使用
target_link_libraries(app PRIVATE
    OpenSSL::SSL
    OpenSSL::Crypto
    Boost::filesystem
    Boost::system
    Threads::Threads
)

查找路径配置

# 指定查找路径
cmake -B build \
    -DCMAKE_PREFIX_PATH="/opt/boost;/opt/openssl" \
    -DOpenSSL_ROOT_DIR=/opt/openssl \
    -DBoost_ROOT=/opt/boost

# 多个路径用分号分隔
cmake -B build -DCMAKE_PREFIX_PATH="/opt/lib1;/opt/lib2;/opt/lib3"
# CMakeLists.txt 中设置
list(APPEND CMAKE_PREFIX_PATH /opt/mylib)
list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)

# 环境变量
# export CMAKE_PREFIX_PATH="/opt/lib1;/opt/lib2"

FindXxx.cmake 编写

完整的 Find 模块

# cmake/FindSQLite3.cmake
#
# 查找 SQLite3 库
#
# 定义以下变量:
#   SQLite3_FOUND        - 是否找到
#   SQLite3_INCLUDE_DIR  - 头文件路径
#   SQLite3_LIBRARY      - 库文件路径
#   SQLite3_VERSION      - 版本号
#
# 定义以下目标:
#   SQLite3::SQLite3     - 导入目标

include(FindPackageHandleStandardArgs)
include(CheckIncludeFile)

# 查找头文件
find_path(SQLite3_INCLUDE_DIR
    NAMES sqlite3.h
    PATHS
        /usr/local/include
        /usr/include
        ${SQLite3_ROOT}/include
        $ENV{SQLite3_ROOT}/include
)

# 查找库文件
find_library(SQLite3_LIBRARY
    NAMES sqlite3
    PATHS
        /usr/local/lib
        /usr/lib
        ${SQLite3_ROOT}/lib
        $ENV{SQLite3_ROOT}/lib
)

# 获取版本号
if(SQLite3_INCLUDE_DIR AND EXISTS "${SQLite3_INCLUDE_DIR}/sqlite3.h")
    file(STRINGS "${SQLite3_INCLUDE_DIR}/sqlite3.h" 
         SQLite3_VERSION_LINE
         REGEX "^#define SQLITE_VERSION[ \t]+\"[0-9]+\\.[0-9]+\\.[0-9]+\"")
    if(SQLite3_VERSION_LINE)
        string(REGEX REPLACE "^#define SQLITE_VERSION[ \t]+\"([0-9]+\\.[0-9]+\\.[0-9]+)\".*"
               "\\1" SQLite3_VERSION "${SQLite3_VERSION_LINE}")
    endif()
endif()

# 检查是否找到所有必需的组件
find_package_handle_standard_args(SQLite3
    REQUIRED_VARS SQLite3_LIBRARY SQLite3_INCLUDE_DIR
    VERSION_VAR SQLite3_VERSION
)

# 创建导入目标
if(SQLite3_FOUND AND NOT TARGET SQLite3::SQLite3)
    add_library(SQLite3::SQLite3 UNKNOWN IMPORTED)
    set_target_properties(SQLite3::SQLite3 PROPERTIES
        IMPORTED_LOCATION "${SQLite3_LIBRARY}"
        INTERFACE_INCLUDE_DIRECTORIES "${SQLite3_INCLUDE_DIR}"
    )
    
    # 平台相关依赖
    if(UNIX AND NOT APPLE)
        set_property(TARGET SQLite3::SQLite3 APPEND PROPERTY
            INTERFACE_LINK_LIBRARIES dl pthread
        )
    endif()
endif()

mark_as_advanced(SQLite3_LIBRARY SQLite3_INCLUDE_DIR)
# 使用自定义的 Find 模块
list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
find_package(SQLite3 3.35 REQUIRED)
target_link_libraries(app PRIVATE SQLite3::SQLite3)

查找系统库的 Find 模块

# cmake/FindEpoll.cmake
include(CheckIncludeFile)
include(CheckSymbolExists)

check_include_file("sys/epoll.h" HAVE_EPOLL_H)
if(HAVE_EPOLL_H)
    check_symbol_exists(epoll_create "sys/epoll.h" HAVE_EPOLL_CREATE)
endif()

include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Epoll
    DEFAULT_MSG
    HAVE_EPOLL_H
    HAVE_EPOLL_CREATE
)

FetchContent 外部依赖管理

FetchContent 基础

include(FetchContent)

# 声明外部依赖
FetchContent_Declare(
    googletest
    GIT_REPOSITORY https://github.com/google/googletest.git
    GIT_TAG        v1.14.0
    GIT_SHALLOW    TRUE              # 浅克隆,节省时间
)

# 下载并添加到项目
FetchContent_MakeAvailable(googletest)

# 使用
add_executable(my_test test.cpp)
target_link_libraries(my_test PRIVATE GTest::gtest_main)

FetchContent 参数详解

FetchContent_Declare(
    mylib
    # Git 方式
    GIT_REPOSITORY https://github.com/user/mylib.git
    GIT_TAG        v2.0.0             # tag 或 commit hash
    GIT_SHALLOW    TRUE               # 浅克隆
    GIT_PROGRESS   TRUE               # 显示进度
    
    # 或 URL 方式(推荐,更稳定)
    # URL      https://github.com/user/mylib/archive/v2.0.0.tar.gz
    # URL_HASH SHA256=abc123...
    
    # 可选配置
    PATCH_COMMAND  git apply ${CMAKE_SOURCE_DIR}/patches/mylib.patch
    UPDATE_DISCONNECTED TRUE           # 配置后不再更新
)

FetchContent 高级用法

include(FetchContent)

# 预先检查是否可用(不下载)
FetchContent_Declare(
    fmt
    GIT_REPOSITORY https://github.com/fmtlib/fmt.git
    GIT_TAG        10.2.1
)

FetchContent_GetProperties(fmt)
if(NOT fmt_POPULATED)
    FetchContent_Populate(fmt)
    add_subdirectory(${fmt_SOURCE_DIR} ${fmt_BINARY_DIR})
endif()

# 覆盖已有库的选项
set(FMT_DOC OFF CACHE BOOL "" FORCE)
set(FMT_TEST OFF CACHE BOOL "" FORCE)

FetchContent_MakeAvailable(fmt)

FetchContent 常用第三方库模板

include(FetchContent)

# Google Test
FetchContent_Declare(googletest
    GIT_REPOSITORY https://github.com/google/googletest.git
    GIT_TAG        v1.14.0
)

# spdlog
FetchContent_Declare(spdlog
    GIT_REPOSITORY https://github.com/gabime/spdlog.git
    GIT_TAG        v1.13.0
)

# nlohmann/json
FetchContent_Declare(nlohmann_json
    GIT_REPOSITORY https://github.com/nlohmann/json.git
    GIT_TAG        v3.11.3
)

# fmt
FetchContent_Declare(fmt
    GIT_REPOSITORY https://github.com/fmtlib/fmt.git
    GIT_TAG        10.2.1
)

# 一次性下载所有依赖
FetchContent_MakeAvailable(googletest spdlog nlohmann_json fmt)

Conan2 包管理器集成

Conan2 基础

# 安装 Conan
pip install conan

# 检测配置
conan profile detect

# 查看默认配置
conan profile show default

conanfile.txt

# conanfile.txt — 最简单的依赖声明
[requires]
openssl/3.2.0
zlib/1.3.1
fmt/10.2.1
spdlog/1.13.0

[generators]
CMakeDeps         # 生成 xxx-config.cmake
CMakeToolchain    # 生成工具链文件

[options]
openssl/*:shared=True

conanfile.py(推荐)

# conanfile.py — 更灵活的依赖声明
from conan import ConanFile
from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout
from conan.tools.files import copy
import os

class MyAppConan(ConanFile):
    name = "myapp"
    version = "1.0.0"
    
    # 元数据
    license = "MIT"
    description = "My application"
    url = "https://github.com/user/myapp"
    
    # 设置
    settings = "os", "compiler", "build_type", "arch"
    
    # 选项
    options = {
        "shared": [True, False],
        "fPIC": [True, False],
        "with_openssl": [True, False],
    }
    default_options = {
        "shared": False,
        "fPIC": True,
        "with_openssl": True,
    }
    
    # 依赖
    requires = [
        "fmt/10.2.1",
        "spdlog/1.13.0",
    ]
    
    # 构建依赖
    tool_requires = ["cmake/3.28.1"]
    
    # 导出的源文件
    exports_sources = "CMakeLists.txt", "src/*", "include/*"
    
    def config_options(self):
        if self.settings.os == "Windows":
            self.options.rm_safe("fPIC")
    
    def configure(self):
        if self.options.shared:
            self.options.rm_safe("fPIC")
    
    def requirements(self):
        if self.options.with_openssl:
            self.requires("openssl/3.2.0")
    
    def layout(self):
        cmake_layout(self)
    
    def generate(self):
        tc = CMakeToolchain(self)
        tc.generate()
    
    def build(self):
        cmake = CMake(self)
        cmake.configure()
        cmake.build()
    
    def package(self):
        copy(self, "LICENSE", src=self.source_folder, 
             dst=os.path.join(self.package_folder, "licenses"))
        cmake = CMake(self)
        cmake.install()
    
    def package_info(self):
        self.cpp_info.libs = ["myapp"]

Conan + CMake 集成

# 安装依赖
conan install . --output-folder=build --build=missing

# 配置项目(使用 Conan 生成的工具链文件)
cmake -B build \
    -DCMAKE_TOOLCHAIN_FILE=build/conan_toolchain.cmake \
    -DCMAKE_BUILD_TYPE=Release

# 构建
cmake --build build --parallel

# 一步完成(Conan 2 推荐方式)
conan build . --build-folder=build
# CMakeLists.txt — 自动找到 Conan 安装的库
cmake_minimum_required(VERSION 3.15)
project(MyApp)

find_package(fmt REQUIRED)
find_package(spdlog REQUIRED)

if(WITH_OPENSSL)
    find_package(OpenSSL REQUIRED)
endif()

add_executable(app src/main.cpp)
target_link_libraries(app PRIVATE
    fmt::fmt
    spdlog::spdlog
    $<TARGET_NAME_IF_EXISTS:OpenSSL::SSL>
)

Conan Profile 管理

# 创建交叉编译 profile
cat > profiles/arm64-linux << 'EOF'
[settings]
os=Linux
os_build=Linux
arch=armv8
arch_build=x86_64
compiler=gcc
compiler.version=12
compiler.libcxx=libstdc++11
build_type=Release

[options]

[env]
CC=aarch64-linux-gnu-gcc
CXX=aarch64-linux-gnu-g++

[conf]
tools.cmake.cmaketoolchain:generator=Ninja
EOF

# 使用 profile 安装依赖
conan install . -pr profiles/arm64-linux --output-folder=build-arm64

vcpkg 集成

vcpkg 基础

# 安装 vcpkg
git clone https://github.com/microsoft/vcpkg.git
cd vcpkg && ./bootstrap-vcpkg.sh

# 全局安装库
./vcpkg install fmt spdlog nlohmann-json

# 查看已安装
./vcpkg list

vcpkg + CMake 集成

# 使用工具链文件
cmake -B build \
    -DCMAKE_TOOLCHAIN_FILE=/path/to/vcpkg/scripts/buildsystems/vcpkg.cmake \
    -DVCPKG_TARGET_TRIPLET=x64-linux

cmake --build build
# CMakeLists.txt — 正常使用 find_package
cmake_minimum_required(VERSION 3.15)
project(MyApp)

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

add_executable(app src/main.cpp)
target_link_libraries(app PRIVATE
    fmt::fmt
    spdlog::spdlog
    nlohmann_json::nlohmann_json
)

vcpkg.json 清单模式

{
    "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json",
    "name": "myapp",
    "version-string": "1.0.0",
    "dependencies": [
        {
            "name": "fmt",
            "version>=": "10.0.0"
        },
        "spdlog",
        "nlohmann-json"
    ],
    "overrides": [
        {
            "name": "fmt",
            "version": "10.2.1"
        }
    ],
    "features": {
        "openssl": {
            "description": "Enable TLS support",
            "dependencies": ["openssl"]
        }
    }
}

系统库 vs 第三方库策略

依赖策略对比

策略优点缺点适用场景
系统库轻量、安全更新版本不可控稳定的生产环境
FetchContent无额外工具编译时间长简单项目
Conan/vcpkg版本锁定、二进制缓存额外工具依赖复杂项目
源码子模块完全可控维护成本高核心依赖

推荐策略

# 策略 1: 优先系统库,备选 FetchContent
find_package(ZLIB QUIET)
if(NOT ZLIB_FOUND)
    message(STATUS "ZLIB not found, fetching...")
    include(FetchContent)
    FetchContent_Declare(zlib
        URL https://github.com/madler/zlib/releases/download/v1.3.1/zlib-1.3.1.tar.gz
    )
    FetchContent_MakeAvailable(zlib)
endif()

# 策略 2: 选项控制
option(USE_SYSTEM_ZLIB "Use system zlib" OFF)
if(USE_SYSTEM_ZLIB)
    find_package(ZLIB REQUIRED)
else()
    include(FetchContent)
    FetchContent_Declare(zlib ...)
    FetchContent_MakeAvailable(zlib)
endif()

依赖版本锁定

FetchContent 版本锁定

# 使用 commit hash 锁定精确版本
FetchContent_Declare(
    mylib
    GIT_REPOSITORY https://github.com/user/mylib.git
    GIT_TAG        a1b2c3d4e5f6...   # 精确到 commit
)

# 使用 URL + hash 锁定
FetchContent_Declare(
    mylib
    URL      https://github.com/user/mylib/archive/v2.0.0.tar.gz
    URL_HASH SHA256=abc123def456...
)

CMake 预设锁定

// CMakePresets.json — 锁定依赖版本
{
    "version": 6,
    "configurePresets": [
        {
            "name": "default",
            "cacheVariables": {
                "FETCHCONTENT_FULLY_DISCONNECTED": "ON",
                "FETCHCONTENT_UPDATES_DISCONNECTED": "ON"
            }
        }
    ]
}

Conan 版本锁定

# 生成 lockfile
conan lock create conanfile.py

# 使用 lockfile 安装
conan install . --lockfile=conan.lock

# 更新 lockfile
conan lock update conanfile.py

私有仓库管理

Git 私有仓库

# 使用 SSH 协议访问私有仓库
FetchContent_Declare(
    mylib
    GIT_REPOSITORY [email protected]:myorg/mylib-private.git
    GIT_TAG        v1.0.0
)

# 使用 token 访问
FetchContent_Declare(
    mylib
    GIT_REPOSITORY https://${GITHUB_TOKEN}@github.com/myorg/mylib-private.git
    GIT_TAG        v1.0.0
)

Conan 私有仓库

# 添加 Artifactory 私有仓库
conan remote add private https://conan.example.com/api/conan/conan

# 登录
conan remote login private myuser

# 上传包到私有仓库
conan create . --user=mycompany --channel=stable
conan upload mylib/1.0.0@mycompany/stable --remote=private

# 从私有仓库安装
conan install . -r=private

内部镜像

# 使用内部 Git 镜像
cmake -B build -DFETCHCONTENT_BASE_DIR=/opt/cmake-cache

# 使用内部 Conan 镜像
conan remote add internal https://conan.internal.company.com

包管理最佳实践

1. 最小化依赖

# ❌ 避免:依赖整个 Boost
find_package(Boost REQUIRED)

# ✅ 推荐:只依赖需要的组件
find_package(Boost REQUIRED COMPONENTS filesystem system)

2. 使用导入目标

# ❌ 避免:手动管理路径
include_directories(${ZLIB_INCLUDE_DIRS})
link_libraries(${ZLIB_LIBRARIES})

# ✅ 推荐:使用导入目标
target_link_libraries(app PRIVATE ZLIB::ZLIB)

3. 条件化可选依赖

# 可选的 TLS 支持
option(ENABLE_TLS "Enable TLS support" ON)
if(ENABLE_TLS)
    find_package(OpenSSL REQUIRED)
    target_compile_definitions(app PRIVATE ENABLE_TLS)
    target_link_libraries(app PRIVATE OpenSSL::SSL)
endif()

4. 版本兼容性检查

find_package(Python 3.8 REQUIRED COMPONENTS Interpreter)
if(Python_VERSION VERSION_LESS "3.8")
    message(FATAL_ERROR "Python 3.8+ is required")
endif()

5. 依赖传递性控制

# PRIVATE: 仅当前目标使用(不传递)
target_link_libraries(app PRIVATE fmt::fmt)

# PUBLIC: 当前目标和依赖者都使用
target_link_libraries(mylib PUBLIC OpenSSL::SSL)

# INTERFACE: 仅传递给依赖者(如 header-only 库)
target_link_libraries(header_lib INTERFACE nlohmann_json::nlohmann_json)

⚠️ 注意点

  1. FetchContent 重复下载:使用 FETCHCONTENT_UPDATES_DISCONNECTED ON 避免每次配置都更新
  2. Conan 工具链文件:必须使用 Conan 生成的 conan_toolchain.cmake
  3. vcpkg 三元组:确保 triplet 与目标平台匹配
  4. find_package 版本:系统库版本可能不满足最低要求
  5. 依赖冲突:多个依赖库可能要求不同版本的同一个库

💡 提示

  1. 加速 FetchContent:使用 GIT_SHALLOW TRUE 浅克隆
  2. Conan 二进制缓存:使用 conan install --build=missing 只编译缺失的包
  3. vcpkg 清单模式:推荐使用 vcpkg.json 而非命令行安装
  4. 混合管理:核心依赖用 FetchContent,常用库用 Conan/vcpkg
  5. CI 依赖缓存:缓存 ~/.conan2 或 vcpkg 的 installed/ 目录

工程场景

场景 1:新项目依赖配置

# 推荐: Conan + CMake 预设
# conanfile.py 定义依赖
# CMakePresets.json 定义构建配置
# CI 只需:
#   conan install . -of=build --build=missing
#   cmake --preset default
#   cmake --build --preset default

场景 2:迁移遗留项目到 CMake 包管理

# 步骤 1: 替换手动 -I/-L 路径为 find_package
# 步骤 2: 使用导入目标替代全局设置
# 步骤 3: 添加 Conan/vcpkg 管理第三方库
# 步骤 4: 更新 CI 流水线

场景 3:容器化构建环境

# Dockerfile — 带依赖管理的构建环境
FROM ubuntu:22.04

RUN apt-get update && apt-get install -y \
    build-essential cmake ninja-build \
    python3-pip

RUN pip3 install conan

WORKDIR /app
COPY conanfile.py .

RUN conan install . -of=build --build=missing \
    -s compiler=gcc -s compiler.version=11 \
    -s build_type=Release

COPY . .
RUN cmake -B build \
    -G Ninja \
    -DCMAKE_TOOLCHAIN_FILE=build/conan_toolchain.cmake \
    -DCMAKE_BUILD_TYPE=Release \
    && cmake --build build

扩展阅读