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

3
我一直试图创建一个 Dockerfile,使得我可以像这里演示的那样单独构建应用程序而不涉及依赖项: Cache Rust dependencies with Docker build 然而,对于我来说似乎并没有起作用,因为我的工作树与 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 中的 main 方法。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可执行文件仍然似乎只是打印“Hello world”的shell示例项目,而不是我在第二次构建之前复制的rocket应用程序。
据我所知,第一次构建影响了第二次构建,也许是因为当我触摸虚拟shell项目中的lib.rs文件时,它会构建它而没有run()方法?因此,当第二个启动时,它看不到run方法,因为它是空的?但这对我来说没有多大意义,因为我已经复制了包含run()方法的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]
...

1个回答

4
我是一名有用的助手,可以为您翻译文本。
我一开始无法重现这个问题。然后我注意到至少需要一个依赖项似乎是必要条件。
使用以下代码行:
RUN rm ./target/release/deps/rocket_example_pro*

你正在强制重新构建rocket_example_pro二进制文件。但是库将保留从第一个空文件构建的状态。尝试更改为:
RUN rm ./target/release/deps/librocket_example_pro*

虽然我个人认为从target目录中随机删除文件是一个非常粗糙的解决方案。我更喜欢通过调整时间戳来触发库的重建:

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

如果想要一个简洁的解决方案,请看一下cargo-chef。


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

为了决定是否重新构建,Cargo 似乎 比较 target/…/*.d 的修改时间和 *.d 文件中列出的文件的修改时间。

很可能,src/lib.rs 是先创建的,然后运行了 docker build。因此,src/lib.rstarget/release/librocket_example_pro.d 旧,导致在复制 src/lib.rstarget/release/librocket_example_pro.rlib 没有被重新构建。

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

  1. 使用原始的 Dockerfile 运行 cargo build,看到它失败
  2. 在 docker 外部运行 echo >> src/lib.rs 更新其修改时间和哈希值
  3. 运行 cargo build,它成功了
注意,对于第二步,仅使用 touch src/lib.rs 更新 mtime 是不够的,因为 docker 在复制文件时会设置 mtime,但在决定是否使用缓存步骤时会 忽略 mtime

这个方法很管用,谢谢:D。 有没有地方可以让我深入了解这个行为? 说实话,我有点困惑,因为我本来以为复制实际的src文件会强制重建这些文件,因为文件已经发生了更改?例如,您建议通过触及src/lib.rs而不是删除生成的目标文件来执行此操作。 - C. Dodds
1
编辑过。这里可能有一些小错误。 - Caesar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接