You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Dockerfile中缓存通过FetchContent_Declare下载的依赖安装

C++项目依赖缓存优化问题

项目背景

我正在开发一个C++项目,使用CMake的FetchContent_Declare下载并管理所有依赖。项目结构如下:

├── CMakeLists.txt
├── CMakePresets.json
├── Dockerfile
├── get_dependencies.cmake
├── get_grpc.cmake
├── run_unittests.sh
├── src
│   ├── CMakeLists.txt
│   ├── GribManager
│   ├── RoutingLib
│   ├── RoutingLibTests
│   └── RoutingPython

依赖通过get_dependencies.cmake和get_grpc.cmake管理,示例内容:

include(FetchContent)

message(STATUS "Getting iGraph")
FetchContent_Declare(
    igraph
    URL https://github.com/igraph/igraph/releases/download/0.10.4/igraph-0.10.4.tar.gz
    URL_HASH MD5=10a3f325425970c75a7ba8359376e208
    FIND_PACKAGE_ARGS NAMES igraph
    SYSTEM
)

FetchContent_MakeAvailable(igraph)
target_compile_options(igraph PRIVATE -O3)

message(STATUS "Getting GEOS")
FetchContent_Declare(
    GEOS
    URL https://download.osgeo.org/geos/geos-3.11.1.tar.bz2
    URL_HASH MD5=5732ec96b391ecddc35bda9795b654ea
    FIND_PACKAGE_ARGS NAMES GEOS
    SYSTEM
)

FetchContent_MakeAvailable(GEOS)

顶层CMakeLists.txt引入依赖配置后构建项目:

cmake_minimum_required(VERSION 3.15)
SET(CMAKE_CXX_STANDARD 17)

add_compile_options(-fPIC)

project(RoutingLib C CXX)

set(CMAKE_CXX_FLAGS_OLD "${CMAKE_CXX_FLAGS}")
set(CMAKE_CXX_FLAGS "-O3")
include(get_grpc.cmake)
include(get_dependencies.cmake)
set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS_OLD})

add_subdirectory(src)

项目使用Docker构建,Dockerfile负责构建、测试、生成Python绑定:

FROM quay.io/pypa/manylinux2014_x86_64:latest

RUN export MY_INSTALL_DIR=$HOME/.local
RUN git clone --recurse-submodules --depth 1 --shallow-submodules https://github.com/grpc/grpc
WORKDIR /grpc
RUN mkdir -p cmake/build
RUN cmake -DgRPC_INSTALL=ON -DgRPC_BUILD_TESTS=OFF -DCMAKE_INSTALL_PREFIX=$MY_INSTALL_DIR && \
    make -j 4 && \
    make install

WORKDIR /

COPY run_unittests.sh run_unittests.sh
RUN chmod +x run_unittests.sh

COPY CMakePresets.json CMakePresets.json
COPY get_grpc.cmake get_grpc.cmake
COPY get_dependencies.cmake get_dependencies.cmake
COPY CMakeLists.txt CMakeLists.txt
COPY src src

RUN mkdir build
WORKDIR /build
ARG BUILD_VERSION="0.0.0"
ARG PYTHON_EXECUTABLE="/opt/python/cp310-cp310/bin/python"

# Run unit tests in debug
RUN cmake .. --preset debug_linux -DBUILD_VERSION=${BUILD_VERSION} -DPython_EXECUTABLE=${PYTHON_EXECUTABLE}
WORKDIR /build/Debug
RUN cmake --build . --target routinglib_unittests

# Run code coverage
WORKDIR /
RUN ./run_unittests.sh

WORKDIR /build
# Build python package
RUN cmake .. --preset release_linux -DBUILD_VERSION=${BUILD_VERSION} -DPython_EXECUTABLE=${PYTHON_EXECUTABLE}
WORKDIR /build/Release
RUN cmake --build . --target python_package
WORKDIR /build/Release/src/RoutingPython/dist
RUN auditwheel repair WeatherRouting-0.0.0-cp310-cp1310-linux_x86_64.whl

当前问题

每次src目录变更时,Docker会重新执行后续的CMake配置步骤,导致依赖的下载和构建无法被缓存,大幅增加构建时间。需要在保持安装便捷的前提下,分离依赖的下载/缓存与项目源码的构建。


解决方案

方案一:优化Docker缓存层级+CMake条件编译

这是最轻量化的修改,无需大幅调整现有依赖管理逻辑,仅通过调整Dockerfile顺序和添加CMake选项实现缓存分离。

