🚧 This project is under development and is not ready for production yet. APIs are subjects to change.
Build Rust crates with Nix Build System.
- No IFDs (import-from-derivation). See meme.
- No
cargo
dependency during building. Onlyrustc
. - No need for hash prefetching or code generation1.
- Crate level caching, globally shared.
- nixpkgs integration for non-Rust dependencies.
Feature checklist
- Binary cache
- Top 256 popular crate versions with default features
- Nix library
- Non-flake support.
-
[workspace]
-
members
- Auto-
members
-
excludes
FIXME: Buggy.
-
-
resolver
Currently has custom resolution algorithm, more like v2. -
links
-
[profile]
-
[{,dev-,build-}dependencies]
-
[features]
- Overriding API
-
[target.<cfg>.dependencies]
-
[patch]
Automatically supported. Since the dependency graphCargo.lock
currently relies oncargo
's generation. - Cross-compilation. FIXME: Buggy with proc-macros.
noc
helper-
noc init
: Initial templateflake.nix
generation- Dependency kinds
-
registry
-
registry-index
-
git
-
path
inside workspace -
path
outside workspace
-
- Target detection
- Library FIXME: Assume to always exist.
- Binary
- Test
- Bench
- Example
- Dependency kinds
-
Cargo.lock
generation and updating
-
- (Optional) Add binary substituters for pre-built popular crates, by either
- Install
cachix
and runcachix use nocargo
(see more detail aboutcachix
), or - Manually add substituter
https://nocargo.cachix.org
with public keynocargo.cachix.org-1:W6jkp5htZBA1tUdU8XHLaD7zBrIFnor0MsLhHgrJeHk=
- Install
- Enter the root directory of your rust workspace or package. Currently, you should have
Cargo.lock
already created bycargo
. - Run
nix run github:oxalica/nocargo init
to generateflake.nix
. Or write it by hand by following the next section. - Check flake outputs with
nix flake show
. Typically, the layout would be like,└───packages └───x86_64-linux ├───default: package 'rust_mypkg1-0.1.0' # The "default" package. For workspace, it's the top-level one if exists. ├───mypkg1: package 'rust_mypkg1-0.1.0' # Crate `mypkg1` with `release` profile. ├───mypkg1-dev: package 'rust_mypkg1-debug-0.1.0' # Crate `mypkg1` with `dev` profile. ├───mypkg2: package 'rust_mypkg2-0.1.0' # etc. └───mypkg2-dev: package 'rust_mypkg2-debug-0.1.0'
- Run
nix build .#<pkgname>
to build your package. Built binaries (if any) will be placed in./result/bin
, and the library will be in./result/lib
. - Have fun!
A template flake.nix
with common setup are below. It's mostly the same as the generated one, except that the helper noc
will scan the workspace and discover all external registries and git dependencies for you.
{
description = "My Rust packages";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs";
flake-utils.url = "github:numtide/flake-utils";
nocargo = {
url = "github:oxalica/nocargo";
inputs.nixpkgs.follows = "nixpkgs";
# See below.
# inputs.registry-crates-io.follows = "registry-crates-io";
};
# Optionally, you can explicitly import crates.io-index here.
# So you can `nix flake update` at any time to get cutting edge version of crates,
# instead of waiting `nocargo` to dump its dependency.
# Otherwise, you can simply omit this to use the locked registry from `nocargo`,
# which is updated periodically.
# registry-crates-io = { url = "github:rust-lang/crates.io-index"; flake = false; };
};
outputs = { nixpkgs, flake-utils, nocargo, ... }@inputs:
flake-utils.lib.eachSystem [ "x86_64-linux" ] (system:
let
# The entry API to make Nix derivations from your Rust workspace or package.
# The output of it consists of profile names, like `release` or `dev`, each of which is
# a attrset of all member package derivations keyed by their package names.
ws = nocargo.lib.${system}.mkRustPackageOrWorkspace {
# The root directory, which contains `Cargo.lock` and top-level `Cargo.toml`
# (the one containing `[workspace]` for workspace).
src = ./.;
# If you use registries other than crates.io, they should be imported in flake inputs,
# and specified here. Note that registry should be initialized via `mkIndex`,
# with an optional override.
extraRegistries = {
# "https://example-registry.org" = nocargo.lib.${system}.mkIndex inputs.example-registry {};
};
# If you use crates from git URLs, they should be imported in flake inputs,
# and specified here.
gitSrcs = {
# "https://github.com/some/repo" = inputs.example-git-source;
};
# If some crates in your dependency closure require packages from nixpkgs.
# You can override the argument for `stdenv.mkDerivation` to add them.
#
# Popular `-sys` crates overrides are maintained in `./crates-io-override/default.nix`
# to make them work out-of-box. PRs are welcome.
buildCrateOverrides = with nixpkgs.legacyPackages.${system}; {
# Use package id format `pkgname version (registry)` to reference a direct or transitive dependency.
"zstd-sys 2.0.1+zstd.1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = old: {
nativeBuildInputs = [ pkg-config ];
propagatedBuildInputs = [ zstd ];
};
# Use package name to reference local crates.
"mypkg1" = old: {
nativeBuildInputs = [ git ];
};
};
# We use the rustc from nixpkgs by default.
# But you can override it, for example, with a nightly version from https://github.com/oxalica/rust-overlay
# rustc = rust-overlay.packages.${system}.rust-nightly_2022-07-01;
};
in rec {
# For convenience, we hoist derivations of `release` and `dev` profile for easy access,
# with `dev` packages postfixed by `-dev`.
# You can export different packages of your choice.
packages = ws.release
// nixpkgs.lib.mapAttrs' (name: value: { name = "${name}-dev"; inherit value; }) ws.dev
// {
# The "default" features are turned on by default.
# You can `override` the library derivation to enable a different set of features.
# Explicit overriding will disable "default", unless you manually include it.
mypkg1-with-custom-features = (ws.release.mypkg1.override {
# Enables two features (and transitive ones), and disables "default".
features = [ "feature1" "feature2" ];
}).bin;
};
});
}
Main differences are already clarified on the top.
nocargo
is inspired by cargo2nix
and buildRustCrate
in nixpkgs
. We are more like cargo2nix
while the generation part is implemented by pure Nix, but less like naersk
which is a wrapper to call cargo
to build the package inside derivations.
In other words, we and cargo2nix
use Nix as a Build System, while nearsk
use Nix as a Package Manager or Packager.
Detail comparison of nocargo, cargo2nix/buildRustCrate, naersk and buildRustPackage
nocargo | cargo2nix/buildRustCrate |
naersk | buildRustPackage |
|
---|---|---|---|---|
Depend on cargo |
Updating Cargo.lock |
Updating & generating & building | Updating & vendoring & building | Building |
Derivation granularity | Per crate | Per crate | Per package + one dependency closure | All in one |
Crate level sharing | ✔️ | ✔️ | ✖ | ✖ |
Binary substitution per crate | ✔️ | Not implemented | ✖ | ✖ |
Code generation | ✖ | ✔️ | ✖ | ✖ |
Edit workspace & rebuild | Rebuild leaf crates | Rebuild leaf crates | Rebuild leaf crates | Refetch and rebuild all crates |
Edit dependencies & rebuild | Rebuild changed crates (refetch if needed) | Refetch, regenerate and rebuild changed crates | Refetch and rebuild all crates | Refetch and rebuild all crates |
Offline rebuild as long as | Not adding unfetched crate dependency | Not adding unfetched crate dependency | Not changing any dependencies | ✖ |
- Sharing through fine-grained derivations between all projects, not just in one workspace.
- Binary substitution per crate.
No need for global
target_dir
/CARGO_TARGET_DIR
or sccache. - Easy
nixpkgs
integration for non-Rust package dependencies, cross-compilation (planned) and package overriding. - More customizability: per-crate
rustc
flags tweaking, arbitrary crate patching, force dynamic linking and more.
Sorry, currently no. 😿 Updating of Cargo.lock
still relies on cargo
.
This can happen when creating a new project or changing dependencies.
We are mainly using cargo
's SAT solver to pin down the dependency graph.
It's possible to implement it ourselves, but not yet, due to the complexity.
MIT Licensed.
Footnotes
-
Initial template generation and
Cargo.lock
updatin don't count for "code generation". The former is optional, and the latter is indeed not "code". ↩