Cargo-dyndrv: A new way of building Cargo packages with Nix

We have a new way way of building Cargo projects! As the blog post describes, what distinguishes this from all the other ways of building Cargo project in Nix is this combination of methods:

  • Uses dynamic derivations, so no eval time penalty for per-crate incrementality
  • Uses Cargo’s unit graph, so we automatically match to how Cargo performs operations

Both cargo-dyndrv itself and this blog post about it are all @artemist’s work; I am only the one posting it because she has already left for NixCon (see you all there!) and I haven’t left yet, so my internet connection is better.

Finally, thank you for Saronic for collaborating with us on this project.

42 Likes

How does it differ from GitHub - figsoda/drowse: Nix dynamic derivations made easy · GitHub ?

1 Like

The difference is bullet 2: our use of the Cargo unit graph.

That project is using crate2nix underneath the hood, which re-implements lots of what Cargo does. There is nothing wrong with that per se — crate2nix does it a lot less lines of code, in a nicely declarative way! But there are two major downsides:

  • That evaluation is slow. drowse (IIUC) sticks the same evaluation inside a derivation, so while the main evaluation is snappy, you still ultimately need to wait for the slow part, just at build time instead.

  • Re-implementing means that we’re constantly playing catch-up with Cargo, whereas leveraging the unit graph means that not only do we stay caught up, but we also should get support for new Cargo subcommands that work across the crate graph (think stuff like cargo clippy) for free.

    We do loose some flexibility vs the reimplementation approach, but the full fidelity and forward compat I think make this a very favorable trade.


Another way to look at this is since cargo-dyndrv itself knows nothing about the Nix language, Nixpkgs, or other idioms, one could imagine it someday (after Nix world domination ;)) becoming part of Cargo itself.

2 Likes

This is outstanding work! :person_bowing:t4:

2 Likes

Very interesting, would like to see some benchmarks vs cargo-nix-plugin.

This might interest @Qknight

@markuskowa great minds think alike :smiley:

This is incredible timing, I was just looking into pipelined Rust builds yesterday, created a hacky suboptimal solution on top of crate2nix: Pipelined builds · Issue #493 · nix-community/crate2nix · GitHub

So I’m glad someone is working on a legit version of this!

1 Like

Sounds cool. I’m not that deep into nix+rust, but I use craneLib a lot. If anyone more technical than me has time, maybe you can write about how is this better?

Crane is 2 drvs, this is drv per crate

5 Likes

CraneLib invokes cargo, so its behavior is true to running cargo directly. The trade-off is it is substantially less efficient about caching than other options. If you follow the doc recommendations precisely then craneLib builds all of your Rust dependencies in one derivation, and builds your workspace in a second derivation. I think what it does is basically dump the target directory built by that first derivation into the target directory of the second build.

That means that if you change any Rust dep then you have to rebuild all of them. But there’s another problem that bugs me more. I described how craneLib prepulates the target directory. I think cargo treats the content of target as an incremental build, or something to that effect. You’ll notice that it shows “compiling” messages for all deps on every build, even when the deps cache is fresh. That’s because it spends some time doing a verification pass to make sure all of the compiled deps are up-to-date. It’s much faster than building without cached deps, but it is unnecessary time.

You’ll notice that it shows “compiling” messages for all deps on every build, even when the deps cache is fresh. That’s because it spends some time doing a verification pass to make sure all of the compiled deps are up-to-date.

Oh is it because of that? I always thought that was populating templates. Rust doesn’t know which type to use when compiling the dependency crate, and it can only compile it when it sees what types you called it with.