当前位置: 首页 > news >正文

CMake管理外部依赖的模块

在 CMake 中,FetchContentExternalProject 都是管理外部依赖的模块,但它们的 设计目标、使用场景和执行时机 有本质区别。以下通过对比表格、代码示例和场景分析详细说明它们的区别。


核心区别对比表

特性FetchContentExternalProject
执行阶段配置阶段cmake 命令运行时)构建阶段make/ninja 运行时)
集成方式直接作为项目子目录 (add_subdirectory)独立构建,作为外部进程
源码位置默认在 build/_deps 目录下默认在 build/ExternalProject 相关目录
构建控制自动集成到主项目构建流程需手动管理配置、编译、安装等步骤
典型场景头文件库、小型依赖项需编译的复杂库(如 Boost、OpenCV)
适用阶段CMake 配置阶段CMake 构建阶段
灵活性简单易用,但定制性弱复杂但高度可定制(支持分阶段操作)
版本要求CMake ≥3.11CMake ≥2.8.11(基础功能)

场景示例分析

场景1:集成一个仅头文件库(如 spdlog)

推荐使用 FetchContent
无需编译,直接包含头文件即可使用:

cmake_minimum_required(VERSION 3.14)
project(MyApp)include(FetchContent)# 声明依赖
FetchContent_Declare(spdlogGIT_REPOSITORY https://github.com/gabime/spdlog.gitGIT_TAG        v1.11.0  # 固定版本
)# 自动下载并添加子目录
FetchContent_MakeAvailable(spdlog)# 直接使用 spdlog
add_executable(my_app main.cpp)
target_link_libraries(my_app PRIVATE spdlog::spdlog_header_only)

场景2:编译一个需要构建的库(如 GoogleTest)

推荐使用 ExternalProject
需独立编译并安装到系统目录:

cmake_minimum_required(VERSION 3.10)
project(MyProject)include(ExternalProject)# 定义 GoogleTest 的构建流程
ExternalProject_Add(googletestGIT_REPOSITORY  https://github.com/google/googletest.gitGIT_TAG         release-1.12.1CMAKE_ARGS      -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}  # 安装到系统目录TEST_COMMAND    ""  # 跳过测试
)# 主项目链接已安装的库
add_executable(my_test test.cpp)
add_dependencies(my_test googletest)  # 确保先构建 googletest
target_link_libraries(my_test PRIVATE GTest::GTest GTest::Main)

关键区别详解

1. 执行时机
  • FetchContent
    在运行 cmake 配置项目时下载并解压源码,依赖项会成为主项目的一部分,直接参与配置和构建。

  • ExternalProject
    在运行 makeninja 构建项目时才下载和编译依赖项,作为独立进程运行。


2. 源码集成方式
  • FetchContent
    通过 add_subdirectory() 将依赖项源码直接嵌入主项目,共享同一个构建目录和变量作用域。

  • ExternalProject
    依赖项完全独立构建,需通过 find_package() 或手动指定头文件/库路径。


3. 典型使用场景
场景推荐模块原因
引入单头文件库(如 fmt)FetchContent无需编译,直接包含头文件
引入需编译的静态库ExternalProject独立构建,避免污染主项目配置
需要修改依赖项代码ExternalProject可通过 PATCH_COMMAND 修改源码
多阶段构建(如下载+编译)ExternalProject支持分阶段控制(下载、配置、编译、安装)

混合使用场景示例

若需在项目中同时使用两种模块(例如:主项目用 FetchContent,某个子依赖用 ExternalProject):

