-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
cargo build --dependencies-only #2644
Comments
I do not remember exactly why, but I do remember that I ended just running rustc manually. |
Sometimes you add a bunch of dependencies to your project, know it will take a while to compile next time you |
As described in #3615 this is useful with |
@gregwebs out of curiosity do you want to cache compiled dependencies or just downloaded dependencies? Caching compiled dependencies isn't implemented today (but would be with a command such as this) but downloading dependencies is available via |
Generally, as with my caching use case, the dependencies change infrequently and it makes sense to cache the compilation of them. The Haskell tool stack went through all this and they seemed to generally decided to merge things into a single command where possible. For |
@gregwebs ok thanks for the info! |
@alexcrichton, |
@KalitaAlexey I personally wouldn't be convinced just yet, but it'd be good to canvas opinions from others on @rust-lang/tools as well |
@alexcrichton, |
I don't see much of a use case - you can just do |
What's the API? |
Implement an |
I wasn't able to find any information about an |
Docs are a little thin, but start here: cargo/src/cargo/ops/cargo_rustc/mod.rs Lines 62 to 64 in 609371f
You can look at the RLS for an example of how to use them: https://github.com/rust-lang-nursery/rls/blob/master/src/build.rs#L288 |
A question of Stack Overflow wanted this feature. In that case, the OP wanted to build the dependencies for a Docker layer. A similar situation exists for the playground, where I compile all the crates once. In my case, I just put in a dummy |
@shepmaster unfortunately the proposed solution wouldn't satisfy that question because a |
I ended up here because I also am thinking about the Docker case. To do a good docker build I want to: COPY Cargo.toml Cargo.lock /mything
RUN cargo build-deps --release # creates a layer that is cached
COPY src /mything/src
RUN cargo build --release # only rebuild this when src files changes This means the dependencies would be cached between docker builds as long as I understand |
The dockerfile template in shepmaster's linked stackoverflow post above SOLVES this problemI came to this thread because I also wanted the docker image to be cached after building the dependencies. After later resolving this issue, I posted something explaining docker caching, and was informed that the answer was already linked in the stackoverflow post. I made this mistake, someone else made this mistake, it's time to clarify.
After building, changing only the source and rebuilding starts from the cached image with dependencies already built. |
someone needs to relax... |
Also @KarlFish what you're proposing is not actually working. If using
|
Here's a better version. FROM rust:1.20.0
WORKDIR /usr/src
# Create blank project
RUN USER=root cargo new umar
# We want dependencies cached, so copy those first.
COPY Cargo.toml Cargo.lock /usr/src/umar/
WORKDIR /usr/src/umar
# This is a dummy build to get the dependencies cached.
RUN cargo build --release
# Now copy in the rest of the sources
COPY src /usr/src/umar/src/
# This is the actual build.
RUN cargo build --release \
&& mv target/release/umar /bin \
&& rm -rf /usr/src/umar
WORKDIR /
EXPOSE 3000
CMD ["/bin/umar"] |
You can always review the complete Dockerfile for the playground. |
Hi! |
I agree that it would be really cool to have a --deps-only option so that we could cache our filesystem layers better in Docker. I haven't tried replicating this yet, but it looks very promising. This is in glibc and not musl, by the way. My main priority is to get to a build that doesn't take 3-5 minutes ever time, not a 5 MB alpine-based image. |
I ran into wanting this today. |
@steveklabnik can you share more details about your case? Why would building only the dependencies be useful? Is starting with an empty lib.rs/main.rs not applicable to your situation? |
613th up-vote (now that's a famous number) Would be great to see this. Will give cargo-chef a shot in the meantime. |
Hey, could we PLEASE get this? I tried
FROM rust:bullseye AS builder
RUN rustup toolchain install nightly
WORKDIR /app
COPY Cargo.toml Cargo.lock rust-toolchain.toml /app/
COPY . /app/
RUN --mount=type=cache,target=/root/.cargo \
--mount=type=cache,target=/root/.rustup \
--mount=type=cache,target=/app/target/release/build \
--mount=type=cache,target=/app/target/release/deps \
--mount=type=cache,target=/app/target/release/.fingerprint \
cargo build --release
FROM debian:bullseye-slim
WORKDIR /app
COPY --from=builder /app/target/release/my-app .
ENTRYPOINT [ "./my-app" ] I would like to be able to just have one line that says "build all my dependencies", and then another line that builds the rest. This would leave docker caching the first one (which hasn't changed), and rebuilding the second. I can then use Would be super cool :) |
@lionkor please see the summary |
[rant] This issue has 650 upvotes, 333 comments, and 119 participants. It is not even on the roadmap under "Big Projects, no backers". This issue is 8.5 years old. The cited summary is almost 1.5 years old. During these years thousands upon thousands of CI hours have been wasted. The environmental footprint of this waste is enormous (both in compute hours and network traffic). CI providers are happy because their pockets get filled, yay! Just one example: Microsoft Sustainability vs. GitHub Actions Pricing Calculator [/rant] [rant] Maybe we can start with a Pareto version:
[/rant] So please, put it on the roadmap and find a corporate backer—saving millions of compute hours would be a nice PR win and very good for the environment. Thanks. |
@epage I've read the document at the top of this issue (and the one you've quoted in your last comment), and I have a question. Currently, if I understand it correctly, one of the disadvantages of the external With this in mind, can you please help me in analysis of the following alternative?:
If this approach is feasible, cargo (or an external tool at first) could get an additional flag that would allow it to "export"/"copy" all its required "build plan dependencies" in a separate directory. This can then be executed at the proposed Stage 1, and later stages 2 and 3 could be more or less standard. |
So far we've mostly been punting this to third-party solutions, like Please keep in mind:
|
@lionkor If you have an issue with glibc in cargo-chef, you should be able to simply use a different base image with a different glibc and then install cargo-chef in your Dockerfile manually. Have you tried that? Btw, for clarity, I would like to reiterate that That doesn't mean that this problem is unsolvable, just that it's way more difficult than just implementing |
Yes, one solution is to make my own image with cargo-chef in it, which matches the rust, cargo and glibc versions I need exactly, and spend yet another few hours making sure that works reliably, ontop of all the time spent already trying different solutions. One workaround is literally:
I understand that this does not fit every possible use-case in the current and all future uses of cargo and rust, for all eternity, yes. This is a very specific problem, with a very clear solution (let me build only the dependencies). The only thing this has to do is build only the dependencies. I don't see how a very simple, clear feature like that has to do with the "complex nature" and "half-measures". Nobody is asking for a kitchen-sink "please do all the docker stuff for me" command, or a "please guess what im thinking right now" solution, we are just asking for a command that does a bog-standard build, and stops right before building the bin or lib of the current top-level project. I don't see how that is a difficult, complex, multi-faceted issue. I read the summary, the title of which ("Better support of Docker layer caching in Cargo") is missing the entire point already. I just want to build the dependencies and not the current project. That's it. The context, reasoning, etc. about docker is irrelevant once you understand that the only feature that is requested here is bailing out of the build early. I'm not expecting anyone build this feature, it's been made clear that this is too complex to build and that I'm also "holding it wrong". My server just gets real toasty every deployment - luckily the DC has A/C. Maybe eventually I'll have the time to contribute, I just think it's very silly that this is just being shot down. |
@lionkor This is a great summary. If Cargo ever wants to implement deeper Docker integration via some special This issue was merely asking for a way to build only the dependencies as specified in Cargo.toml / Cargo.lock, and it feels like discussions of other tools and behaviour is what caused it to stall with "analysis paralysis" due to an ever-expanding context and list of requirements. Cargo already has |
Without any context, it's a request for a feature without a goal. Why do that? There's no discernible point to it. If the context is ignored, all that will happen is that a day after cargo getting a new flag, this same group of issues will be reopened on the issue tracker because it doesn't quite have the outcomes everyone expects; AND another set of issues relating to any errors or oversights in the new feature that now has no apparent purpose. Note that even after saying that context is irrelevant (@lionkor) you jump back to providing context for your frustration (your deployment). I'm not having a go at you for expressing that frustration, but pointing out that without providing context, it would not make much sense to be frustrated by the lack of a feature. (Or maybe I mistook your point, sorry if so.) I, like many others here, would like to have some way to reduce the build-test loop time. Once I gave up in a huff and actually implemented something for myself, I found that there were so many little details that I simply hadn't known about before. Not only that, but those details would be different for different use cases (even small changes to my own). It took that experience, plus quite a few readings of the devs' comments here, to realise that a
It's not being shot down though, it's being considered and interrogated.
I would definitely suggest trying this, it hasn't solved every issue in every CI pipeline I've had, but just ignoring the pre-built image and incorporating cargo chef into my usual "let's write my own image for this then" moments has been a real boon. It's even been great for local testing experiments, which is where I most often quickly roll my own Dockerfile with local bind mounts etc. etc. just to reproduce and weed out bugs before pushing anything for review. |
I think I didn't make entirely clear what I meant: The context is relevant for everyone to understand whether there is a problem, and why this is a solution to that problem. Simply saying "a lot of other tools have a --only-deps or --deps-only or --only-build-deps" would be less useful, because why implement it just because other similar tools have it? Instead, the context of a docker build is relatable and easy to understand, and so that is used to illustrate the problem. It's also useful in case the flag already exists, maybe its just called "--foo-bar" instead. But it doesn't exist in the software, in this case. The engineer's task is to extract a solution from the problem statement, without encoding the problem in the solution (to avoid building something only useful for this one problem). In that vein, we dont use the words from the problem statement, such as "docker", "caching", "CI/CD" and instead we get to the core problem and solve that. Such a solution includes words like "building" and "dependencies", but not "docker". The context is relevant to understanding whether this is a problem, whether there is a built in solution, to evaluate how common/urgent this may be, and to evaluate the impact of building such a feature. So, its relevant generally, but its not relevant to the solution itself.
The suggested solution not only encodes the problem in the solution, but that "solution" given also is only a third party workaround, at best, which might work for people with enough time, but isn't a good solution for such a widely requested (and therefore arguably important) feature. Bringing it back to the solution here, now: If we could agree that there are use cases for building only the dependencies, just like there are use cases for downloading the dependencies (cargo fetch), etc., then we could realistically post a single comment saying "Here are the acceptance criteria, does anyone wanna build this?", and that would constitute "not shooting it down". (Or, whatever equivalent of that process in this repo, I'm not intimately familiar with the process) |
💯agree I go into some of the why below, but I've encountered my own share of:
For Docker, you can get that sort of output via There are some concerns with cache mounts, but it really depends on context. Notably with a typical CI workflow:
Neither of those concerns would be something you'd experience locally with a single git checkout and iterative builds, so they're not immediately apparent.
Personally I'm not a fan of cargo-chef. You can leverage cache mounts correctly for a shared cache, and project specific build cache. If the problem only occurs in CI for you, you can also do the build outside of Docker, or do the build via a container before adding to a You may also be interested in trying Earthly (Uses BuildKit, you still get an image that Docker can use):
Given the above, is it really a need for building dependencies separately, or is it an XY problem? (your problem is due to another underlying issue that'd still affect you) Downloading dependencies I can understand. But if the pre-built deps was beneficial, you could demonstrate that in a CI workflow that would simulate the above concerns with Docker and Cargo cache behaviours aren't getting in the way? If you've only verified such from external tools like cargo-chef (which IIRC workaround the issue), that doesn't equate to such a feature making that tooling obsolete. I am late to this discussion, so perhaps that's been covered somewhere above already, but if no one has verified such and there is no reason why it shouldn't be possible to verify, then that would be a good first step. Otherwise you're trying to resolve the wrong concern. |
It definitely does seem like so, but it's unclear whether it really is that simple. I think that it's a general theme of this issue; people are frustrated because it seems like cargo refuses to implement a trivial thing, but the reality is that the thing is not as trivial as it might seem. One thing that we could explore is the possibility of doing a |
That's exactly the original ask (at least for me) and would be perfect. Assuming that "ignore local Rust files" also means "ignore absence of local Rust files", so that we can really copy Cargo.toml + Cargo.lock + other relevant configs but not bother creating dummy sources.
I'd suggest to mirror behaviour of cargo-fetch as closely as possible to, again, make this command and feature request well-scoped. If it's not downloaded by cargo-fetch, it doesn't need to be built, and vice versa. |
Hi! Thanks for the amazing summary document! I'm working on a thing that tries to maximize Docker build cache usage and I believe this approach may be of interest here. It's a # syntax=docker.io/docker/dockerfile:1@sha256:fe40cf4e92cd0c467be2cfc30657a680ae2398318afd50b0c80585784c604f28
# Generated by https://github.com/fenollp/supergreen version 0.8.0
FROM --platform=$BUILDPLATFORM docker.io/library/rust:1.80.1-slim@sha256:e8e40c50bfb54c0a76218f480cc69783b908430de87b59619c1dca847fdbd753 AS rust-base
FROM scratch AS cratesio-anstyle-1.0.1-index.crates.io-6f17d22bba15001f
ADD --chmod=0664 --checksum=sha256:3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd \
https://static.crates.io/crates/anstyle/anstyle-1.0.1.crate /crate
SHELL ["/usr/bin/dash", "-c"]
RUN \
--mount=from=rust-base,src=/lib,dst=/lib \
--mount=from=rust-base,src=/lib64,dst=/lib64 \
--mount=from=rust-base,src=/usr,dst=/usr \
set -eux \
&& mkdir /extracted \
&& tar zxf /crate --strip-components=1 -C /extracted
FROM rust-base AS dep-lib-anstyle-1.0.1-dfef56a61f24fbad-index.crates.io-6f17d22bba15001f
WORKDIR /tmp/clis-buildxargs_master/release/deps
ENV \
CARGO_CRATE_NAME="anstyle" \
CARGO_MANIFEST_DIR="/home/pete/.cargo/registry/src/index.crates.io-6f17d22bba15001f/anstyle-1.0.1" \
CARGO_PKG_AUTHORS= \
CARGO_PKG_DESCRIPTION="ANSI text styling" \
CARGO_PKG_HOMEPAGE="https://github.com/rust-cli/anstyle" \
CARGO_PKG_LICENSE="MIT OR Apache-2.0" \
CARGO_PKG_LICENSE_FILE= \
CARGO_PKG_NAME="anstyle" \
CARGO_PKG_README="README.md" \
CARGO_PKG_REPOSITORY="https://github.com/rust-cli/anstyle.git" \
CARGO_PKG_RUST_VERSION="1.64.0" \
CARGO_PKG_VERSION="1.0.1" \
CARGO_PKG_VERSION_MAJOR="1" \
CARGO_PKG_VERSION_MINOR="0" \
CARGO_PKG_VERSION_PATCH="1" \
CARGO_PKG_VERSION_PRE= \
TERM="tmux-256color" \
RUSTCBUILDX=1
RUN \
--mount=type=bind,from=cratesio-anstyle-1.0.1-index.crates.io-6f17d22bba15001f,source=/extracted,target=/home/pete/.cargo/registry/src/index.crates.io-6f17d22bba15001f/anstyle-1.0.1 \
set -eux \
&& export CARGO="$(which cargo)" \
&& /bin/bash -c "rustc '--crate-name' 'anstyle' '--edition' '2021' '--error-format' 'json' '--json' 'diagnostic-rendered-ansi,artifacts,future-incompat' '--diagnostic-width' '254' '--crate-type' 'lib' '--emit' 'dep-info,metadata,link' '-C' 'opt-level=3' '-C' 'embed-bitcode=no' '--cfg' 'feature=\"default\"' '--cfg' 'feature=\"std\"' '--check-cfg' 'cfg(docsrs)' '--check-cfg' 'cfg(feature, values(\"default\", \"std\"))' '-C' 'metadata=dfef56a61f24fbad' '-C' 'extra-filename=-dfef56a61f24fbad' '--out-dir' '/tmp/clis-buildxargs_master/release/deps' '-C' 'strip=debuginfo' '-L' 'dependency=/tmp/clis-buildxargs_master/release/deps' '--cap-lints' 'warn' '-A' 'clippy::assigning_clones' '-A' 'clippy::blocks_in_conditions' '-W' 'clippy::cast_lossless' '-W' 'clippy::redundant_closure_for_method_calls' '-W' 'clippy::str_to_string' '-C' 'overflow-checks=true' /home/pete/.cargo/registry/src/index.crates.io-6f17d22bba15001f/anstyle-1.0.1/src/lib.rs \
1> >(sed 's/^/::STDOUT:: /') \
2> >(sed 's/^/::STDERR:: /' >&2)"
FROM scratch AS out-dfef56a61f24fbad
COPY --from=dep-lib-anstyle-1.0.1-dfef56a61f24fbad-index.crates.io-6f17d22bba15001f /tmp/clis-buildxargs_master/release/deps/*-dfef56a61f24fbad* /
FROM scratch AS cratesio-utf8parse-0.2.1-index.crates.io-6f17d22bba15001f
ADD --chmod=0664 --checksum=sha256:711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a \
https://static.crates.io/crates/utf8parse/utf8parse-0.2.1.crate /crate
SHELL ["/usr/bin/dash", "-c"]
RUN \
--mount=from=rust-base,src=/lib,dst=/lib \
--mount=from=rust-base,src=/lib64,dst=/lib64 \
--mount=from=rust-base,src=/usr,dst=/usr \
set -eux \
&& mkdir /extracted \
&& tar zxf /crate --strip-components=1 -C /extracted
FROM rust-base AS dep-lib-utf8parse-0.2.1-1f8b17e9e43ce6f1-index.crates.io-6f17d22bba15001f
WORKDIR /tmp/clis-buildxargs_master/release/deps
ENV \
CARGO_CRATE_NAME="utf8parse" \
CARGO_MANIFEST_DIR="/home/pete/.cargo/registry/src/index.crates.io-6f17d22bba15001f/utf8parse-0.2.1" \
CARGO_PKG_AUTHORS="Joe Wilm <joe@jwilm.com>:Christian Duerr <contact@christianduerr.com>" \
CARGO_PKG_DESCRIPTION="Table-driven UTF-8 parser" \
CARGO_PKG_HOMEPAGE= \
CARGO_PKG_LICENSE="Apache-2.0 OR MIT" \
CARGO_PKG_LICENSE_FILE= \
CARGO_PKG_NAME="utf8parse" \
CARGO_PKG_README= \
CARGO_PKG_REPOSITORY="https://github.com/alacritty/vte" \
CARGO_PKG_RUST_VERSION= \
CARGO_PKG_VERSION="0.2.1" \
CARGO_PKG_VERSION_MAJOR="0" \
CARGO_PKG_VERSION_MINOR="2" \
CARGO_PKG_VERSION_PATCH="1" \
CARGO_PKG_VERSION_PRE= \
TERM="tmux-256color" \
RUSTCBUILDX=1
RUN \
--mount=type=bind,from=cratesio-utf8parse-0.2.1-index.crates.io-6f17d22bba15001f,source=/extracted,target=/home/pete/.cargo/registry/src/index.crates.io-6f17d22bba15001f/utf8parse-0.2.1 \
set -eux \
&& export CARGO="$(which cargo)" \
&& /bin/bash -c "rustc '--crate-name' 'utf8parse' '--edition' '2018' '--error-format' 'json' '--json' 'diagnostic-rendered-ansi,artifacts,future-incompat' '--diagnostic-width' '254' '--crate-type' 'lib' '--emit' 'dep-info,metadata,link' '-C' 'opt-level=3' '-C' 'embed-bitcode=no' '--cfg' 'feature=\"default\"' '--check-cfg' 'cfg(docsrs)' '--check-cfg' 'cfg(feature, values(\"default\", \"nightly\"))' '-C' 'metadata=1f8b17e9e43ce6f1' '-C' 'extra-filename=-1f8b17e9e43ce6f1' '--out-dir' '/tmp/clis-buildxargs_master/release/deps' '-C' 'strip=debuginfo' '-L' 'dependency=/tmp/clis-buildxargs_master/release/deps' '--cap-lints' 'warn' '-A' 'clippy::assigning_clones' '-A' 'clippy::blocks_in_conditions' '-W' 'clippy::cast_lossless' '-W' 'clippy::redundant_closure_for_method_calls' '-W' 'clippy::str_to_string' '-C' 'overflow-checks=true' /home/pete/.cargo/registry/src/index.crates.io-6f17d22bba15001f/utf8parse-0.2.1/src/lib.rs \
1> >(sed 's/^/::STDOUT:: /') \
2> >(sed 's/^/::STDERR:: /' >&2)"
FROM scratch AS out-1f8b17e9e43ce6f1
COPY --from=dep-lib-utf8parse-0.2.1-1f8b17e9e43ce6f1-index.crates.io-6f17d22bba15001f /tmp/clis-buildxargs_master/release/deps/*-1f8b17e9e43ce6f1* /
FROM scratch AS cratesio-anstyle-parse-0.2.1-index.crates.io-6f17d22bba15001f
ADD --chmod=0664 --checksum=sha256:938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333 \
https://static.crates.io/crates/anstyle-parse/anstyle-parse-0.2.1.crate /crate
SHELL ["/usr/bin/dash", "-c"]
RUN \
--mount=from=rust-base,src=/lib,dst=/lib \
--mount=from=rust-base,src=/lib64,dst=/lib64 \
--mount=from=rust-base,src=/usr,dst=/usr \
set -eux \
&& mkdir /extracted \
&& tar zxf /crate --strip-components=1 -C /extracted
FROM rust-base AS dep-lib-anstyle-parse-0.2.1-32683c410c273145-index.crates.io-6f17d22bba15001f
WORKDIR /tmp/clis-buildxargs_master/release/deps
ENV \
CARGO_CRATE_NAME="anstyle_parse" \
CARGO_MANIFEST_DIR="/home/pete/.cargo/registry/src/index.crates.io-6f17d22bba15001f/anstyle-parse-0.2.1" \
CARGO_PKG_AUTHORS= \
CARGO_PKG_DESCRIPTION="Parse ANSI Style Escapes" \
CARGO_PKG_HOMEPAGE="https://github.com/rust-cli/anstyle" \
CARGO_PKG_LICENSE="MIT OR Apache-2.0" \
CARGO_PKG_LICENSE_FILE= \
CARGO_PKG_NAME="anstyle-parse" \
CARGO_PKG_README="README.md" \
CARGO_PKG_REPOSITORY="https://github.com/rust-cli/anstyle.git" \
CARGO_PKG_RUST_VERSION="1.64.0" \
CARGO_PKG_VERSION="0.2.1" \
CARGO_PKG_VERSION_MAJOR="0" \
CARGO_PKG_VERSION_MINOR="2" \
CARGO_PKG_VERSION_PATCH="1" \
CARGO_PKG_VERSION_PRE= \
TERM="tmux-256color" \
RUSTCBUILDX=1
RUN \
--mount=type=bind,from=cratesio-anstyle-parse-0.2.1-index.crates.io-6f17d22bba15001f,source=/extracted,target=/home/pete/.cargo/registry/src/index.crates.io-6f17d22bba15001f/anstyle-parse-0.2.1 \
--mount=type=bind,from=out-1f8b17e9e43ce6f1,target=/tmp/clis-buildxargs_master/release/deps/libutf8parse-1f8b17e9e43ce6f1.rmeta,source=/libutf8parse-1f8b17e9e43ce6f1.rmeta \
set -eux \
&& export CARGO="$(which cargo)" \
&& /bin/bash -c "rustc '--crate-name' 'anstyle_parse' '--edition' '2021' '--error-format' 'json' '--json' 'diagnostic-rendered-ansi,artifacts,future-incompat' '--diagnostic-width' '254' '--crate-type' 'lib' '--emit' 'dep-info,metadata,link' '-C' 'opt-level=3' '-C' 'embed-bitcode=no' '--cfg' 'feature=\"default\"' '--cfg' 'feature=\"utf8\"' '--check-cfg' 'cfg(docsrs)' '--check-cfg' 'cfg(feature, values(\"core\", \"default\", \"utf8\"))' '-C' 'metadata=32683c410c273145' '-C' 'extra-filename=-32683c410c273145' '--out-dir' '/tmp/clis-buildxargs_master/release/deps' '-C' 'strip=debuginfo' '-L' 'dependency=/tmp/clis-buildxargs_master/release/deps' '--extern' 'utf8parse=/tmp/clis-buildxargs_master/release/deps/libutf8parse-1f8b17e9e43ce6f1.rmeta' '--cap-lints' 'warn' '-A' 'clippy::assigning_clones' '-A' 'clippy::blocks_in_conditions' '-W' 'clippy::cast_lossless' '-W' 'clippy::redundant_closure_for_method_calls' '-W' 'clippy::str_to_string' '-C' 'overflow-checks=true' /home/pete/.cargo/registry/src/index.crates.io-6f17d22bba15001f/anstyle-parse-0.2.1/src/lib.rs \
1> >(sed 's/^/::STDOUT:: /') \
2> >(sed 's/^/::STDERR:: /' >&2)"
FROM scratch AS out-32683c410c273145
COPY --from=dep-lib-anstyle-parse-0.2.1-32683c410c273145-index.crates.io-6f17d22bba15001f /tmp/clis-buildxargs_master/release/deps/*-32683c410c273145* / So each new My point is that a full build with this tool gives you a final (huge) Dockerfile with all deps as stages. So maybe one could just strip out the last stages (the ones about local code) and use this as the base stages for a |
Would be nice to have this implemented, a la |
@zkkv @Brayan-724 : while I appreciate your enthusiasm, your comments are akin to a "me too". Please try to keep the noise down and instead use the emoji to express interest. Thanks! |
Let me share our experiences and insights, leveraging from this comment #2644 (comment). We've been using We also experimented with This led us to explore cache mounts, despite being aware of the mtime issue. We anticipated rebuilds when our CI copied files into the Dockerfile, but just re-running the build skips that step, reducing the build time to 3-5 seconds. As @polarathene mentioned, we started with After implementing these changes, we've seen a ~36% improvement in build times. We no longer face the rebuild issues associated with
Although our Dockerfile from the aforementioned PR should provide a good starting point for more complex projects, I'll also create a post with two simpler examples for those who want to copy/paste and modify a working example. |
Hi, I have been dealing with this lately and saw that the summary doc asked for feedback on the workarounds, so here you go: Cargo chefI haven’t actually tried this, because I looked at the issues and saw that certain Cargo features we are using were not supported (patches). From looking at the code it takes on a lot of responsibility from Cargo and seems likely to be fragile. Docker Cache mountsThe problem with docker cache mounts is they cannot be exported or imported. If you are not using the same machine for each build they are effectively useless. Normally on GitHub Actions, AWS CodeBuild, etc I will export a cache and import it in subsequent builds. Even with mode set to It might be possible to find the docker cache mount on the host and save that to a cache, but it’s not really a supported Docker API and it would require setting up additional storage. If the other workarounds don’t work I will probably setup a self hosted runner with a persistent cache. Copy skeleton / two step buildI tried this. It’s a workspace with too many crates to copy by hand, so I wrote a tool to generate the skeleton from cargo metadata as a tar file. I am currently struggling with mtime issues, but not the ones I expected. When I generate the tar I set the mtime of the stub files to 1970, so they are older than the actual files. Later I copy in the actual files with their original timestamps. I thought that since the mtime of the source file was newer than the mtime when it was last compiled, Cargo would rebuild it. But Cargo doesn’t check that. Instead it checks the dep-info file, which was built from the stub. And since the actual source preserved its timestamps when docker copied it in, it has not been updated since the last build according to the mtime. I am going to try to work around this by adding a dependency to the stub that cannot ever appear in the real unit to force Cargo to invalidate the build. It’s worth noting that if there was a The stub approach is nice in that I can invoke the build normally and everything supported by Cargo just works. But it is unfortunate that any issue with mtime comparison results in you “shipping hello world to production”, as someone else said. |
We have recently discussed this a bit more on Zulip. Here is a summary of that discussion so far: To reiterate, the most basic form of better supporting Rust (re)builds in the context of Docker layers (as it currently stands, without doing some alternatives like allowing build with only the
I would argue that 1) is mostly a limitation of Docker, because it simply does not support The 2) problem is more annoying, because the entrypoints are a bit more tricky to enumerate/detect and primarily because you need to get rid of their contents, and then you start running into mtime issues. Here Cargo could indeed help in theory, if it simply tried to compile the project without caring about library/binary/... targets at all (and thus it would ignore the fact that their entrypoints are missing on disk). We have discussed this and it is probably possible, but it would (probably) require a non-trivial refactoring of Cargo to make this work, because the target discovery is now performed very soon in the Cargo buld process. I suppose that if someone wanted to push this forward, having an implementation proof-of-concept of this approach could help the discussion going forward. Just a remark, building out of |
I am able to do 1 & 2 in user land pretty easily be using What would really help me is what the issue originally asked for: a The ability to only compile deps avoids the mtime issue completely and eliminates the risk of shipping a broken binary. I am working around the lack of that flag by manually building the list of deps from cargo metadata, then running Since it is possible to do that it seems like it wouldn’t be impossible to implement the aforementioned flag in Cargo. WDYT? |
As I described above, possibly the simplest implementation of Note that if you're already manually copying all TOML and also |
@matt-allan in case you haven't tried it, the workaround I use for the mtime problem (in projects where I'm not employing COPY Cargo.toml Cargo.lock /repo/
WORKDIR /repo
RUN \
mkdir -v src && \
echo 'fn main() {}' > src/main.rs && \
cargo build --release && \
rm -Rvf src
COPY src /repo/src
RUN \
touch src/main.rs && \
cargo build --release It's more boilerplate in a workspace, but if you're already generating a tarball for the skeleton files, I'd imagine generating this wouldn't be too much additional pain and it'd be a solution to your problem using tools available today |
Ok I think I misunderstood you. It sounded like ignoring the entrypoint files was not simple (“ require a non-trivial refactoring of Cargo to make this work”). What I was proposing is a mechanism similar to |
This is common to all build systems: You need to So this not an issue with regards to Cargo. One related question would be if https://doc.rust-lang.org/cargo/commands/cargo-build.html are clear enough: What files (environment variables, flags, stuff gleaned from the operating system, etc.) are relevant for
$ mkdir /tmp/test && cd "$_"
$ cat << 'EOF' >Cargo.toml
[workspace]
resolver = "2"
EOF
$ cargo new -q one
$ cargo new -q two
$ cargo new -q dir/three
$ cargo new -q dir/sub/four
$ cargo -q build
$ cargo -q clean
$ tree --noreport /tmp/test
/tmp/test
├── Cargo.lock
├── Cargo.toml
├── dir
│ ├── sub
│ │ └── four
│ │ ├── Cargo.toml
│ │ └── src
│ │ └── main.rs
│ └── three
│ ├── Cargo.toml
│ └── src
│ └── main.rs
├── one
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── two
├── Cargo.toml
└── src
└── main.rs
$ cat << 'EOF' >Dockerfile
# syntax=docker/dockerfile:1.7-labs
FROM busybox
WORKDIR /tmp/test
COPY --parents **/Cargo.toml ./
COPY Cargo.lock ./
COPY --parents **/src ./
EOF
$ docker build -q -t test .
$ docker run --rm test tree /tmp/test
/tmp/test
├── Cargo.lock
├── Cargo.toml
├── dir
│ ├── sub
│ │ └── four
│ │ ├── Cargo.toml
│ │ └── src
│ │ └── main.rs
│ └── three
│ ├── Cargo.toml
│ └── src
│ └── main.rs
├── one
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── two
├── Cargo.toml
└── src
└── main.rs Being explicit might be better from a security/reproducibility standpoint though … So this would be the want (in relation to Docker) articulated in this GitHub issue: $ cat << 'EOF' >Dockerfile
# syntax=docker/dockerfile:1.7-labs
FROM rust
WORKDIR /tmp/test
COPY --parents Cargo.lock **/Cargo.toml ./
RUN cargo build --release --deps-only
COPY --parents **/src ./
RUN cargo build --release
EOF |
Sure, but in Python, JavaScript, Ruby, etc., you usually have a single lockfile and a single package file. In Rust, you have a lockfile and a bunch of TOML files that can be almost arbitrarily nested. So it is a bit more annoying for Rust than for other similar tools.
Cool, didn't know about that one! Thanks for bringing it up. Hopefully it will be stabilized soon, it would make it a bit easier for Rust projects to copy their manifests.
Indeed, a cargo feature that would ignore Rust source entrypoints on disk could most likely work like this. |
@matt-allan , while perhaps not a complete solution to the ticket here or the buildkit one you linked to above, there are several GitHub actions for caching buildkit's internal cache mounts. E.g an actively maintained varent I've used prior: I'd still like to see a solution to moby/buildkit#1512 eventually as well, but perhaps out of scope for the discussion here. |
There is a lot to read in this thread and I guess someone must have mentioned this previously but I haven't found it. For me, the reason to want the originally requested change is so that I can build dependencies non-verbose, then build my own crates verbose. That saves a lot of unnecessary log output in CI builds. Currently there is no way to apply Cleaning and rebuilding my crates easily takes 5-10 minutes of CI build time, so that is not an attractive option for me. |
cargo team notes:
This is primarily targeted at docker layer caching for dependencies. For limiting docker layer caching of workspace members, see #14566.
There should be an option to only build dependencies.
The text was updated successfully, but these errors were encountered: