CMake进阶: externalproject_add用于在构建阶段下载、配置、构建和安装外部项目

目录

1.简介

2.核心作用

[3.完整示例:集成 fmt 库](#3.完整示例:集成 fmt 库)

4.externalproject_add与FetchContent区别

5.总结

相关链接


1.简介

externalproject_add 是 CMake 中 ExternalProject 模块提供的核心命令,用于在构建阶段 下载、配置、构建和安装外部项目(如第三方库)。它与 FetchContent 的核心区别在于:FetchContent配置阶段 处理依赖,而 externalproject_add构建阶段处理,更适合复杂的外部项目集成(如非 CMake 项目、需要自定义构建步骤的项目)。

CMake进阶: 使用FetchContent方法基于gTest的C++单元测试_cmake fetchcontent-CSDN博客

基本语法:

cpp 复制代码
include(ExternalProject)  # 必须先引入模块

ExternalProject_Add(
  <项目名称>  # 自定义名称,用于标识外部项目
  [SOURCE_DIR <源码目录>]  # 外部项目源码路径(绝对路径或相对路径)
  [BINARY_DIR <构建目录>]  # 外部项目构建路径
  [INSTALL_DIR <安装目录>]  # 外部项目安装路径(默认与构建目录关联)
  
  # 下载方式(选一种)
  [GIT_REPOSITORY <Git仓库地址>]  # 从Git仓库下载
  [GIT_TAG <标签/分支/commit>]    # Git版本标识
  [URL <压缩包地址>]              # 从压缩包下载(.zip/.tar.gz等)
  [URL_HASH <算法>=<哈希值>]      # 验证压缩包完整性
  
  # 配置步骤(针对CMake项目)
  [CMAKE_ARGS <参数1> <参数2>...]  # 传递给外部项目的CMake参数(如编译选项)
  
  # 构建步骤
  [BUILD_COMMAND <命令>]  # 自定义构建命令(默认由生成器决定,如make、msbuild)
  [BUILD_IN_SOURCE ON]    # 是否在源码目录内构建(默认在BINARY_DIR)
  
  # 安装步骤
  [INSTALL_COMMAND <命令>]  # 自定义安装命令(默认是make install等)
  
  # 依赖关系
  [DEPENDS <其他外部项目>]  # 声明依赖的其他外部项目(确保先构建依赖)
)

2.核心作用

externalproject_add 用于在当前项目的构建过程中,动态集成外部项目,支持:

  1. 从 Git 仓库、压缩包等来源下载外部项目源码;
  2. 自定义外部项目的配置(如编译选项、安装路径);
  3. 控制外部项目的构建和安装流程;
  4. 声明外部项目之间的依赖关系(确保构建顺序)。

3.完整示例:集成 fmt 库

以下示例使用 externalproject_add 集成 fmt 库(一个 C++ 格式化库),并在主项目中使用。

1.项目结构

cpp 复制代码
external_demo/
├── CMakeLists.txt  # 根配置
└── main.cpp        # 主程序(使用 fmt 库)

2.CMakeLists.txt 配置

cpp 复制代码
cmake_minimum_required(VERSION 3.14)
project(ExternalDemo)

# 引入 ExternalProject 模块
include(ExternalProject)

# 定义外部项目安装路径(可选,统一管理外部依赖安装目录)
set(EXTERNAL_INSTALL_DIR ${CMAKE_BINARY_DIR}/external_install)
file(MAKE_DIRECTORY ${EXTERNAL_INSTALL_DIR}/include)  # 确保头文件目录存在
file(MAKE_DIRECTORY ${EXTERNAL_INSTALL_DIR}/lib)      # 确保库目录存在

# -------------------------- 声明外部项目 fmt --------------------------
ExternalProject_Add(
  fmt_external  # 外部项目名称(自定义)
  
  # 下载配置(从Git仓库获取)
  GIT_REPOSITORY https://github.com/fmtlib/fmt.git
  GIT_TAG 10.2.1  # 版本标签
  GIT_SHALLOW ON  # 浅克隆
  
  # 目录配置
  SOURCE_DIR ${CMAKE_BINARY_DIR}/external_src/fmt  # 源码目录
  BINARY_DIR ${CMAKE_BINARY_DIR}/external_build/fmt  # 构建目录
  INSTALL_DIR ${EXTERNAL_INSTALL_DIR}  # 安装目录(自定义)
  
  # 传递给 fmt 的 CMake 配置参数(如指定安装路径、禁用测试)
  CMAKE_ARGS
    -DCMAKE_INSTALL_PREFIX=${EXTERNAL_INSTALL_DIR}  # 指定安装路径
    -DFMT_BUILD_TESTS=OFF  # 禁用 fmt 自身的测试
    -DCMAKE_CXX_STANDARD=17  # 指定 C++ 标准
  
  # 安装后,fmt 会将头文件安装到 ${EXTERNAL_INSTALL_DIR}/include,库文件到 lib
)

# -------------------------- 主项目配置 --------------------------
# 定义主程序目标
add_executable(myapp main.cpp)

# 依赖外部项目 fmt_external(确保 fmt 先安装完成)
add_dependencies(myapp fmt_external)

# 链接 fmt 库(需手动指定头文件和库路径)
target_include_directories(myapp PRIVATE ${EXTERNAL_INSTALL_DIR}/include)
target_link_directories(myapp PRIVATE ${EXTERNAL_INSTALL_DIR}/lib)
target_link_libraries(myapp PRIVATE fmt)  # fmt 库名称

# 指定 C++ 标准
target_compile_features(myapp PRIVATE cxx_std_17)

3.main.cpp 代码

cpp 复制代码
#include <fmt/core.h>

int main() {
  fmt::print("Hello, {}! 2+3={}\n", "fmt", 2+3);  // 使用 fmt 库
  return 0;
}

4.构建与运行

cpp 复制代码
# 创建构建目录
mkdir build && cd build

# 生成构建文件
cmake ..

# 编译(会先下载、构建、安装 fmt,再编译主程序)
cmake --build .

# 运行主程序
./Debug/myapp.exe  # Windows(Debug 配置)
# 或 ./myapp  # Linux/macOS

输出:Hello, fmt! 2+3=5

5.关键参数说明

  1. CMAKE_ARGS:向外部 CMake 项目传递配置参数(如安装路径、编译选项),是最常用的参数之一。
  2. INSTALL_DIR:指定外部项目的安装目录,便于主项目统一引用头文件和库。
  3. add_dependencies:声明主项目依赖外部项目,确保外部项目先构建安装完成。
  4. SOURCE_DIR/BINARY_DIR :手动指定源码和构建目录(可选,默认在构建目录的 ExternalProject 子目录)。

4.externalproject_add与FetchContent区别

1.处理阶段不同

  • FetchContent :在配置阶段(cmake 命令执行时) 处理外部依赖。

    它会在 CMake 配置项目时下载 / 获取外部源码,并将其作为当前项目的一部分直接集成到构建系统中(类似 add_subdirectory)。配置完成后,外部依赖的目标(targets)会直接暴露给当前项目。

  • ExternalProject_Add :在构建阶段(make/ninja 等命令执行时) 处理外部依赖。

    它仅在 CMake 配置时声明外部项目的构建规则,实际的下载、配置、编译、安装等操作会延迟到用户执行构建命令时才执行。配置阶段不会获取源码或构建依赖,仅记录构建步骤。

2.集成方式不同

  • FetchContent深度集成 ,外部依赖作为当前项目的 "子项目"。

    通过 FetchContent_MakeAvailable() 会自动调用 add_subdirectory() 将外部项目添加到当前构建系统,因此外部项目的目标(如库目标 gtest)可以直接被当前项目的目标(如 target_link_libraries)引用,无需手动指定路径。

    示例:

cpp 复制代码
include(FetchContent)
FetchContent_Declare(
  googletest
  URL https://github.com/google/googletest/archive/refs/tags/v1.14.0.zip
)
FetchContent_MakeAvailable(googletest)  # 相当于 add_subdirectory(外部源码目录)

# 直接使用外部项目的目标
add_executable(my_test test.cpp)
target_link_libraries(my_test gtest_main)  # gtest_main 是 googletest 的目标
  • ExternalProject_Add独立构建 ,外部依赖作为 "独立项目"。

    它会为外部依赖创建一个独立的构建流程(下载、配置、编译、安装),生成的库 / 二进制文件位于单独的目录中。当前项目需要手动指定外部依赖的路径(源码目录、二进制目录)才能引用其产物(如库文件),无法直接使用其目标。

    示例:

cpp 复制代码
include(ExternalProject)
ExternalProject_Add(
  googletest
  URL https://github.com/google/googletest/archive/refs/tags/v1.14.0.zip
  PREFIX ${CMAKE_BINARY_DIR}/googletest  # 外部项目的构建/安装根目录
  INSTALL_COMMAND ""  # 禁用安装(如需安装可指定路径)
)

# 获取外部项目的路径
ExternalProject_Get_Property(googletest SOURCE_DIR BINARY_DIR)

# 手动指定库路径并链接
add_executable(my_test test.cpp)
target_include_directories(my_test PRIVATE ${SOURCE_DIR}/googletest/include)
target_link_libraries(my_test PRIVATE ${BINARY_DIR}/lib/libgtest_main.a)
add_dependencies(my_test googletest)  # 确保外部项目先于当前目标构建

3.依赖关系管理不同

  • FetchContent :依赖关系由 CMake 自动处理。

    由于外部项目的目标在配置阶段已暴露,当前项目的目标可以直接依赖外部目标(如 target_link_libraries),CMake 会自动调整构建顺序(先构建外部依赖,再构建当前目标)。

  • ExternalProject_Add :依赖关系需要手动声明。

    外部项目是独立构建的,当前项目必须通过 add_dependencies() 显式指定依赖关系,否则可能出现 "当前目标先于外部依赖构建" 的错误。

4.适用场景不同

  • FetchContent 更适合

    • 外部依赖是 CMake 项目 (可通过 add_subdirectory 集成)。
    • 需要在配置阶段确定依赖是否可用(如条件编译依赖于外部项目的特性)。
    • 依赖规模较小,希望简化集成(直接使用目标,无需手动处理路径)。
    • 希望外部依赖的构建产物(如库)与当前项目的构建目录合并管理。
  • ExternalProject_Add 更适合

    • 外部依赖非 CMake 项目 (如 Makefile 项目、Autotools 项目),需要自定义构建命令(如 CONFIGURE_COMMANDBUILD_COMMAND)。
    • 依赖规模较大,希望延迟构建(避免配置阶段耗时过长)。
    • 需要独立控制外部项目的安装路径(如安装到系统目录或自定义前缀)。
    • 依赖需要跨项目共享(如多个子项目共用同一个外部依赖的构建产物)。

5.其他关键差异

特性 FetchContent ExternalProject_Add
源码获取时机 配置阶段(cmake 时) 构建阶段(make/ninja 时)
目标(targets)暴露 自动暴露,可直接引用 不暴露,需手动处理路径
支持非 CMake 项目 不支持(依赖 add_subdirectory 支持(可自定义构建命令)
配置阶段耗时 可能较长(需下载 / 处理源码) 较短(仅声明规则,不执行实际操作)
安装支持 通常作为子项目构建,不单独安装 支持自定义安装步骤(INSTALL_COMMAND

总结:

  • 优先用 FetchContent:依赖是 CMake 项目,追求简单集成,需要在配置阶段确定依赖。
  • 优先用 ExternalProject_Add:依赖是非 CMake 项目,需要独立控制构建 / 安装,或希望延迟构建。

5.总结

externalproject_add 是处理复杂外部依赖的灵活工具,适合非 CMake 项目或需要自定义构建流程的场景。但由于其在构建阶段执行且不自动暴露目标,配置复杂度高于 FetchContent。实际项目中,优先使用 FetchContent 处理 CMake 依赖,复杂场景再考虑 externalproject_add

相关链接

相关推荐
rhythmcc42 分钟前
【visual studio】visual studio配置环境opencv和onnxruntime
c++·人工智能·opencv
檀越剑指大厂1 小时前
【Linux系列】服务器 IP 地址查询
linux·服务器·tcp/ip
Skylar_.3 小时前
嵌入式 - Linux软件编程:进程
java·linux·服务器
rannn_1113 小时前
【Linux学习|黑马笔记|Day4】IP地址、主机名、网络请求、下载、端口、进程管理、主机状态监控、环境变量、文件的上传和下载、压缩和解压
linux·笔记·后端·学习
長琹3 小时前
9、C 语言内存管理知识点总结
linux·c语言
白书宇3 小时前
5.从零开始写LINUX内核--从实模式到保护模式的过渡实现
linux·汇编·数据库·开源
野生柚子4 小时前
记录学习K8s 集群中OOM Killer的决策基准及执行流程
linux·运维
码与农4 小时前
C++设计模式
开发语言·c++
Tipriest_4 小时前
CMake message()使用指南
c++·cmake·message