ExternalProject_Add 使用手册与文档详解
本文目录 展开章节导航
关键区别:在构建期运行外部项目
ExternalProject_Add 来自 ExternalProject 模块,先 include(ExternalProject) 才能使用。它为下载、配置、构建、安装等步骤创建目标,但不会像 add_subdirectory 那样把外部库的 CMake target 自动带进当前作用域。
因此,“调用 ExternalProject 后立刻 find_package”通常会在第一次配置时失败:那时依赖还没有安装。需要选择 superbuild、显式 imported target,或更适合配置期集成的 FetchContent。
目录与步骤速查
| 选项 | 作用 | 易错点 |
|---|---|---|
PREFIX | 组织外部项目的工作目录 | 不代表必然有 prefix/install 子目录 |
SOURCE_DIR | 外部源码目录 | 不把已有工作树当下载器的可清理目录 |
BINARY_DIR | 独立构建目录 | 优先 out-of-source 构建 |
INSTALL_DIR | 提供安装目录占位符 | 仍需传给外部项目的 CMAKE_INSTALL_PREFIX |
DEPENDS | 指定其他目标依赖 | 链接消费者也需正确依赖外部构建产物 |
BUILD_BYPRODUCTS | 声明构建阶段生成物 | Ninja 等生成器需要知道谁生成库文件 |
LOG_* | 保存步骤输出 | 下载、配置与编译错误分开定位 |
可在本地复现的最小示例
以下是 Linux、单配置 Ninja/Makefiles 下的四个文件,不需要下载第三方仓库。演示库使用自己的源代码,实际外部项目需核对安装布局和编译选项。
主项目 CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(external_demo LANGUAGES CXX)
include(ExternalProject)
set(HELLO_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/external/hello")
set(HELLO_BUILD "${CMAKE_CURRENT_BINARY_DIR}/hello-build")
ExternalProject_Add(hello_ep
SOURCE_DIR "${HELLO_SOURCE}"
BINARY_DIR "${HELLO_BUILD}"
DOWNLOAD_COMMAND ""
UPDATE_COMMAND ""
CMAKE_ARGS
-DCMAKE_BUILD_TYPE:STRING=Release
-DCMAKE_CXX_COMPILER:FILEPATH=${CMAKE_CXX_COMPILER}
INSTALL_COMMAND ""
BUILD_BYPRODUCTS "${HELLO_BUILD}/libhello.a"
LOG_CONFIGURE TRUE
LOG_BUILD TRUE
LOG_OUTPUT_ON_FAILURE TRUE
)
add_library(hello_imported STATIC IMPORTED GLOBAL)
set_target_properties(hello_imported PROPERTIES
IMPORTED_LOCATION "${HELLO_BUILD}/libhello.a"
)
add_dependencies(hello_imported hello_ep)
add_executable(app main.cpp)
target_link_libraries(app PRIVATE hello_imported)external/hello/CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(hello LANGUAGES CXX)
add_library(hello STATIC hello.cpp)两个 C++ 源文件
external/hello/hello.cpp:
int hello() { return 42; }main.cpp:
#include <iostream>
int hello();
int main() {
const int value = hello();
std::cout << value << '\n';
return value == 42 ? 0 : 1;
}构建与运行:
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --parallel 2
./build/app预期输出 42。完整验证应包含全新构建目录与第二次增量构建。多配置生成器、Windows 库名、Debug 后缀和共享库运行路径不在此最小示例的范围内。
下载第三方依赖时
Git 源尽量固定提交;使用 GIT_SHALLOW TRUE 时不能任意指定历史提交哈希,需遵循该选项对分支/标签的限制。URL 下载提供完整 SHA256,3308f84... 之类省略值不能运行。
INACTIVITY_TIMEOUT 针对支持它的下载行为,不是任意编译进程的通用超时。不要用 BUILD_COMMAND make 写死生成器;普通 CMake 外部项目可使用默认构建步骤。
离线复现还需要保存源包和依赖版本。不要声称加上某个选项就自动解决所有网络、ABI 或工具链差异。
FCL / libccd / Eigen 集成检查
原工程中 FCL 依赖 libccd、Eigen 等组件,集成时需明确:
- 外部依赖的先后构建顺序及消费者 target 的依赖。
- 每个库的头文件、库文件与 CMake package 安装位置。
- 静态/动态链接、位置无关代码和运行时搜索路径。
- 同一二进制边界上的 C++ 标准库 ABI 与编译选项。
_GLIBCXX_USE_CXX11_ABI=0 不是“兼容旧 GCC”的万能开关;只有链接边界确实要求旧 libstdc++ dual ABI 时才采用,并确保相关二进制一致。不要因为头文件库 Eigen 出现在依赖列表里,就把所有 ABI 问题归结为它。
参考:CMake ExternalProject 官方文档、GCC dual ABI 说明。
阅读自测与验收
- 使用全新构建目录和第二次增量构建分别运行 app;只有增量构建成功时,应检查是否依赖了旧库产物。
- 切换编译器、构建类型或生成器时使用独立目录,核对导入库路径和 BUILD_BYPRODUCTS,不能只看头文件是否可找到。