cmake_minimum_required(VERSION 3.14)
project(HybridExample)# 使用 FetchContent 引入头文件库
include(FetchContent)
FetchContent_Declare(fmtURL https://github.com/fmtlib/fmt/releases/download/9.1.0/fmt-9.1.0.zipURL_HASH SHA256=abcdef...
)
FetchContent_MakeAvailable(fmt)# 使用 ExternalProject 构建复杂库
include(ExternalProject)
ExternalProject_Add(my_heavy_libURL http://example.com/heavy-lib.tar.gzCONFIGURE_COMMAND <源码路径>/configure --prefix=${CMAKE_INSTALL_PREFIX}BUILD_COMMAND makeINSTALL_COMMAND make install
)# 主项目链接两者
add_executable(app main.cpp)
target_link_libraries(app PRIVATE fmt::fmt)
add_dependencies(app my_heavy_lib)  # 确保先构建 my_heavy_lib

如何选择?

  • 优先 FetchContent
    依赖项轻量、无需复杂构建流程、需要直接调用其 CMake 目标时。

  • 选择 ExternalProject
    依赖项需要独立构建、需自定义步骤(如打补丁)、或构建流程复杂(如 Autotools 项目)。


总结

  • FetchContent 是“轻量级依赖管理”,适合简单集成。
  • ExternalProject 是“重型构建工具”,适合完全控制外部项目流程。
  • 根据依赖项的性质(是否需要编译、是否需修改代码)和项目需求(是否需要隔离构建环境)选择合适工具。


为什么 FetchContent_Declare 中可以使用 PATCH_COMMAND

尽管 PATCH_COMMAND 在官方文档中属于 ExternalProject 模块,但在实际使用中,某些 CMake 版本(尤其是较新版本)允许在 FetchContent_Declare 中使用 PATCH_COMMAND。这是因为 FetchContent 底层借用了 ExternalProject 的机制来实现依赖管理,从而间接支持了一些 ExternalProject 的参数。以下是详细解释和注意事项:


1. 底层实现机制

FetchContent 模块在内部调用了 ExternalProject 的功能来管理依赖项的下载和配置。因此,FetchContent_Declare 的某些参数(如 PATCH_COMMAND)实际上是通过 ExternalProject_Add 传递的。尽管官方文档未明确列出这些参数,但在实践中它们可能被隐式支持。

示例代码分析
FetchContent_Declare(san_cmakeGIT_REPOSITORY https://github.com/arsenm/sanitizers-cmakeGIT_TAG        masterSOURCE_DIR     external/sanitizers-cmakePATCH_COMMAND  sed -i 's/old_text/new_text/' ${san_cmake_SOURCE_DIR}/CMakeLists.txt
)
  • 实际行为
    当调用 FetchContent_MakeAvailable(san_cmake) 时,CMake 会通过 ExternalProject 的流程处理依赖项,期间执行 PATCH_COMMAND

2. 版本兼容性

  • CMake ≥3.14
    部分版本开始支持在 FetchContent 中直接使用 ExternalProject 的参数(如 PATCH_COMMAND),但需谨慎使用,因为这是非官方行为。
  • CMake ❤️.14
    此类版本可能直接报错,因为参数未被识别。

3. 使用 PATCH_COMMAND 的风险

尽管功能上有效,但需要注意以下问题:

  1. 非官方支持
    CMake 官方文档未明确说明 FetchContent 支持 PATCH_COMMAND,未来版本可能移除此特性。
  2. 跨平台兼容性
    补丁命令(如 sed)在不同操作系统(Windows/macOS/Linux)中的行为可能不同,需额外处理。
  3. 路径依赖
    必须确保 ${san_cmake_SOURCE_DIR}PATCH_COMMAND 执行时已正确赋值(需在 FetchContent_Populate 之后)。

4. 正确用法示例

若需在 FetchContent 中安全使用 PATCH_COMMAND,应结合 FetchContent_Populate 显式控制流程:

