使用 Docker build 和 lib.rs 缓存 Rust 依赖项

Cache Rust dependencies with Docker build and lib.rs

我一直在尝试为我的 rust 构建创建一个 Dockerfile,它允许我从依赖项中单独构建应用程序,如下所示:

然而,这似乎对我不起作用,因为 lib.rs 文件的工作树略有不同。我的 Dockerfile 布局如下:

FROM rust:1.60 as build

# create a new empty shell project
RUN USER=root cargo new --bin rocket-example-pro

WORKDIR /rocket-example-pro

# create dummy lib.rs file to build dependencies separately from changes to src
RUN touch src/lib.rs 

# copy over your manifests
COPY ./Cargo.lock ./Cargo.lock
COPY ./Cargo.toml ./Cargo.toml

RUN cargo build --release --locked
RUN rm src/*.rs

# copy your source tree
COPY ./src ./src

# build for release
RUN rm ./target/release/deps/rocket_example_pro*

RUN cargo build --release --locked ## <-- fails

# our final base
FROM rust:1.60

# copy the build artifact from the build stage
COPY --from=build /rocket-example-pro/target/release/rocket_example_pro .

# set the startup command to run your binary
CMD ["./rocket_example_pro"]

正如您最初看到的,我复制了 toml 文件并执行构建,类似于之前演示的那样。然而,由于我的项目结构略有不同,我似乎遇到了问题,因为我的 main.rs 几乎只有一行调用我的 lib.rs 中的主要方法。 lib.rs 也在我的 toml 文件中定义,该文件在构建依赖项之前被复制,并要求我触摸 lib.rs 文件以使其不会在此处构建失败,否则会丢失。

在我似乎无法解决的第二个构建步骤中,在我复制了实际的源文件来构建应用程序之后,我收到了错误消息

Compiling rocket_example_pro v0.1.0 (/rocket-example-pro)
error[E0425]: cannot find function `run` in crate `rocket_example_pro`
 --> src/main.rs:3:22
  |
3 |     rocket_example_pro::run().unwrap();
  |                      ^^^ not found in `rocket_example_pro`

在一个空目录中自己执行这些步骤时,我自己似乎并没有遇到相同的错误,而是最后一步成功了,但是生成的 rocket-example-pro 可执行文件似乎仍然是 shell 示例项目只打印 'Hello world' 而不是我在第二次构建之前复制的火箭应用程序。

据我所知,第一个构建似乎影响了第二个,也许当我触摸虚拟 shell 项目中的 lib.rs 文件时,它构建它时没有 运行() 方法?所以当第二个启动时,它没有看到 运行 方法,因为它是空的?但这对我来说没有多大意义,因为我已经用 运行() 方法复制了 lib.rs 文件。

这里是 toml 文件的样子,如果它有帮助的话:

[package]
name = "rocket_example_pro"
version = "0.1.0"
edition = "2021"

[[bin]]
name = "rocket_example_pro"
path = "src/main.rs"

[lib]
name = "rocket_example_pro"
path = "src/lib.rs"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
...

(一开始我无法重现。然后我注意到至少有一个依赖似乎是一个必要条件。)

同线

RUN rm ./target/release/deps/rocket_example_pro*

您正在强制重建 rocket_example_pro 二进制文件。但是库将保持从第一个空文件构建的状态。尝试更改为

RUN rm ./target/release/deps/librocket_example_pro*

虽然就我个人而言,我认为从 target 目录中删除随机文件是一个非常 hacky 的解决方案。我更愿意通过调整时间戳来触发库的重建:

RUN touch src/lib.rs && cargo build --release --locked ## Doesn't fail anymore

要获得干净的解决方案,请查看 cargo-chef。


[编辑:]那么这里发生了什么?

为了决定是否重建,cargo seems totarget/…/*.d 的 mtime 与 *.d 文件内容中列出的文件的 mtime 进行比较。

大概是src/lib.rs先创建,然后docker build是运行。所以 src/lib.rs 早于 target/release/librocket_example_pro.d,导致 target/release/librocket_example_pro.rlib 在复制到 src/lib.rs 后没有被重建。

您可以部分验证这就是正在发生的事情。

  1. 用原来的Dockerfile,运行cargo build,看到失败
  2. 运行 echo >> src/lib.rs 在 docker 之外更新其 mtime 和 hash
  3. 运行cargo build,成功

请注意,对于第 2 步,使用 touch src/lib.rs 更新 mtime 是不够的,因为 docker 将在 COPY 文件时设置 mtime,但它会 ignore mtime 在决定​​是否使用缓存步骤时。