有没有办法告诉Cargo安装并构建我的所有依赖项,但不尝试构建我的应用程序?
我原以为cargo install
会这么做,但实际上它也一直在构建我的应用程序。我想达到这样一种状态,即cargo build
可以找到所有可以使用的依赖项,但不需要接触/src
目录。
我真正想完成的事情:
我正在尝试为Rust应用程序构建一个Docker映像,我想在其中执行以下步骤:
构建时间(docker build .
):
- 导入安装了防锈工具的docker图像
- 添加我的Cargo.toml和Cargo.lock文件
- 下载并构建所有依赖项
- 将我的源目录添加到映像中
- 构建我的源代码
运行时间(docker run ...
):
- 运行应用程序
我已经尝试了以下Dockerfile
,但指示的步骤也构建了我的应用程序(当然失败了,因为源目录还没有):
FROM jimmycuadra/rust
ADD Cargo.toml /source
ADD Cargo.lock /source
RUN cargo install # <-- failure here
ADD src /source/src
RUN cargo build
ENTRYPOINT cargo run
我想将安装依赖关系步骤与实际构建应用程序分开的原因是,如果我不更改依赖关系,我希望Docker能够使用已安装和构建的所有依赖关系的缓存映像。因此,在安装dependencies之后的之前,我不能ADD /src /source/src
,因为当我更改自己的代码时,这会使缓存的映像无效。
据我所知,Cargo中没有本地支持只构建依赖项。这是一个悬而未决的问题。如果你能向Cargo提交一些东西来完成它,或者创建一个第三方Cargo插件,我不会感到惊讶。当我自己的代码太坏而无法编译时,我也希望cargo doc
具有此功能;-)
然而,我维护的Rust游乐场确实实现了你的最终目标。有一个基本的Docker容器,可以安装Rustup并在Cargo.toml
中复制,所有的板条箱都可以用于游乐场。构建步骤创建一个空白项目(带有一个伪src/lib.rs
),然后调用cargo build
和cargo build --release
来编译板条箱:
RUN cd / &&
cargo new playground
WORKDIR /playground
ADD Cargo.toml /playground/Cargo.toml
RUN cargo build
RUN cargo build --release
RUN rm src/*.rs
所有下载的板条箱存储在Docker镜像的$HOME/.cargo
目录中,所有构建的板条箱都存储在应用程序target/{debug,release}
目录中。
稍后,真实的源文件被复制到容器中,并且cargo build
/cargo run
可以使用现在编译的crates再次执行。
如果您正在构建一个可执行项目,那么您也需要在Cargo.lock中进行复制。
如果添加一个伪main或lib文件,则可以使用cargo build
来下拉依赖项。我目前正在为我的基于Docker的项目使用这个解决方案:
COPY Cargo.toml .
RUN mkdir src
&& echo "// dummy file" > src/lib.rs
&& cargo build
我正在使用--volumes
,所以在这一点上我已经完成了。主机卷进入并吹走伪文件,当我稍后构建源文件时,货物使用缓存的依赖项。如果您想稍后添加COPY
(或ADD
)并使用缓存的依赖项,则此解决方案也同样有效。
基于GitHub注释
FROM rust:1.37
WORKDIR /usr/src
# Create blank project
RUN USER=root cargo new PROJ
# We want dependencies cached, so copy those first.
COPY Cargo.toml /usr/src/PROJ/
COPY Cargo.lock /usr/src/PROJ/
WORKDIR /usr/src/PROJ
# This is a dummy build to get the dependencies cached.
RUN cargo build --release
# Now copy in the rest of the sources
COPY MyPROJECT/src /usr/src/PROJ/src/
# This is the actual build.
RUN cargo build --release
&& mv target/release/appname /bin
&& rm -rf /usr/src/PROJ
WORKDIR /
EXPOSE 8888
CMD ["/bin/appname"]
货物厨师工具就是为了解决这个问题而设计的。以下是README中关于如何在Dockerfile:中使用它的示例
FROM lukemathwalker/cargo-chef as planner
WORKDIR app
COPY . .
RUN cargo chef prepare --recipe-path recipe.json
FROM lukemathwalker/cargo-chef as cacher
WORKDIR app
COPY --from=planner /app/recipe.json recipe.json
RUN cargo chef cook --release --recipe-path recipe.json
FROM rust as builder
WORKDIR app
COPY . .
# Copy over the cached dependencies
COPY --from=cacher /app/target target
COPY --from=cacher $CARGO_HOME $CARGO_HOME
RUN cargo build --release --bin app
FROM rust as runtime
WORKDIR app
COPY --from=builder /app/target/release/app /usr/local/bin
ENTRYPOINT ["/usr/local/bin/app"]
我只是想在这里发布这篇文章,这样其他人就会看到它的发展。Docker有一个我刚开始使用的实验工具,叫做货运码头(https://github.com/denzp/cargo-wharf/tree/master/cargo-wharf-frontend)。它是一个Docker BuildKit前端,为您缓存已构建的货物依赖关系。如果您只更改了一个源文件,那么在调用docker build
时,这是唯一会重新生成的文件。您可以通过注释您的Cargo.toml文件来使用它,然后将Docker指向您的Cargo.toml,而不是Dockerfile。去看看,这正是我想要的。(我与该项目没有任何关系。)
可以通过cargo init
、cargo build
和cargo install
完成。例如,对于名为foo
的项目,定义以下Dockerfile
:
FROM rust:slim-bullseye
# Build dependencies only.
RUN cargo init foo
COPY Cargo.toml foo/
RUN cargo build --release;
rm -rf foo
# Install `foo`.
COPY . .
RUN echo "// force Cargo cache invalidation" >> foo/src/main.rs;
cargo install --path foo
CMD ["foo"]
这里,cargo init
创建Cargo期望的占位符文件,cargo build
构建在Cargo.toml
中指定的依赖项,cargo install
创建foo
二进制文件。出于某种原因,Docker一直在构建cargo init foo
创建的默认项目。上面通过附加// force Cargo cache invalidation
来强制更新main.rs
来解决这个问题。
为了避免由于大型构建上下文和大型层而导致的缓慢构建,请确保通过.dockerignore
忽略target
等不重要的文件夹。例如,定义以下.dockerignore
:
**/*.lock
LICENSE
README.md
target