代码示例
cmake_minimum_required(VERSION 3.14)
project(MyProject)include(FetchContent)# 声明依赖项
FetchContent_Declare(san_cmakeGIT_REPOSITORY https://github.com/arsenm/sanitizers-cmakeGIT_TAG        masterSOURCE_DIR     ${CMAKE_SOURCE_DIR}/external/sanitizers-cmakePATCH_COMMAND  sed -i.bak 's/cmake_minimum_required(VERSION 2.8.12)/cmake_minimum_required(VERSION 2.8.12...3.27)/' ${san_cmake_SOURCE_DIR}/CMakeLists.txt
)# 手动触发下载和补丁
FetchContent_GetProperties(san_cmake)
if(NOT san_cmake_POPULATED)FetchContent_Populate(san_cmake)  # 此步骤会执行 PATCH_COMMANDadd_subdirectory(${san_cmake_SOURCE_DIR} ${san_cmake_BINARY_DIR})
endif()# 使用依赖项
include(${san_cmake_SOURCE_DIR}/cmake/sanitize-helpers.cmake)

5. 替代方案推荐

若需更稳定的修补机制,建议以下方法:

方法1:通过 ExternalProject 显式控制
include(ExternalProject)ExternalProject_Add(san_cmakeGIT_REPOSITORY https://github.com/arsenm/sanitizers-cmakeGIT_TAG        masterSOURCE_DIR     ${CMAKE_SOURCE_DIR}/external/sanitizers-cmakePATCH_COMMAND  sed -i 's/.../' <SOURCE_DIR>/CMakeLists.txtCONFIGURE_COMMAND ""BUILD_COMMAND    ""INSTALL_COMMAND  ""
)# 主项目中引用
add_subdirectory(${CMAKE_SOURCE_DIR}/external/sanitizers-cmake)
方法2:下载后手动修补
FetchContent_Declare(san_cmake ...)
FetchContent_MakeAvailable(san_cmake)# 在下载完成后执行修补
add_custom_command(TARGET san_cmakePOST_BUILDCOMMAND sed -i 's/.../' ${san_cmake_SOURCE_DIR}/CMakeLists.txt
)

6. 总结

  • 可用性
    在较新 CMake 版本中,FetchContent_Declare 确实可以借用 ExternalProjectPATCH_COMMAND,但属于非官方行为。
  • 风险
    跨平台兼容性差,未来版本可能不再支持。
  • 推荐做法
    若需修补依赖项,优先使用 ExternalProject 或在 FetchContent 下载后通过 add_custom_command 执行修补。

相关文章:

  • 极大电视 0.0.5.2| 基于Web的电视直播应用,提供高清、流畅的央视频道和各大卫视直播,完全免费无广告
  • C# 方法的结构与执行详解
  • GD32F407单片机开发入门(二十五)HC-SR04超声波模块测距实战含源码
  • [计算机科学#7]:CPU的三阶段,取指令、解码、执行
  • 2025五一杯数学建模B题:矿山数据处理问题,详细问题分析,思路模型
  • 实习入职的总结
  • RPG_5.角色动画
  • 如何拿奖蓝桥杯
  • UN R79 关于车辆转向装置形式认证的统一规定(正文部分2)
  • 数字智慧方案6157丨智慧医疗建设方案(85页PPT)(文末有下载方式)
  • 结构模式识别理论与方法
  • 内存 “舞台” 上,进程如何 “翩翩起舞”?(转)
  • 雅思写作--70个高频表达
  • 华为OD机试真题——斗地主之顺子(2025A卷:100分)Java/python/JavaScript/C/C++/GO最佳实现
  • JDK-17 保姆级安装教程(附安装包)
  • 运维工作中,Ansible常用模块有哪些?
  • 【Python-Day 8】从入门到精通:Python 条件判断 if-elif-else 语句全解析
  • 上位机知识篇---流水线执行
  • KWDB初体验
  • 如何通过日志在本地调试LangChain编写的程序?
  • 用小型核反应堆给数据中心供电,国内企业正在开展项目论证
  • 五一首日出沪高峰,G1503高东收费站上午车速约30公里/小时
  • 乌方公布矿产协议详情:未提债务义务,包含美再援助条款
  • 人民日报评论员:因势利导对经济布局进行调整优化
  • 七部门联合发布《终端设备直连卫星服务管理规定》
  • 五大光伏龙头一季度亏损超80亿元,行业冬天难言结束