步骤1:修改顶层CMakeLists.txt,添加项目构建开关

添加BUILD_PROJECT选项,允许单独处理依赖而不构建项目源码:

cmake_minimum_required(VERSION 3.15)
SET(CMAKE_CXX_STANDARD 17)

add_compile_options(-fPIC)

project(RoutingLib C CXX)

# 添加开关,控制是否构建项目源码
option(BUILD_PROJECT "Build the main project sources" ON)

set(CMAKE_CXX_FLAGS_OLD "${CMAKE_CXX_FLAGS}")
set(CMAKE_CXX_FLAGS "-O3")
include(get_grpc.cmake)
include(get_dependencies.cmake)
set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS_OLD})

# 仅当开关开启时,才添加src目录
if(BUILD_PROJECT)
    add_subdirectory(src)
endif()

步骤2:调整Dockerfile的文件复制与构建顺序

先复制不常变更的依赖配置文件,提前完成依赖的下载和构建,再复制src目录进行项目构建:

FROM quay.io/pypa/manylinux2014_x86_64:latest

RUN export MY_INSTALL_DIR=$HOME/.local
RUN git clone --recurse-submodules --depth 1 --shallow-submodules https://github.com/grpc/grpc
WORKDIR /grpc
RUN mkdir -p cmake/build
RUN cmake -DgRPC_INSTALL=ON -DgRPC_BUILD_TESTS=OFF -DCMAKE_INSTALL_PREFIX=$MY_INSTALL_DIR && \
    make -j 4 && \
    make install

WORKDIR /

# 先复制不常变更的文件:脚本、CMake配置
COPY run_unittests.sh run_unittests.sh
RUN chmod +x run_unittests.sh

COPY CMakePresets.json CMakePresets.json
COPY get_grpc.cmake get_grpc.cmake
COPY get_dependencies.cmake get_dependencies.cmake
COPY CMakeLists.txt CMakeLists.txt

# 创建临时目录,提前处理依赖(不构建项目源码)
RUN mkdir build_deps
WORKDIR /build_deps
ARG BUILD_VERSION="0.0.0"
ARG PYTHON_EXECUTABLE="/opt/python/cp310-cp310/bin/python"

# 配置CMake,仅下载并构建依赖
RUN cmake .. --preset release_linux -DBUILD_VERSION=${BUILD_VERSION} -DPython_EXECUTABLE=${PYTHON_EXECUTABLE} -DBUILD_PROJECT=OFF
# 触发依赖构建
RUN cmake --build .

# 此时依赖已缓存,再复制src目录
WORKDIR /
COPY src src

# 开始构建项目和测试
RUN mkdir build
WORKDIR /build
# 配置CMake,开启项目构建
RUN cmake .. --preset debug_linux -DBUILD_VERSION=${BUILD_VERSION} -DPython_EXECUTABLE=${PYTHON_EXECUTABLE}
WORKDIR /build/Debug
RUN cmake --build . --target routinglib_unittests

# 运行单元测试和覆盖率
WORKDIR /
RUN ./run_unittests.sh

# 构建Python包
WORKDIR /build
RUN cmake .. --preset release_linux -DBUILD_VERSION=${BUILD_VERSION} -DPython_EXECUTABLE=${PYTHON_EXECUTABLE}
WORKDIR /build/Release
RUN cmake --build . --target python_package
WORKDIR /build/Release/src/RoutingPython/dist
RUN auditwheel repair WeatherRouting-0.0.0-cp310-cp310-linux_x86_64.whl

效果

当src目录变更时,仅COPY src及后续步骤会重新执行,依赖的下载和构建步骤因依赖配置文件未变更而被Docker缓存,大幅缩短构建时间。

方案二:改用ExternalProject实现彻底的依赖分离(可选)

如果需要更精细的依赖控制,可以改用CMake的ExternalProject_Add替代FetchContent,将依赖安装到独立目录,再通过find_package引入。这种方式缓存独立性更强,但需要修改现有依赖配置逻辑。

核心思路

  1. 创建ExternalDependencies.cmake,用ExternalProject_Add定义每个依赖,指定安装路径
  2. 在CMake配置阶段先构建所有依赖,再通过find_package加载依赖
  3. Docker中可单独构建依赖并安装到固定目录,后续项目构建直接引用该目录的依赖

内容的提问来源于stack exchange,提问作者Tom McLean

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 12:59:15