CPM:CMake 包管理详细介绍
CPM(CMake Package Manager)是一个专为 CMake 设计的轻量级依赖管理工具。它本质上是一个跨平台的 CMake 脚本,充当 CMake FetchContent 模块的薄包装器,提供了版本控制、缓存、简单 API 等功能,被誉为“CMake 缺失的包管理器”。CPM 旨在实现无设置、跨平台、可重现的依赖管理,帮助开发者轻松处理 C++ 项目中的外部依赖,而无需额外的安装步骤。
CPM 的工作原理
CPM 在 CMake 的配置阶段(configure stage)获取和配置依赖项。它利用 FetchContent 下载项目或资源,对于现代 CMake 项目,它会自动配置;对于其他项目,则允许手动创建目标。依赖项通过 Git 提交或标签进行版本控制,确保构建的可重现性。
- 递归依赖处理:CPM 确保依赖项不会重复添加,并以最低所需版本添加。
- 离线构建支持:如果源已缓存,CPM 会覆盖 CMake 的下载命令,实现离线配置。
- 自动浅克隆:在使用版本标签和源缓存时,进行浅克隆以提高速度并减少存储。
- 覆盖机制:可以通过 find_package 覆盖依赖,使用系统包管理器集成。
CPM 不处理预构建二进制文件,而是从源代码下载并构建依赖,这使得它适合需要精确控制的环境。
安装 CPM
安装 CPM 非常简单,无需全局安装。只需将 CPM 脚本添加到项目中:
-
在项目目录中创建
cmake
文件夹。 -
下载最新版本的
get_cpm.cmake
或直接CPM.cmake
:mkdir -p cmake wget -O cmake/CPM.cmake https://github.com/cpm-cmake/CPM.cmake/releases/latest/download/get_cpm.cmake
或者,在项目的 CMakeLists.txt
中直接下载 CPM:
include(FetchContent)
FetchContent_Declare(_cpmURL https://github.com/cpm-cmake/CPM.cmake/releases/latest/download/get_cpm.cmake
)
FetchContent_MakeAvailable(_cpm)
include(${_cpm_SOURCE_DIR}/CPM.cmake)
这种方式确保 CPM 与项目一起分发,无需额外设置。
使用示例
CPM 的核心函数是 CPMAddPackage
,它接受命名参数来声明依赖。
完整 CMakeLists.txt 示例
cmake_minimum_required(VERSION 3.14 FATAL_ERROR)# 创建项目
project(MyProject)# 添加可执行文件
add_executable(main main.cpp)# 包含 CPM
include(cmake/CPM.cmake)# 添加依赖
CPMAddPackage("gh:fmtlib/fmt#7.1.3")
CPMAddPackage("gh:nlohmann/json@3.10.5")
CPMAddPackage("gh:catchorg/Catch2@3.4.0")# 链接依赖
target_link_libraries(main fmt::fmt nlohmann_json::nlohmann_json Catch2::Catch2WithMain)
这个示例展示了如何添加 fmt、json 和 Catch2 库,并链接到主目标。
CPMAddPackage 函数参数
参数 | 描述 | 示例 |
---|---|---|
NAME | 依赖的唯一名称(应为导出的目标名称) | NAME fmt |
VERSION | 依赖的最低版本(可选,默认 0) | VERSION 7.1.3 |
PATCHES | 要应用的补丁文件序列(使用 patch 命令) | PATCHES fix1.patch fix2.patch |
OPTIONS | 传递给依赖的配置选项 | OPTIONS "JSON_BuildTests OFF" |
DOWNLOAD_ONLY | 只下载项目,不配置(可选) | DOWNLOAD_ONLY YES |
GIT_REPOSITORY | Git 仓库 URL(或其他来源,如直接 URL) | GIT_REPOSITORY https://github.com/fmtlib/fmt |
GIT_TAG | Git 标签或提交哈希(默认 v(VERSION)) | GIT_TAG 7.1.3 |
简写语法示例:
- Git 包:
CPMAddPackage("gh:fmtlib/fmt@7.1.3")
- 带标签:
CPMAddPackage("gh:fmtlib/fmt#v7.1.3")
- 存档包:
CPMAddPackage("https://example.com/my-package-1.2.3.zip")
添加后,可访问变量如 <dependency>_SOURCE_DIR
、CPM_LAST_PACKAGE_NAME
等。
特定库示例
-
Catch2:
CPMAddPackage("gh:catchorg/Catch2@2.5.0")
-
yaml-cpp(指定版本以匹配异常标签格式):
CPMAddPackage("gh:jbeder/yaml-cpp#yaml-cpp-0.6.3@0.6.3")
-
Boost(使用源存档加速):
CPMAddPackage(NAME BoostVERSION 1.84.0URL https://github.com/boostorg/boost/releases/download/boost-1.84.0/boost-1.84.0.tar.xzURL_HASH SHA256=2e64e5d79a738d0fa6fb546c6e5c2bd28f88d268a2a080546f74e5ff98f29d0eOPTIONS "BOOST_ENABLE_CMAKE ON" )
-
Lua(手动创建目标,因为无 CMake 支持):
CPMAddPackage(NAME luaGIT_REPOSITORY https://github.com/lua/lua.gitVERSION 5.3.5DOWNLOAD_ONLY YES )if (lua_ADDED)FILE(GLOB lua_sources ${lua_SOURCE_DIR}/*.c)list(REMOVE_ITEM lua_sources "${lua_SOURCE_DIR}/lua.c" "${lua_SOURCE_DIR}/luac.c")add_library(lua STATIC ${lua_sources})target_include_directories(lua PUBLIC $<BUILD_INTERFACE:${lua_SOURCE_DIR}>) endif()
优势
CPM 的主要优势包括:
- 小型且可重用项目:处理所有依赖,让开发者专注于创建小型、测试良好的库。
- 跨平台:在配置阶段添加项目,兼容所有 CMake 工具链和生成器。
- 可重现构建:通过 Git 版本控制确保项目始终可构建。
- 即插即用:无需安装,只需添加脚本即可。
- 无需打包:直接添加外部源作为依赖。
- 简单源分发:简化包含源文件和依赖的项目,减少对单头文件或 Git 子模块的需求。
- 离线和缓存支持:使用
CPM_SOURCE_CACHE
避免重复下载。
特性
- 版本检查:如果依赖需要更新的版本,会发出警告。
- 包锁定文件:管理传递依赖,而不修改 CMakeLists.txt。使用
CPMUsePackageLock(package-lock.cmake)
并通过目标更新。 - 本地包覆盖:通过 CMake 选项如
-DCPM_<dependency>_SOURCE=/path/to/dep
覆盖依赖。 - 补丁支持:顺序应用补丁文件。
- 选项传递:自定义依赖配置。
- 私有仓库支持:在 CI 中使用 Git 配置重写 URL 以添加令牌。
- 变量和标志:如
EXCLUDE_FROM_ALL
、SYSTEM
、FIND_PACKAGE_ARGUMENTS
。 - 自定义缓存键:用于使用外部文件的补丁。
配置选项如 CPM_USE_LOCAL_PACKAGES
(优先 find_package)、CPM_DOWNLOAD_ALL
(强制下载以实现可重现性)等。
与其他工具的比较
与 find_package 比较
find_package 依赖系统安装的库,版本不明确,可能导致不可预测构建。CPM 从源构建依赖,确保一致性,但不暴露如 <PackageName>_LIBRARIES
的变量(需手动处理)。
与 vcpkg 或 Conan 比较
vcpkg 和 Conan 是更全面的包管理器,支持预构建二进制和安装步骤,适合复杂项目。CPM 是无设置的,仅在配置时添加,不处理二进制。CPM 可以与它们集成,通过 CPM_USE_LOCAL_PACKAGES
使用 find_package。CPM 更轻量,适合快速启动,而 vcpkg/Conan 提供更多功能如二进制缓存。
与纯 FetchContent / ExternalProject 比较
CPM 是 FetchContent 的包装器,添加了简单 API、版本检查、离线构建、浅克隆、包锁定等功能。ExternalProject 在构建时添加,复杂化工具链和嵌套依赖;FetchContent 在配置时,但缺少 CPM 的扩展。
限制
- 无预构建二进制:每次新构建目录都从源构建依赖。建议使用
CPM_SOURCE_CACHE
和 ccache 缓解。 - 依赖 CMakeLists 质量:许多库的 CMakeLists 不适合子项目,需要手动配置。
- 菱形依赖:使用第一个添加的版本,如果冲突则警告;需手动解决。
- 策略设置:将某些 CMake 策略设为 NEW(如 CMP0077),如果需要 OLD 则需调整代码。
最近更新与版本
CPM 的最新版本可从 GitHub Releases 下载。示例中使用如 Catch2@3.4.0、Boost 1.84.0 等,显示与近期库兼容。社区讨论(如 Reddit 和 Discourse)显示 CPM 仍在活跃维护,2024 年有新视频和文章讨论其优势。作为开源项目,建议定期更新脚本以获取改进。
总体而言,CPM 特别适合小型项目、快速原型和跨平台开发,是 CMake 生态中一个高效的依赖管理解决方案。如果项目复杂,考虑结合 vcpkg 或 Conan 以获得更多功能。