Skip to content
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

rustPlatform: Allow impure cache. #98813

Closed
wants to merge 1 commit into from
Closed

Conversation

kevincox
Copy link
Contributor

I hope this isn't too controversial. Right now if you make any change to your rust source (including any non-source files) a subsequent Nix build will recompile all of your dependencies and do a full compile of your crate. This change allows using a directory of your choice to cache cargo's artifacts. This allows incremental rebuilds.

The intent of this is that it can be used in CI, and possibly local development to reduce full rebuilds. This is especially useful if you depend on a large number of crates.

This is not intended to be used for production releases and other cases where complete correctness is required.

Concerns:

  • I think executables that have been removed from the source will be grabbed by the installPhase so might not be removed from the package output.
Motivation for this change

Reduce development and CI time while being able to use the same Nix build process for CI and deployment.

Things done
  • Tested using sandboxing (nix.useSandbox on NixOS, or option sandbox in nix.conf on non-NixOS linux)
  • Built on platform(s)
    • NixOS
    • macOS
    • other Linux distributions
  • Tested via one or more NixOS test(s) if existing and applicable for the change (look inside nixos/tests)
  • Tested compilation of all pkgs that depend on this change using nix-shell -p nixpkgs-review --run "nixpkgs-review wip"
  • Tested execution of all binary files (usually in ./result/bin/)
  • Determined the impact on package closure size (by running nix path-info -S before and after)
  • Ensured that relevant documentation is up to date
  • Fits CONTRIBUTING.md.

I hope this isn't too controversial. Right now if you make any change to your rust source (including any non-source files) a subsequent Nix build will recompile all of your dependencies and do a full compile of your crate. This change allows using a directory of your choice to cache cargo's artifacts. This allows incremental rebuilds.

The intent of this is that it can be used in CI, and possibly local development to reduce full rebuilds. This is especially useful if you depend on a large number of crates.

This is not intended to be used for production releases and other cases where complete correctness is required.

Concerns:

- I think executables that have been removed from the source will be grabbed by the installPhase so might not be removed from the package output.
@zowoq
Copy link
Contributor

zowoq commented Sep 26, 2020

Why do this rather than using buildRustCrate or one of the other rust/nix builders?

@kevincox
Copy link
Contributor Author

I didn't know about https://github.com/edolstra/import-cargo and all the rest require generating files instead just just using the regular cargo setup for development. I guess I'll have to check out the status of flakes to see if that one will work well.

However IIUC even in that case it won't cache the incremental build of the crate itself. However as long as I don't need to rebuild dependencies it will be fast enough for my CI.

@kevincox
Copy link
Contributor Author

I also tested edolstra's and it doesn't appear to avoid recompiling dependencies (or I set it up wrong).

@zowoq
Copy link
Contributor

zowoq commented Sep 26, 2020

... all the rest require generating files instead just just using the regular cargo setup for development.

Sorry but I still don't understand why you would want to use buildRustPackage if you want a "regular cargo setup"?

@danieldk
Copy link
Contributor

danieldk commented Sep 26, 2020

  • I think executables that have been removed from the source will be grabbed by the installPhase so might not be removed from the package output.

cargo clean -p SPEC could be used to (only) clean the main package, which would avoid such left-overs.

I hope this isn't too controversial. Right now if you make any change to your rust source (including any non-source files)

I don't know. This feels like an invitation for people to disable sandboxing, which will lead to more breakage of Nix' model [1] and potentially more incorrect PR submissions. As @zowoq says, there are proper solutions to this. E.g. buildRustCrate+crate2nix build every transitive (crate) dependency as a Nix derivation, giving you fully incremental builds. As a bonus, build outputs are shared between projects. We have been using crate2nix with a binary cache in CI over year and it works great.

I would prefer to see crate2nix added to nixpkgs (ping @kolloch, any objections?) and steering people who need incremental builds in that direction.

[1] Which buildRustPackage already is, since it doesn't use proper fixed-output derivations.

@andir
Copy link
Member

andir commented Sep 26, 2020 via email

@danieldk
Copy link
Contributor

danieldk commented Sep 26, 2020

The author should look at naersk https://github.com/nmattia/naersk as that does a two stage compilation. One for the dependencies and another for your crate.

