如何在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引入。这种方式缓存独立性更强,但需要修改现有依赖配置逻辑。
核心思路
- 创建
ExternalDependencies.cmake,用ExternalProject_Add定义每个依赖,指定安装路径 - 在CMake配置阶段先构建所有依赖,再通过
find_package加载依赖 - Docker中可单独构建依赖并安装到固定目录,后续项目构建直接引用该目录的依赖
内容的提问来源于stack exchange,提问作者Tom McLean
相关产品推荐
相关产品推荐