naerks is nice too, but the caveat is that all the dependencies are recompiled when Cargo.lock is modified. My experience is that even with modest projects using the async ecosystem (lots of dependencies), this generally leads to ~8 minute rebuilds on GitHub actions. For an early-stage project with quickly-changing dependencies I burned through my GH Actions minutes in a few days ;).

So I guess it depends a bit on your CI capacity and how often you expect to make changes that affect Cargo.lock.

(Yes, I should get an enterprise plan or set up my own GH Actions runner ;).)

@andir
Copy link
Member

andir commented Sep 26, 2020 via email

@danieldk
Copy link
Contributor

I think I'll finally work on a comparison blog post for all those approaches that I used in the past. There are always a few trade offs to make.

That would be really nice!

@danieldk
Copy link
Contributor

danieldk commented Sep 26, 2020

I think I'll finally work on a comparison blog post for all those approaches that I used in the past. There are always a few trade offs to make.

That would be really nice!

The nixpkgs manual is also quite outdated when it comes to comparing approaches. E.g., it still suggest Carnix for buildRustCrate. As far as I can see, Carnix is not actively maintained anymore.

@kevincox
Copy link
Contributor Author

and potentially more incorrect PR submissions

Just to be clear I don't think this option should ever be used in nixpkgs.

However there are cases where perfect purity are not worth the cost. This is why every CI system I am aware of except hydra has support for caching. Sure, it would be more correct if you built from source, but the time and resource cost isn't worth it.

@kevincox
Copy link
Contributor Author

Sorry but I still don't understand why you would want to use buildRustPackage if you want a "regular cargo setup?

  • It makes the project approachable for people who don't use nix.
  • I can use tools like cargo-edit with no extra steps.
  • I can still use nix to declare and fetch my dependencies (very helpful for CI and also helps people who want to use nix locally)

My primary use case here is speeding up my CI. I have found nix very helpful in CI however full rebuilds are a source of pain. I have resorted to useing nix-shell --run cargo build but this means that I am bypassing a large part of the buuld I have defined, which looses features and leads to skew.

This PR makes an option that is very similar to a regular build with just the addition of allowing caching.

@zowoq
Copy link
Contributor

zowoq commented Sep 26, 2020

I have resorted to useing nix-shell --run cargo build but this means that I am bypassing a large part of the buuld I have defined, which looses features and leads to skew.

Why not define the build once and import it into both nix-shell and buildRustPackage?

Comment on lines +84 to +90
releaseDirPrefix = if impureCacheDir == null
then "target"
else
assert
stdenv.lib.assertMsg (stdenv.lib.hasPrefix "/" impureCacheDir)
"impureCacheDir must be an absolute path.";
impureCacheDir;
Copy link
Contributor

@danieldk danieldk Sep 26, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe impureCacheDir could just be renamed cacheDir (or perhaps targetDir) with the default set to "target"? Then this absolute path check is not necessary.

I think this would be a good compromise, because then the derivation does not encourage impure use, but anyone who is willing to disable the Nix sandbox could still set it to an absolute directory. I guess setting CARGO_TARGET_DIR to target as a result does not have a negative impact, except for causing a lot of rebuilds ;).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe impureCacheDir could just be renamed cacheDir with the default set
to "target"? Then this absolute path check is not necessary.

That could be done.

...because then the derivation does not encourage impure use
I'm not sure. This just sounds like hiding the true purpose because I can't
think another reason to set the cache directory.

One possible alternative would be allow passing in the cache directory
manually and provide the resulting cache directory as a separate output.
This is weirder to use and technically pure. However I decided against this
solution as it didn't seem to add any real value other than working without
sandboxing.

@kevincox

This comment has been minimized.

# If set the CARGO_TARGET_DIR will be set to this directory. This will allow
# you to avoid rebuilding dependencies.
# This setting is *incompatible* with sandboxing.
, impureCacheDir ? null
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also should be in the manual, ideally with an example. I assume one could use a relaxed sandbox, where only the rust package is built impure (__noChroot = true;)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked the docs and there isn't an obvious place to put this as there is no attribute reference. Is there a spot that you would recomend?

@kevincox
Copy link
Contributor Author

kevincox commented Nov 2, 2020

I'm closing this for now because naersk was good enough for me. However I think the approach is still viable when impute benefits such as caching are desired.

@kevincox kevincox closed this Nov 2, 2020
@kevincox kevincox deleted the kevincox-rust-cache branch November 2, 2020 14:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants