Crane

A Nix library for building cargo projects.

  • Source fetching: automatically done using a Cargo.lock file
  • Incremental: build your workspace dependencies just once, then quickly lint, build, and test changes to your project without slowing down
  • Composable: split builds and tests into granular steps. Gate CI without burdening downstream consumers building from source.

Features

  • Automatic vendoring of dependencies in a way that works with Nix
    • Alternative cargo registries are supported (with a minor configuration change)
    • Git dependencies are automatically supported without additional configuration.
      • Cargo retains the flexibility to only use these dependencies when they are actually needed, without forcing an override for the entire workspace.
  • Reusing dependency artifacts after only building them once
  • clippy checks
  • rustfmt checks
  • cargo-doc generation
  • And support for a number of popular tools such as:

Getting Started

The easiest way to get started is to initialize a flake from a template:

# Start with a comprehensive suite of tests
nix flake init -t github:ipetkov/crane#quick-start

Otherwise check out the examples and templates for more detailed examples. An API Reference is also available.

Compatibility Policy

Breaking changes can land on the master branch at any time, so it is recommended you use a versioning strategy when consuming this library (for example, using something like flakes or niv).

Tagged releases will be cut periodically and changes will be documented in the CHANGELOG. Release versions will follow Semantic Versioning.

The test suite is run against the latest stable nixpkgs release, as well as nixpkgs-unstable. Any breakage on those channels is considered a bug and should be reported as such.

License

This project is licensed under the MIT license.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion by you, shall be licensed as MIT, without any additional terms or conditions.

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog and this project adheres to Semantic Versioning.

Unreleased

0.16.4 - 2024-04-07

Added

  • Added a warning if an unsupported version of nixpkgs is used

Changed

  • cargoNextest now supports setting withLlvmCov which will automatically run cargo llvm-cov nextest. Note that withLlvmCov = true; is (currently) only supported when partitions = 1;

Fixed

  • inheritCargoArtifactsHook and installCargoArtifactsHook now correctly handle the case when CARGO_TARGET_DIR is set to a nested directory
  • Dependency vendoring now correctly takes unused patch dependencies into account

0.16.3 - 2024-03-19

Changed

  • Sources are now fetched crates.io's CDN, following cargo's (new) default behavior.

Fixed

  • vendorMultipleCargoDeps correctly lists registries as an optional parameter

0.16.2 - 2024-02-21

Changed

  • cleanCargoToml now also strips out [lints] and [workspace.lints] definitions. This means avoiding unnecessarily rebuilding dependencies when the lint definitions change, and it avoids issues with failing to build dummified sources which might have violated a lint marked as deny or forbid

Fixed

  • Fixed an edge case with inheriting workspace dependencies where the workspace dependency is a string (e.g. foo = "0.1.2") but the crate definition is a table (e.g. foo = { workspace = true, optional = true })

0.16.1 - 2024-01-28

Changed

  • buildDepsOnly now ignores any outputs (besides the default out)

Fixed

  • buildDepsOnly no longer fails when workspace is configured with #[deny(unused-extern-crates)]
  • vendorCargoDeps (and friends) are now much more friendly to cross-compilation definitions. Specifically, source vendoring will always build dependencies to run on the build machine (and not for the host we're cross compiling to).

0.16.0 - 2024-01-18

Changed

  • Breaking: dropped compatibility for Nix versions below 2.18.1
  • Breaking: dropped compatibility for nixpkgs-23.05.
  • buildTrunkPackage has a new argument, wasm-bindgen-cli must be set to avoid mismatching versions between the wasm-bindgen library and CLI tool.

Fixed

  • Workspace inheritance of lints in git dependencies is now correctly handled

0.15.1 - 2023-11-30

Changed

  • buildDepsOnly will now assume cargoTestExtraArgs = "--no-run"; if not specified (since there is no point to trying to run tests with the stripped sources). To get the old behavior back, set cargoTestExtraArgs = "";

Fixed

  • buildTrunkPackage's preConfigure script to fail quicker with a more obvious error message if dependencies at not appropriately met

0.15.0 - 2023-11-05

Added

  • cargoDeny added for running cargo-deny.
  • installCargoArtifactsHook will now pass along the contents of $zstdCompressionExtraArgs as arguments to zstd when compressing artifacts. This allows for tailoring compression behavior, for example, by setting zstdCompressionExtraArgs = "-19"; on the derivation.

Changed

  • The use-zstd artifact installation mode now uses a chained, incremental approach to avoid redundancy. Old behavior (taking a full snapshot of the cargo artifacts) can be achieved by setting doCompressAndInstallFullArchive = true.
  • The default installCargoArtifactsMode has been changed to use-zstd, meaning cargo artifacts will be compressed to a series of incremental, zstd compressed tarballs across derivations. To get the old behavior back, set installCargoArtifactsMode = "use-symlink" to any derivation which produces cargo artifacts.
  • All dependencies (outside of nixpkgs) have been dropped from the (main) flake.lock file so they do not pollute downstream projects' lock files.

Fixed

  • mkDummySrc now properly handles file cleaning (and file including) when a build is invoked with a --store ... override

0.14.3 - 2023-10-17

Changed

  • craneUtils will now be built with the rustPlatform provided by nixpkgs instead of the currently configured toolchain. This should hopefully result in fewer surprises for those testing with really old MSRV toolchains.
  • devShell will now additionally include clippy and rustfmt from the currently configured toolchain

Fixed

  • replaceCargoLockHook now runs as a prePatch hook (rather than postUnpack) which correctly replaces the Cargo.lock in the source directory rather than the parent directory

0.14.2 - 2023-10-15

Added

  • replaceCargoLockHook can now be used to easily replace or insert a Cargo.lock file in the current derivation

Changed

  • cargoAudit will pass --ignore yanked by default if cargoAuditExtraArgs are not specified. This is because cargo-audit cannot check for yanked crates from inside of the sandbox. To get the old behavior back, set cargoAuditExtraArgs = "";.

Fixed

  • Fixed handling of Cargo workspace inheritance for git-dependencies where said crate relies on reading non-TOML metadata (i.e. comments) from its Cargo.toml at build time. (#407)
  • Fixed handling of dummy target names to avoid issues with cargo doc. (#410)
  • When using installCargoArtifactsMode = "use-zstd"; all files will be marked as user-writable while compressing
  • removeReferencesToVendoredSources now signs aarch64-darwin binaries. (#418)

0.14.1 - 2023-09-23

Fixed

  • Fixed a bug where buildPackage would fail to inherit artifacts from dependency crates if cargoArtifacts was not explicitly specified.

0.14.0 - 2023-09-21

Added

  • Added devShell, a thin wrapper around pkgs.mkShell which automatically provides cargo and rustc.
  • Added the ability to specify output hashes of git dependencies for fully offline evaluations. The outputHashes attribute can now be optionally specified in vendorCargoDeps, vendorGitDeps, vendorMultipleCargoDeps, or anything else which delegates to them.

Changed

  • Breaking (technically): buildDepsOnly, buildPackage, cargoBuild, cargoClippy, cargoDoc, cargoLlvmCov, and cargoTest's defaults have been changed such that if cargoExtraArgs have not been set, a default value of --locked will be used. This ensures that a project's committed Cargo.lock is exactly what is expected (without implicit changes at build time) but this may end up rejecting builds which were previously passing. To get the old behavior back, set cargoExtraArgs = "";
  • Breaking: cargoDoc will no longer install cargo artifacts by default. Set doInstallCargoArtifacts = true; to get the old behavior back.
  • cargoDoc will now install generated documentation in $out/share/doc
  • Fixed a bug when testing proc macro crates with cargoNextest on macOS. (#376)
  • Replaced various internal usages of runCommandLocal with runCommand for more optimal behavior when downloading cached artifacts

0.13.1 - 2023-08-22

Changed

  • buildTrunkPackage will now use dart-sass instead of nodePackages.sass
  • Vendoring git dependencies will now always resolve symlinks inside of a crate's directory. This allows for symlinks inside of a crate's directory to possibly refer to files at the root of the git repo itself (via symlink) and have those contents preserved during vendoring.

0.13.0 - 2023-08-07

Added

  • buildPackage now supports installing dylib targets
  • Added support for sparse registries

Changed

  • Breaking: dropped compatibility for Nix versions below 2.13.3
  • Breaking: dropped compatibility for nixpkgs-22.05. nixpkgs-23.05 and
  • Breaking (technically): if buildPackage is called without setting cargoArtifacts, the default buildDepsOnly invocation will now stop running any installation hooks
  • Breaking (technically): buildPackage no longer installs cargo binary dependencies (i.e. when the bindeps feature is used) by default
  • inheritCargoArtifactsHook will now symlink dependency .rlib and .rmeta files. This means that derivations which reuse existing cargo artifacts will run faster as fewer files (and bytes!) need to be copied around. To disable this behavior, set doNotLinkInheritedArtifacts = true;.
  • cargoTarpaulin will now set doNotLinkInheritedArtifacts = true; unless otherwise specified
  • Update crane-utils dependencies for successful build in nightly Rust (2023-06-28)

0.12.2 - 2023-06-06

Added

  • Added support for the Trunk wasm app build tool

Changed

  • resolver key is no longer cleaned from Cargo.toml

Fixed

  • buildTrunkPackage will now strip references to store files by default
  • buildTrunkPackage will now set the right wasm-opt version

0.12.1 - 2023-04-10

Changed

  • Breaking: When setting a default value for cargoArtifacts, buildPackage will now ignore installPhase and installPhaseCommand when calling buildPackage. To bring back the old behavior, please specify cargoArtifacts explicitly

Added

  • vendorMultipleCargoDeps can now be used to vendor crates from multiple distinct Cargo.lock files. Notably this allows for building the standard library (via -Z build-std or equivalent) since both the project's and the Rust toolchain's Cargo.lock files can be vendored together

Changed

  • vendorCargoRegistries now accepts a registries parameter from the caller. If not specified, it will be computed via cargoConfigs. Also cargoConfigs is now an optional parameter which will default to [] if not specified.

Fixed

  • vendorCargoDeps correctly accepts arguments which have not set src, so long as one of cargoLock, cargoLockContents, or cargoLockParsed is set

0.12.0 - 2023-03-19

Added

  • Add a stubbed binary target to each "dummy" crate generated to support "artifact dependencies" nightly feature in case a crate is used as bin artifact dependency.
  • Add cargoLlvmCov to run cargo llvm-cov
  • Add cargoLockParsed option to vendorCargoDeps to support Cargo.lock files parsed as nix attribute sets.
  • craneLib.path can now be used as a convenience wrapper on (or drop in replacement of) builtins.path to ensure reproducible results whenever paths like ./. or ./.. are used directly.

Changed

  • Breaking (technically): mkCargoDerivation will remove the following attributes before lowering to mkDerivation: cargoLock, cargoLockContents and cargoLockParsed. If your derivation needs these values to be present they can be explicitly passed through via .overrideAttrs buildDepsOnly as dummySrc will take priority
  • The API docs have been updated to refer to craneLib (instead of just lib) to avoid ambiguities with pkgs.lib.
  • cargo is now invoked with --release when $CARGO_PROFILE == release instead of passing in --profile release to better support tools which do not understand the latter

Fixed

  • Fixed support for projects depending on crates utilising per-target workspace dependencies.

0.11.3 - 2023-02-19

Fixed

  • Fixed an unintentional cache invalidation whenever buildDepsOnly would run on an unfiltered source (like src = ./.;).

Changed

  • A warning will now be emitted if a derivation's pname or version attributes are not set and the value cannot be loaded from the derivation's root Cargo.toml. To resolve it consider setting pname = "..."; or version = "..."; explicitly on the derivation.
  • A warning will now be emitted if src and dummySrc are passed to buildDepsOnly as dummySrc will take priority

0.11.2 - 2023-02-11

Fixed

  • buildPackage is more tolerant of misbehaving proc macros which write to stdout during the build

0.11.1 - 2023-01-21

Changed

  • Documented and made it easier to build a cargo workspace located in a subdirectory of the source root

Fixed

  • Previously compiled build scripts now maintain their executable bit when inherited
  • Workspace inheritance in git dependencies is now correctly handled

0.11.0 - 2022-12-26

Added

  • Documentation is now available at crane.dev

Changed

  • Breaking: dropped compatibility for Nix versions below 2.11.0
  • Breaking: dropped compatibility for nixpkgs-22.05. nixpkgs-22.11 and nixpkgs-unstable are fully supported
  • Zstd compression of cargo artifacts now defaults to using as many cores as $NIX_BUILD_CORES allows for (or all available cores if it isn't defined)
  • Dummy sources now attempt to use the same name as their original source (minus the Nix store path and hash) to minimize errors with build scripts which expect their full path to not change between runs

0.10.0 - 2022-12-01

Added

  • A new installation mode has been defined which symlinks identical cargo artifacts against previously generated ones. This allows for linear space usage in the Nix store across many chained derivations (as opposed to using a zstd compressed tarball which uses quadratic space across many chained derivations).
  • mkDummySrc optionally accepts a dummyrs argument which allows for customizing the contents of the dummy Rust files that will be generated.

Changed

  • Breaking: all cargo-based derivations will now default to using symlinking their installed artifacts together instead of using zstd compressed tarballs. To get the old behavior back, set installCargoArtifactsMode = "use-zstd"; in the derivation.
    • Note that buildPackage will continue to use zstd compressed tarballs while building dependencies (unless either of cargoArtifacts or installCargoArtifactsMode is defined, in which case they will be honored)
  • Breaking: the format for defining crate registries has been changed: each registry URL should map to a set containing a downloadUrl attribute. This set may also define fetchurlExtraArgs (another set) which will be forwarded to the fetchurl invocations for crates for that registry.
  • Breaking (technically): buildDepsOnly will now only default to running cargo check with the --all-targets flag only if doCheck = true; is set on the derivation (otherwise the flag is omitted). To get the previous behavior back simply set cargoCheckExtraArgs = "--all-targets";.
  • registryFromGitIndex now uses shallow checkouts for better performance
  • registryFromDownloadUrl and registryFromGitIndex now allow specifying fetchurlExtraArgs which will be forwarded to the fetchurl invocations for crates for that registry

Fixed

  • Unpacking a git repository now ignores duplicate crates to match cargo's behavior
  • Sped up stripping references to source files
  • Dummy sources now import the core crate more robustly (playing more nicely with cargo-hakari)
  • Building a crate's dependencies automatically works for uefi targets

0.9.0 - 2022-10-29

Changed

  • Breaking: all setup hooks have been removed from the packages flake output. They can still be accessed via the lib flake output.
  • Breaking: cargoBuild now only runs cargo build in a workspace, tests are no longer run
  • Breaking: buildDepsOnly does not automatically imply the --all-targets flag when invoking cargo check. Use cargoCheckExtraArgs to control this
  • buildDepsOnly now accepts cargoCheckExtraArgs for passing additional arguments just to the cargo check invocation. By default --all-targets will be used
  • buildDepsOnly now accepts cargoTestExtraArgs for passing additional arguments just to the cargo test invocation
  • buildPackage now delegates to mkCargoDerivation instead of cargoBuild

Fixed

  • crateNameFromCargoToml now takes workspace inheritance into account. If a crate does not specify package.version in its (root) Cargo.toml but does specify workspace.package.version then the latter will be returned.
  • Freestanding (#![no_std]) targets are now supported

0.8.0 - 2022-10-09

Added

  • cargoTest can now be used for only running the tests of a workspace

Changed

  • Breaking (technically): build hooks now expect helper tools (like cargo, jq, zstd, etc.) to be present on the path instead of substituting a reference to a (possibly different) executable in the store.
  • mkCargoDerivation now automatically vendors dependencies if cargoVendorDir is not defined
  • mkCargoDerivation now automatically populates pname and version (via crateNameFromCargoToml) if they are not specified
  • mkCargoDerivation now defaults to an empty checkPhaseCargoCommand if not specified
  • cargoAudit now delegates to mkCargoDerivation instead of cargoBuild
  • cargoClippy now delegates to mkCargoDerivation instead of cargoBuild
  • cargoDoc now delegates to mkCargoDerivation instead of cargoBuild
  • cargoFmt now delegates to mkCargoDerivation instead of cargoBuild
  • cargoNextest now delegates to mkCargoDerivation instead of cargoBuild
  • cargoTarpaulin now delegates to mkCargoDerivation instead of cargoBuild

Fixed

  • Installing binaries now uses the same version of cargo as was used to build the package (instead of using whatever version is present in nixpkgs)

Deprecated

  • The packages flake output has been deprecated. All setup hooks can be accessed via the lib flake output (or via the result of the mkLib flake output)

0.7.0 - 2022-09-28

Added

  • cargoDoc can now be used for building the documentation of a workspace
  • cleanCargoSource can now be used to filter sources to only include cargo and Rust files (and avoid rebuilds when irrelevant files change). filterCargoSources is the underlying filter implementation and can be composed with other filters
  • removeReferencesToVendoredSourcesHook defines a post-install hook which will remove any references to vendored sources from any installed binaries. Useful for preventing nix from considering the binaries as having a (runtime) dependency on said sources

Changed

  • Breaking: mkCargoDerivation now includes a default configurePhase which does nothing but run the preConfigure and postConfigure hooks. This is done to avoid breaking builds by including puts happen to have setup-hooks which try to claim the configure phase (such as cmake). To get the old behavior back, set configurePhase = null; in the derivation.
  • mkCargoDerivation (along with any of its callers like cargoBuild, buildPackage, etc.) now accept a stdenv argument which will override the default environment (coming from pkgs.stdenv) for that particular derivation
  • mkDummySrc now accepts extraScript which can be used to run a custom script, and therefore customize what the dummy source contains
  • buildDepsOnly now accepts dummySrc as a way to directly pass in the dummy source to be used. Automatically derived via args.src if not specified.

Fixed

  • cargoAudit properly keeps any audit.toml files when cleaning the source
  • buildPackage now has more robust checks to ensure that all references to vendored sources are removed after installation (which avoids consumers of the final binaries having to download the sources as well)
  • mkDummySrc how handles build scripts in a manner which ensures cargo runs the real script later (instead of thinking it has not changed)

0.6.0 - 2022-09-07

Added

  • Added cargoNextest for running tests via cargo-nextest
  • Added cargoAudit for running cargo-audit with a provided advisory database instance.

Changed

  • Breaking: the --workspace flag is no longer set for all cargo commands by default. The previous behavior can be recovered by setting cargoExtraArgs = "--workspace"; in any derivation.
  • Breaking: the $CARGO_PROFILE environment variable can be used to specify which cargo-profile all invocations use (by default release will be used). Technically breaking if the default command was overridden for any derivation; set CARGO_PROFILE = ""; to avoid telling cargo to use a release build.
  • Breaking: cargoTarpaulin will use the release profile by default
  • Breaking: cargoClippy's cargoClippyExtraArgs now default to "--all-targets" instead of being specified as the cargo command itself. If you have set cargoClippyExtraArgs to an explicit value and wish to retain the previous behavior you should prepend "--all-targets" to it.
  • Breaking: remapSourcePathPrefixHook and the doRemapSourcePathPrefix option have been removed, and the behavior of buildPackage has been updated to break false dependencies on the crate sources from the final binaries (which was the old behavior of the doRemapSourcePathPrefix option). To disable this behavior, set the doNotRemoveReferencesToVendorDir environment variable to any non-empty string.
  • All cargo invocations made during the build are automatically logged
  • Vendoring git dependencies will throw a descriptive error message if a locked revision is missing from Cargo.lock and a hint towards resolution

Fixed

  • Breaking: vendorGitDeps will only include crates referenced by the Cargo.lock file, meaning any extraneous crates which happen to be present in the git repository will be ignored.

0.5.1 - 2022-07-20

Added

  • Added .overrideToolchain as a convenience for using a custom rust toolchain

Fixed

  • Fixed an issue where mkDummySrc would produce incorrect results for filtered sources: #46

0.5.0 - 2022-06-12

Changed

  • Breaking: dropped compatibility for Nix versions below 2.8.1
  • Breaking: updated all flake attributes to follow the new .default guidance as per Nix's warnings. Specifically:
    • Crane's default overlay is now available at .overlays.default (previously .overlay)
    • All templates now use {app,devShells,packages}.default as well
  • Breaking: lib.fromTOML and lib.toTOML have been removed in favor of builtins.fromTOML
  • Improved support for consuming crane without using flakes
  • The nix-std dependency has been dropped

0.4.1 - 2022-05-29

Fixed

  • Dummy source derivations go to greater lengths to only depend on the files they consume. Specifying the entire flake source as an input (e.g. via buildPackage { src = self; }) now avoids rebuilding everything from scratch whenever any file is changed. #28

0.4.0 - 2022-05-10

Changed

  • Breaking: the previously named utils flake input has been renamed to flake-utils
  • buildDepsOnly now adds --all-targets to the default cargo check invocation. This allows caching all artifacts (including from dev-dependencies) such that tools like clippy don't have to generate them every time they run.
  • Templates now use the newer flake format accepted by Nix 2.8 (e.g. {packages,overlays,devShells}.default, etc.)

Fixed

  • Fixed project and template flakes to avoid superfluous follows declaration for flake-utils
  • Fixed quoting of relative paths to allow building with external sources

0.3.3 - 2022-02-24

Fixed

  • Use lib.groupBy if builtins.groupBy isn't available (i.e. if a Nix version earlier than 2.5 is used)
  • The cross compilation example also hows how to set the HOST_CC environment variable which may be required by some build scripts to function properly

0.3.2 - 2022-02-18

Fixed

  • Fixed handling git dependencies whose locked revision is not on the repository's main branch

0.3.1 - 2022-02-17

Added

  • Added template and example for cross compiling to other platforms
  • Added template and example for building static binaries using musl

Changed

  • cargoClippy and cargoTarpaulin will install cargo artifacts by default (or install an empty target directory if there are none). This allows for more easily chaining derivations if doing so is desired.
    • This can be disabled by setting doInstallCargoArtifacts = false; in the derivation

Fixed

  • Fixed an issue where cross compiling would try to needlessly cross compile rustc and cargo themselves

0.3.0 - 2022-02-11

Added

  • downloadCargoPackageFromGit has been added to handle downloading and unpacking a cargo workspace from a git repository
  • vendorCargoRegistries has been added to handle vendoring crates from all registries used in a Cargo.lock file
  • vendorGitDeps has been added to handle vendoring crates from all git sources used in a Cargo.lock file

Changed

  • vendorCargoDeps now automatically handles git dependencies by default
    • Git dependencies will be vendored as another source in the output derivation
    • The cargo configuration is done such that the sources are available to use when it decides, without overriding that crate for the entire workspace
      • For example, if your workspace contains a crate only used for testing which has a git dependency of a crate used by other parts of the workspace, then only that crate will use the git dependency. The rest of the workspace will continue to use the crates.io version, just like cargo behaves when used outside of Nix.

0.2.1 - 2022-02-11

Changed

  • cargoFmt will install cargo artifacts by default (or install an empty target directory if there are none). This allows for more easily chaining derivations if doing so is desired.
    • This can be disabled by setting doInstallCargoArtifacts = false; in the derivation

0.2.0 - 2022-01-30

Added

  • Support for alternative cargo registries

Changed

  • urlForCargoPackage now takes configured registries into account when downloading crate sources
  • Breaking: vendorCargoDeps now vendors each unique registry as a subdirectory within the derivation's output. A config.toml file is also placed at the output root which contains the necessary configurations to point cargo at the vendored sources.
  • configureCargoVendoredDepsHook is now aware of the updated vendorCargoDeps output format, and will use the config.toml file it generates if it is present. Otherwise it will fall back to the previous behavior (which is treat the entire directory as only vendoring crates.io).
  • Source vendoring now uses runCommandLocal (instead of runCommand) to reduce network pressure in trying to fetch results which can quickly be built locally
  • Searching for Cargo.toml or .cargo/config.toml files is now done more efficiently

0.1.0 - 2022-01-22

  • First release

Philosophy

Crane is designed around the idea of composing cargo invocations such that they can take advantage of the artifacts generated in previous invocations. This allows for both flexible configurations and great caching (à la Cachix) in CI and local development builds.

Here's how it works at a high level: when a cargo workspace is built its source is first transformed such that only the dependencies listed by the Cargo.toml and Cargo.lock files are built, and none of the crate's real source is included. This allows cargo to build all dependency crates and prevents Nix from invalidating the derivation whenever the source files are updated. Then, a second derivation is built, this time using the real source files, which also imports the cargo artifacts generated in the first step.

This pattern can be used with any arbitrary sequence of commands, regardless of whether those commands are running additional lints, performing code coverage analysis, or even generating types from a model schema. Let's take a look at two examples at how very similar configurations can give us very different behavior!

Example One: Artifact Reuse

Suppose we are developing a crate and want to run our CI assurance checks via nix flake check. Perhaps we want the CI gate to be very strict and block any changes which raise warnings when run with cargo clippy. Oh, and we want to enforce some code coverage too!

Except we do not want to push our strict guidelines on any downstream consumers who may want to build our crate. Suppose they need to build the crate with a different compiler version (for one reason or another) which comes with a new lint whose warnings we have not yet addressed. We don't want to make their life harder, so we want to make sure we do not run cargo clippy as part of the crate's actual derivation, but at the same time, we don't want to have to rebuild dependencies from scratch.

Here's how we can set up our flake to achieve our goals:

{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
    crane.url = "github:ipetkov/crane";
    crane.inputs.nixpkgs.follows = "nixpkgs";
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = { self, nixpkgs, crane, flake-utils, ... }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = import nixpkgs {
          inherit system;
        };

        craneLib = crane.lib.${system};

        # Common derivation arguments used for all builds
        commonArgs = {
          src = craneLib.cleanCargoSource (craneLib.path ./.);
          strictDeps = true;

          buildInputs = with pkgs; [
            # Add extra build inputs here, etc.
            # openssl
          ];

          nativeBuildInputs = with pkgs; [
            # Add extra native build inputs here, etc.
            # pkg-config
          ];
        };

        # Build *just* the cargo dependencies, so we can reuse
        # all of that work (e.g. via cachix) when running in CI
        cargoArtifacts = craneLib.buildDepsOnly (commonArgs // {
          # Additional arguments specific to this derivation can be added here.
          # Be warned that using `//` will not do a deep copy of nested
          # structures
          pname = "mycrate-deps";
        });

        # Run clippy (and deny all warnings) on the crate source,
        # reusing the dependency artifacts (e.g. from build scripts or
        # proc-macros) from above.
        #
        # Note that this is done as a separate derivation so it
        # does not impact building just the crate by itself.
        myCrateClippy = craneLib.cargoClippy (commonArgs // {
          # Again we apply some extra arguments only to this derivation
          # and not every where else. In this case we add some clippy flags
          inherit cargoArtifacts;
          cargoClippyExtraArgs = "--all-targets -- --deny warnings";
        });

        # Build the actual crate itself, reusing the dependency
        # artifacts from above.
        myCrate = craneLib.buildPackage (commonArgs // {
          inherit cargoArtifacts;
        });

        # Also run the crate tests under cargo-tarpaulin so that we can keep
        # track of code coverage
        myCrateCoverage = craneLib.cargoTarpaulin (commonArgs // {
          inherit cargoArtifacts;
        });
      in
      {
        packages.default = myCrate;
        checks = {
         inherit
           # Build the crate as part of `nix flake check` for convenience
           myCrate
           myCrateClippy
           myCrateCoverage;
        };
      });
}

When we run nix flake check the following will happen:

  1. The sources for any dependency crates will be fetched
  2. They will be built without our crate's code and the artifacts propagated
  3. Our crate, the clippy checks, and code coverage collection will be built, each reusing the same set of artifacts from the initial source-free build. If enough cores are available to Nix it may build all three derivations completely in parallel, or schedule them in some arbitrary order.

Splitting up our builds like this also gives us the benefit of granular control over what is rebuilt. Suppose we change our mind and decide to adjust the clippy flags (e.g. to allow certain lints or forbid others). Doing so will only rebuild the clippy derivation, without having to rebuild and rerun any of our other tests!

Example Two: Sequential Builds

Let's take an alternative approach to the previous example. Suppose instead that we care more about not wasting any resources building certain tests (even if they would succeed!) if another particular check fails. Perhaps binary substitutes are readily available so that we do not mind if anyone building from source is bound by our rules, and we can be sure that all tests have passed as part of the build.

{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
    crane.url = "github:ipetkov/crane";
    crane.inputs.nixpkgs.follows = "nixpkgs";
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = { self, nixpkgs, crane, flake-utils, ... }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = import nixpkgs {
          inherit system;
        };

        craneLib = crane.lib.${system};
        # Common derivation arguments used for all builds
        commonArgs = {
          src = craneLib.cleanCargoSource (craneLib.path ./.);
          strictDeps = true;

          buildInputs = with pkgs; [
            # Add extra build inputs here, etc.
            # openssl
          ];

          nativeBuildInputs = with pkgs; [
            # Add extra native build inputs here, etc.
            # pkg-config
          ];
        };

        # Build *just* the cargo dependencies, so we can reuse
        # all of that work (e.g. via cachix) when running in CI
        cargoArtifacts = craneLib.buildDepsOnly (commonArgs // {
          # Additional arguments specific to this derivation can be added here.
          # Be warned that using `//` will not do a deep copy of nested
          # structures
          pname = "mycrate-deps";
        });

        # First, run clippy (and deny all warnings) on the crate source.
        myCrateClippy = craneLib.cargoClippy (commonArgs // {
          # Again we apply some extra arguments only to this derivation
          # and not every where else. In this case we add some clippy flags
          inherit cargoArtifacts;
          cargoClippyExtraArgs = "--all-targets -- --deny warnings";
        });

        # Next, we want to run the tests and collect code-coverage, _but only if
        # the clippy checks pass_ so we do not waste any extra cycles.
        myCrateCoverage = craneLib.cargoTarpaulin (commonArgs // {
          cargoArtifacts = myCrateClippy;
        });

        # Build the actual crate itself, _but only if the previous tests pass_.
        myCrate = craneLib.buildPackage (commonArgs // {
          cargoArtifacts = myCrateCoverage;
        });
      in
      {
        packages.default = myCrate;
        checks = {
         inherit
           # Build the crate as part of `nix flake check` for convenience
           myCrate
           myCrateCoverage;
        };
      });
}

When we run nix flake check the following will happen:

  1. The sources for any dependency crates will be fetched
  2. They will be built without our crate's code and the artifacts propagated
  3. Next the clippy checks will run, reusing the dependency artifacts above.
  4. Next the code coverage tests will run, reusing the artifacts from the clippy run
  5. Finally the actual crate itself is built

In this case we lose the ability to build derivations independently, but we gain the ability to enforce a strict build order. However, we can easily change our mind, which would be much more difficult if we had written everything as one giant derivation.

Getting Started

The easiest way to get started is to initialize a flake from a template:

# Start with a comprehensive suite of tests
nix flake init -t github:ipetkov/crane#quick-start

# Or if you want something simpler
nix flake init -t github:ipetkov/crane#quick-start-simple

# If you need a custom rust toolchain (e.g. to build WASM targets):
nix flake init -t github:ipetkov/crane#custom-toolchain

# If you need to use another crate registry besides crates.io
nix flake init -t github:ipetkov/crane#alt-registry

# If you need cross-compilation, you can also try out
nix flake init -t github:ipetkov/crane#cross-rust-overlay

# For statically linked binaries using musl
nix flake init -t github:ipetkov/crane#cross-musl

# If you are building a WASM webapp with trunk
nix flake init -t github:ipetkov/crane#trunk

# If you are building a workspace with trunk member
nix flake init -t github:ipetkov/crane#trunk-workspace

# If you would like to perform end to end testing of a webapp
nix flake init -t github:ipetkov/crane#end-to-end-testing

For an even more lean, no frills set up, create a flake.nix file with the following contents at the root of your cargo workspace:

{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
    crane.url = "github:ipetkov/crane";
    crane.inputs.nixpkgs.follows = "nixpkgs";
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = { self, nixpkgs, crane, flake-utils, ... }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        craneLib = crane.lib.${system};
      in
    {
      packages.default = craneLib.buildPackage {
        src = craneLib.cleanCargoSource (craneLib.path ./.);

        # Add extra inputs here or any other derivation settings
        # doCheck = true;
        # buildInputs = [];
        # nativeBuildInputs = [];
      };
    });
}

To build a cargo project with a comprehensive test suite, run the following in a fresh directory:

nix flake init -t github:ipetkov/crane#quick-start

Alternatively, if you have an existing project already, copy and paste the following flake.nix:

{
  description = "Build a cargo project";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";

    crane = {
      url = "github:ipetkov/crane";
      inputs.nixpkgs.follows = "nixpkgs";
    };

    fenix = {
      url = "github:nix-community/fenix";
      inputs.nixpkgs.follows = "nixpkgs";
      inputs.rust-analyzer-src.follows = "";
    };

    flake-utils.url = "github:numtide/flake-utils";

    advisory-db = {
      url = "github:rustsec/advisory-db";
      flake = false;
    };
  };

  outputs = { self, nixpkgs, crane, fenix, flake-utils, advisory-db, ... }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = nixpkgs.legacyPackages.${system};

        inherit (pkgs) lib;

        craneLib = crane.lib.${system};
        src = craneLib.cleanCargoSource (craneLib.path ./.);

        # Common arguments can be set here to avoid repeating them later
        commonArgs = {
          inherit src;
          strictDeps = true;

          buildInputs = [
            # Add additional build inputs here
          ] ++ lib.optionals pkgs.stdenv.isDarwin [
            # Additional darwin specific inputs can be set here
            pkgs.libiconv
          ];

          # Additional environment variables can be set directly
          # MY_CUSTOM_VAR = "some value";
        };

        craneLibLLvmTools = craneLib.overrideToolchain
          (fenix.packages.${system}.complete.withComponents [
            "cargo"
            "llvm-tools"
            "rustc"
          ]);

        # Build *just* the cargo dependencies, so we can reuse
        # all of that work (e.g. via cachix) when running in CI
        cargoArtifacts = craneLib.buildDepsOnly commonArgs;

        # Build the actual crate itself, reusing the dependency
        # artifacts from above.
        my-crate = craneLib.buildPackage (commonArgs // {
          inherit cargoArtifacts;
        });
      in
      {
        checks = {
          # Build the crate as part of `nix flake check` for convenience
          inherit my-crate;

          # Run clippy (and deny all warnings) on the crate source,
          # again, reusing the dependency artifacts from above.
          #
          # Note that this is done as a separate derivation so that
          # we can block the CI if there are issues here, but not
          # prevent downstream consumers from building our crate by itself.
          my-crate-clippy = craneLib.cargoClippy (commonArgs // {
            inherit cargoArtifacts;
            cargoClippyExtraArgs = "--all-targets -- --deny warnings";
          });

          my-crate-doc = craneLib.cargoDoc (commonArgs // {
            inherit cargoArtifacts;
          });

          # Check formatting
          my-crate-fmt = craneLib.cargoFmt {
            inherit src;
          };

          # Audit dependencies
          my-crate-audit = craneLib.cargoAudit {
            inherit src advisory-db;
          };

          # Audit licenses
          my-crate-deny = craneLib.cargoDeny {
            inherit src;
          };

          # Run tests with cargo-nextest
          # Consider setting `doCheck = false` on `my-crate` if you do not want
          # the tests to run twice
          my-crate-nextest = craneLib.cargoNextest (commonArgs // {
            inherit cargoArtifacts;
            partitions = 1;
            partitionType = "count";
          });
        };

        packages = {
          default = my-crate;
        } // lib.optionalAttrs (!pkgs.stdenv.isDarwin) {
          my-crate-llvm-coverage = craneLibLLvmTools.cargoLlvmCov (commonArgs // {
            inherit cargoArtifacts;
          });
        };

        apps.default = flake-utils.lib.mkApp {
          drv = my-crate;
        };

        devShells.default = craneLib.devShell {
          # Inherit inputs from checks.
          checks = self.checks.${system};

          # Additional dev-shell environment variables can be set directly
          # MY_CUSTOM_DEVELOPMENT_VAR = "something else";

          # Extra inputs can be added here; cargo and rustc are provided by default.
          packages = [
            # pkgs.ripgrep
          ];
        };
      });
}

To build a cargo project without extra tests, run the following in a fresh directory:

nix flake init -t github:ipetkov/crane#quick-start-simple

Alternatively, if you have an existing project already, copy and paste the following flake.nix:

{
  description = "Build a cargo project without extra checks";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";

    crane = {
      url = "github:ipetkov/crane";
      inputs.nixpkgs.follows = "nixpkgs";
    };

    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = { self, nixpkgs, crane, flake-utils, ... }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = nixpkgs.legacyPackages.${system};

        craneLib = crane.lib.${system};
        my-crate = craneLib.buildPackage {
          src = craneLib.cleanCargoSource (craneLib.path ./.);
          strictDeps = true;

          buildInputs = [
            # Add additional build inputs here
          ] ++ pkgs.lib.optionals pkgs.stdenv.isDarwin [
            # Additional darwin specific inputs can be set here
            pkgs.libiconv
          ];

          # Additional environment variables can be set directly
          # MY_CUSTOM_VAR = "some value";
        };
      in
      {
        checks = {
          inherit my-crate;
        };

        packages.default = my-crate;

        apps.default = flake-utils.lib.mkApp {
          drv = my-crate;
        };

        devShells.default = craneLib.devShell {
          # Inherit inputs from checks.
          checks = self.checks.${system};

          # Additional dev-shell environment variables can be set directly
          # MY_CUSTOM_DEVELOPMENT_VAR = "something else";

          # Extra inputs can be added here; cargo and rustc are provided by default.
          packages = [
            # pkgs.ripgrep
          ];
        };
      });
}

To build a cargo project with a custom toolchain (e.g. WASM builds), run the following in a fresh directory:

nix flake init -t github:ipetkov/crane#custom-toolchain

Alternatively, if you have an existing project already, copy and paste the following flake.nix:

{
  description = "Build a cargo project with a custom toolchain";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";

    crane = {
      url = "github:ipetkov/crane";
      inputs.nixpkgs.follows = "nixpkgs";
    };

    flake-utils.url = "github:numtide/flake-utils";

    rust-overlay = {
      url = "github:oxalica/rust-overlay";
      inputs = {
        nixpkgs.follows = "nixpkgs";
        flake-utils.follows = "flake-utils";
      };
    };
  };

  outputs = { self, nixpkgs, crane, flake-utils, rust-overlay, ... }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = import nixpkgs {
          inherit system;
          overlays = [ (import rust-overlay) ];
        };

        rustWithWasiTarget = pkgs.rust-bin.stable.latest.default.override {
          targets = [ "wasm32-wasi" ];
        };

        # NB: we don't need to overlay our custom toolchain for the *entire*
        # pkgs (which would require rebuidling anything else which uses rust).
        # Instead, we just want to update the scope that crane will use by appending
        # our specific toolchain there.
        craneLib = (crane.mkLib pkgs).overrideToolchain rustWithWasiTarget;

        my-crate = craneLib.buildPackage {
          src = craneLib.cleanCargoSource (craneLib.path ./.);
          strictDeps = true;

          cargoExtraArgs = "--target wasm32-wasi";

          # Tests currently need to be run via `cargo wasi` which
          # isn't packaged in nixpkgs yet...
          doCheck = false;

          buildInputs = [
            # Add additional build inputs here
          ] ++ pkgs.lib.optionals pkgs.stdenv.isDarwin [
            # Additional darwin specific inputs can be set here
            pkgs.libiconv
          ];
        };
      in
      {
        checks = {
          inherit my-crate;
        };

        packages.default = my-crate;

        apps.default = flake-utils.lib.mkApp {
          drv = pkgs.writeShellScriptBin "my-app" ''
            ${pkgs.wasmtime}/bin/wasmtime run ${my-crate}/bin/custom-toolchain.wasm
          '';
        };

        devShells.default = craneLib.devShell {
          # Inherit inputs from checks.
          checks = self.checks.${system};

          # Extra inputs can be added here; cargo and rustc are provided by default
          # from the toolchain that was specified earlier.
          packages = [
            # rustWithWasiTarget
          ];
        };
      });
}

To build a cargo project which uses another crate registry, run the following in a fresh directory:

nix flake init -t github:ipetkov/crane#alt-registry

Alternatively, if you have an existing project already, copy and paste the following flake.nix:

{
  description = "Build a cargo project with alternative crate registries";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";

    crane = {
      url = "github:ipetkov/crane";
      inputs.nixpkgs.follows = "nixpkgs";
    };

    flake-utils = {
      url = "github:numtide/flake-utils";
    };
  };

  outputs = { self, nixpkgs, crane, flake-utils, ... }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = nixpkgs.legacyPackages.${system};

        craneLibOrig = crane.lib.${system};
        craneLib = craneLibOrig.appendCrateRegistries [
          # Automatically infer the download URL from the registry's index
          #
          # Note that this approach requires checking out the full index at the specified revision
          # and adding a copy to the Nix store.
          #
          # Also note that the specified git revision _does not need to track updates to the index
          # itself_ as long as the pinned revision contains the most recent version of the
          # registry's `config.json` file. In other words, this commit revision only needs to be
          # updated if the `config.json` file changes the download endpoint for this registry.
          (craneLibOrig.registryFromGitIndex {
            indexUrl = "https://github.com/Hirevo/alexandrie-index";
            rev = "90df25daf291d402d1ded8c32c23d5e1498c6725";
            fetchurlExtraArgs = {
              # Extra parameters which will be passed to the fetchurl invocation for each crate
            };
          })

          # If the registry in question is a sparse index, instead configure as
          #(craneLibOrig.registryFromSparse {
          #  indexUrl = "https://index.crates.io";
          #  # where the sha256 is the sha256 of https://index.crates.io/config.json.
          #  configSha256 = "d16740883624df970adac38c70e35cf077a2a105faa3862f8f99a65da96b14a3";
          #  fetchurlExtraArgs = {
          #    # Extra parameters which will be passed to the fetchurl invocation for each crate
          #  };
          #})

          # As a more lightweight alternative, the `dl` endpoint of the registry's `config.json`
          # file can be copied here to avoid needing to copy the index to the Nix store.
          # (craneLibOrig.registryFromDownloadUrl {
          #   indexUrl = "https://github.com/Hirevo/alexandrie-index";
          #   dl = "https://crates.polomack.eu/api/v1/crates/{crate}/{version}/download";
          #   fetchurlExtraArgs = {
          #     # Extra parameters which will be passed to the fetchurl invocation for each crate
          #   };
          # })
        ];

        my-crate = craneLib.buildPackage {
          src = craneLib.cleanCargoSource (craneLib.path ./.);
          strictDeps = true;

          buildInputs = [
            # Add additional build inputs here
            pkgs.openssl
          ] ++ pkgs.lib.optionals pkgs.stdenv.isDarwin [
            # Additional darwin specific inputs can be set here
            pkgs.libiconv
            pkgs.darwin.apple_sdk.frameworks.Security
          ];

          # Specific to our example, but not always necessary in the general case.
          nativeBuildInputs = [
            pkgs.pkg-config
          ];
        };
      in
      {
        checks = {
          # Build the crate as part of `nix flake check` for convenience
          inherit my-crate;
        };

        packages.default = my-crate;

        apps.default = flake-utils.lib.mkApp {
          drv = my-crate;
        };

        devShells.default = craneLib.devShell {
          # Inherit inputs from checks.
          checks = self.checks.${system};

          # Additional dev-shell environment variables can be set directly
          # MY_CUSTOM_DEVELOPMENT_VAR = "something else";

          # Extra inputs can be added here; cargo and rustc are provided by default.
          packages = [
            # pkgs.ripgrep
          ];
        };
      });
}

To build a cargo project while also compiling the standard library or other crates distributed with the Rust toolchain, run the following in a fresh directory:

nix flake init -t github:ipetkov/crane#build-std

Alternatively, if you have an existing project already, copy and paste the following flake.nix:

{
  description = "Build a cargo project while also compiling the standard library";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";

    crane = {
      url = "github:ipetkov/crane";
      inputs.nixpkgs.follows = "nixpkgs";
    };

    flake-utils.url = "github:numtide/flake-utils";

    rust-overlay = {
      url = "github:oxalica/rust-overlay";
      inputs = {
        nixpkgs.follows = "nixpkgs";
        flake-utils.follows = "flake-utils";
      };
    };
  };

  outputs = { self, nixpkgs, crane, flake-utils, rust-overlay, ... }:
    flake-utils.lib.eachSystem [ "x86_64-linux" ] (system:
      let
        pkgs = import nixpkgs {
          inherit system;
          overlays = [ (import rust-overlay) ];
        };

        rustToolchain = pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.default.override {
          extensions = [ "rust-src" ];
          targets = [ "x86_64-unknown-linux-gnu" ];
        });

        # NB: we don't need to overlay our custom toolchain for the *entire*
        # pkgs (which would require rebuidling anything else which uses rust).
        # Instead, we just want to update the scope that crane will use by appending
        # our specific toolchain there.
        craneLib = (crane.mkLib pkgs).overrideToolchain rustToolchain;

        src = craneLib.cleanCargoSource (craneLib.path ./.);

        my-crate = craneLib.buildPackage {
          inherit src;
          strictDeps = true;

          cargoVendorDir = craneLib.vendorMultipleCargoDeps {
            inherit (craneLib.findCargoFiles src) cargoConfigs;
            cargoLockList = [
              ./Cargo.lock

              # Unfortunately this approach requires IFD (import-from-derivation)
              # otherwise Nix will refuse to read the Cargo.lock from our toolchain
              # (unless we build with `--impure`).
              #
              # Another way around this is to manually copy the rustlib `Cargo.lock`
              # to the repo and import it with `./path/to/rustlib/Cargo.lock` which
              # will avoid IFD entirely but will require manually keeping the file
              # up to date!
              "${rustToolchain.passthru.availableComponents.rust-src}/lib/rustlib/src/rust/Cargo.lock"
            ];
          };

          cargoExtraArgs = "-Z build-std --target x86_64-unknown-linux-gnu";

          buildInputs = [
            # Add additional build inputs here
          ];
        };
      in
      {
        checks = {
          inherit my-crate;
        };

        packages.default = my-crate;

        devShells.default = craneLib.devShell {
          # Inherit inputs from checks.
          checks = self.checks.${system};

          # Extra inputs can be added here; cargo and rustc are provided by default
          # from the toolchain that was specified earlier.
          packages = [
            # rustToolchain
          ];
        };
      });
}

To cross compile a rust project using oxalica/rust-overlay, run the following in a fresh directory:

nix flake init -t github:ipetkov/crane#cross-rust-overlay

Alternatively, if you have an existing project already, copy and paste the following flake.nix:

{
  description = "Cross compiling a rust program using rust-overlay";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";

    crane = {
      url = "github:ipetkov/crane";
      inputs.nixpkgs.follows = "nixpkgs";
    };

    flake-utils.url = "github:numtide/flake-utils";

    rust-overlay = {
      url = "github:oxalica/rust-overlay";
      inputs = {
        nixpkgs.follows = "nixpkgs";
        flake-utils.follows = "flake-utils";
      };
    };
  };

  outputs = { nixpkgs, crane, flake-utils, rust-overlay, ... }:
    flake-utils.lib.eachDefaultSystem (localSystem:
      let
        # Replace with the system you want to build for
        crossSystem = "aarch64-linux";

        pkgs = import nixpkgs {
          inherit crossSystem localSystem;
          overlays = [ (import rust-overlay) ];
        };

        rustToolchain = pkgs.pkgsBuildHost.rust-bin.stable.latest.default.override {
          targets = [ "aarch64-unknown-linux-gnu" ];
        };

        craneLib = (crane.mkLib pkgs).overrideToolchain rustToolchain;

        # Note: we have to use the `callPackage` approach here so that Nix
        # can "splice" the packages in such a way that dependencies are
        # compiled for the appropriate targets. If we did not do this, we
        # would have to manually specify things like
        # `nativeBuildInputs = with pkgs.pkgsBuildHost; [ someDep ];` or
        # `buildInputs = with pkgs.pkgsHostHost; [ anotherDep ];`.
        #
        # Normally you can stick this function into its own file and pass
        # its path to `callPackage`.
        crateExpression =
          { openssl
          , libiconv
          , lib
          , pkg-config
          , qemu
          , stdenv
          }:
          craneLib.buildPackage {
            src = craneLib.cleanCargoSource (craneLib.path ./.);
            strictDeps = true;

            # Build-time tools which are target agnostic. build = host = target = your-machine.
            # Emulators should essentially also go `nativeBuildInputs`. But with some packaging issue,
            # currently it would cause some rebuild.
            # We put them here just for a workaround.
            # See: https://github.com/NixOS/nixpkgs/pull/146583
            depsBuildBuild = [
              qemu
            ];

            # Dependencies which need to be build for the current platform
            # on which we are doing the cross compilation. In this case,
            # pkg-config needs to run on the build platform so that the build
            # script can find the location of openssl. Note that we don't
            # need to specify the rustToolchain here since it was already
            # overridden above.
            nativeBuildInputs = [
              pkg-config
            ] ++ lib.optionals stdenv.buildPlatform.isDarwin [
              libiconv
            ];

            # Dependencies which need to be built for the platform on which
            # the binary will run. In this case, we need to compile openssl
            # so that it can be linked with our executable.
            buildInputs = [
              # Add additional build inputs here
              openssl
            ];

            # Tell cargo about the linker and an optional emulater. So they can be used in `cargo build`
            # and `cargo run`.
            # Environment variables are in format `CARGO_TARGET_<UPPERCASE_UNDERSCORE_RUST_TRIPLE>_LINKER`.
            # They are also be set in `.cargo/config.toml` instead.
            # See: https://doc.rust-lang.org/cargo/reference/config.html#target
            CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER = "${stdenv.cc.targetPrefix}cc";
            CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_RUNNER = "qemu-aarch64";

            # Tell cargo which target we want to build (so it doesn't default to the build system).
            # We can either set a cargo flag explicitly with a flag or with an environment variable.
            cargoExtraArgs = "--target aarch64-unknown-linux-gnu";
            # CARGO_BUILD_TARGET = "aarch64-unknown-linux-gnu";

            # This environment variable may be necessary if any of your dependencies use a
            # build-script which invokes the `cc` crate to build some other code. The `cc` crate
            # should automatically pick up on our target-specific linker above, but this may be
            # necessary if the build script needs to compile and run some extra code on the build
            # system.
            HOST_CC = "${stdenv.cc.nativePrefix}cc";
          };

        # Assuming the above expression was in a file called myCrate.nix
        # this would be defined as:
        # my-crate = pkgs.callPackage ./myCrate.nix { };
        my-crate = pkgs.callPackage crateExpression { };
      in
      {
        checks = {
          inherit my-crate;
        };

        packages.default = my-crate;

        apps.default = flake-utils.lib.mkApp {
          drv = pkgs.writeScriptBin "my-app" ''
            ${pkgs.pkgsBuildBuild.qemu}/bin/qemu-aarch64 ${my-crate}/bin/cross-rust-overlay
          '';
        };
      });
}

To build a cargo project with musl to crate statically linked binaries, run the following in a fresh directory:

nix flake init -t github:ipetkov/crane#cross-musl

Alternatively, if you have an existing project already, copy and paste the following flake.nix:

{
  description = "Building static binaries with musl";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";

    crane = {
      url = "github:ipetkov/crane";
      inputs.nixpkgs.follows = "nixpkgs";
    };

    flake-utils.url = "github:numtide/flake-utils";

    rust-overlay = {
      url = "github:oxalica/rust-overlay";
      inputs = {
        nixpkgs.follows = "nixpkgs";
        flake-utils.follows = "flake-utils";
      };
    };
  };

  outputs = { nixpkgs, crane, flake-utils, rust-overlay, ... }:
    flake-utils.lib.eachSystem [ "x86_64-linux" ] (system:
      let
        pkgs = import nixpkgs {
          inherit system;
          overlays = [ (import rust-overlay) ];
        };

        rustToolchain = pkgs.rust-bin.stable.latest.default.override {
          targets = [ "x86_64-unknown-linux-musl" ];
        };

        craneLib = (crane.mkLib pkgs).overrideToolchain rustToolchain;

        my-crate = craneLib.buildPackage {
          src = craneLib.cleanCargoSource (craneLib.path ./.);
          strictDeps = true;

          CARGO_BUILD_TARGET = "x86_64-unknown-linux-musl";
          CARGO_BUILD_RUSTFLAGS = "-C target-feature=+crt-static";
        };
      in
      {
        checks = {
          inherit my-crate;
        };

        packages.default = my-crate;
      });
}

To cross compiling a rust program for windows, run the following in a fresh directory:

nix flake init -t github:ipetkov/crane#cross-windows

Alternatively, if you have an existing project already, copy and paste the following flake.nix:

{
  description = "Cross compiling a rust program for windows";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";

    crane = {
      url = "github:ipetkov/crane";
      inputs.nixpkgs.follows = "nixpkgs";
    };

    fenix = {
      url = "github:nix-community/fenix";
      inputs.nixpkgs.follows = "nixpkgs";
    };

    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = { nixpkgs, crane, fenix, flake-utils, ... }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = nixpkgs.legacyPackages.${system};

        toolchain = with fenix.packages.${system};
          combine [
            minimal.rustc
            minimal.cargo
            targets.x86_64-pc-windows-gnu.latest.rust-std
          ];

        craneLib = (crane.mkLib pkgs).overrideToolchain toolchain;

        my-crate = craneLib.buildPackage {
          src = craneLib.cleanCargoSource (craneLib.path ./.);

          strictDeps = true;
          doCheck = false;

          CARGO_BUILD_TARGET = "x86_64-pc-windows-gnu";

          depsBuildBuild = with pkgs; [
            pkgsCross.mingwW64.stdenv.cc
            pkgsCross.mingwW64.windows.pthreads
          ];
        };
      in
      {
        packages = {
          inherit my-crate;
          default = my-crate;
        };

        checks = {
          inherit my-crate;
        };
      }
    );
}

Trunk is a tool that allow you to build web apps using Rust and webassembly, including compiling scss, and distributing other assets.

Being a more specialized tool, it comes with some constraints that must be noted when using it in combination with crane:

  • Your Toolchain must have the wasm32-unknown-unknown target installed (See: Custom toolchain)
  • For craneLib.buildDepsOnly to work you will need to set the build target (See: API Reference)
  • craneLib.filterCargoSources will remove html, css, your assets folder, so you need to modify the source filtering function (See: Source filtering)
  • You will need to set wasm-bindgen-cli to a version that matches your Cargo.lock file. (See examples)

For a quick-start run the following in a fresh directory:

nix flake init -t github:ipetkov/crane#trunk

Alternatively, if you have an existing project already, copy and paste the following flake.nix:

{
  description = "Build a cargo project";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";

    # The version of wasm-bindgen-cli needs to match the version in Cargo.lock
    # Update this to include the version you need
    nixpkgs-for-wasm-bindgen.url = "github:NixOS/nixpkgs/4e6868b1aa3766ab1de169922bb3826143941973";

    crane = {
      url = "github:ipetkov/crane";
      inputs.nixpkgs.follows = "nixpkgs";
    };

    flake-utils.url = "github:numtide/flake-utils";

    rust-overlay = {
      url = "github:oxalica/rust-overlay";
      inputs = {
        nixpkgs.follows = "nixpkgs";
        flake-utils.follows = "flake-utils";
      };
    };
  };

  outputs = { self, nixpkgs, crane, flake-utils, rust-overlay, nixpkgs-for-wasm-bindgen, ... }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = import nixpkgs {
          inherit system;
          overlays = [ (import rust-overlay) ];
        };

        inherit (pkgs) lib;

        rustToolchain = pkgs.rust-bin.stable.latest.default.override {
          # Set the build targets supported by the toolchain,
          # wasm32-unknown-unknown is required for trunk
          targets = [ "wasm32-unknown-unknown" ];
        };
        craneLib = ((crane.mkLib pkgs).overrideToolchain rustToolchain).overrideScope (_final: _prev: {
          # The version of wasm-bindgen-cli needs to match the version in Cargo.lock. You
          # can unpin this if your nixpkgs commit contains the appropriate wasm-bindgen-cli version
          inherit (import nixpkgs-for-wasm-bindgen { inherit system; }) wasm-bindgen-cli;
        });

        # When filtering sources, we want to allow assets other than .rs files
        src = lib.cleanSourceWith {
          src = ./.; # The original, unfiltered source
          filter = path: type:
            (lib.hasSuffix "\.html" path) ||
            (lib.hasSuffix "\.scss" path) ||
            # Example of a folder for images, icons, etc
            (lib.hasInfix "/assets/" path) ||
            # Default filter from crane (allow .rs files)
            (craneLib.filterCargoSources path type)
          ;
        };

        # Common arguments can be set here to avoid repeating them later
        commonArgs = {
          inherit src;
          strictDeps = true;
          # We must force the target, otherwise cargo will attempt to use your native target
          CARGO_BUILD_TARGET = "wasm32-unknown-unknown";

          buildInputs = [
            # Add additional build inputs here
          ] ++ lib.optionals pkgs.stdenv.isDarwin [
            # Additional darwin specific inputs can be set here
            pkgs.libiconv
          ];
        };

        # Build *just* the cargo dependencies, so we can reuse
        # all of that work (e.g. via cachix) when running in CI
        cargoArtifacts = craneLib.buildDepsOnly (commonArgs // {
          # You cannot run cargo test on a wasm build
          doCheck = false;
        });

        # Build the actual crate itself, reusing the dependency
        # artifacts from above.
        # This derivation is a directory you can put on a webserver.
        my-app = craneLib.buildTrunkPackage (commonArgs // {
          inherit cargoArtifacts;
          # The version of wasm-bindgen-cli here must match the one from Cargo.lock.
          wasm-bindgen-cli = pkgs.wasm-bindgen-cli.override {
            version = "0.2.90";
            hash = "sha256-X8+DVX7dmKh7BgXqP7Fp0smhup5OO8eWEhn26ODYbkQ=";
            cargoHash = "sha256-ckJxAR20GuVGstzXzIj1M0WBFj5eJjrO2/DRMUK5dwM=";
          };
        });

        # Quick example on how to serve the app,
        # This is just an example, not useful for production environments
        serve-app = pkgs.writeShellScriptBin "serve-app" ''
          ${pkgs.python3Minimal}/bin/python3 -m http.server --directory ${my-app} 8000
        '';
      in
      {
        checks = {
          # Build the crate as part of `nix flake check` for convenience
          inherit my-app;

          # Run clippy (and deny all warnings) on the crate source,
          # again, reusing the dependency artifacts from above.
          #
          # Note that this is done as a separate derivation so that
          # we can block the CI if there are issues here, but not
          # prevent downstream consumers from building our crate by itself.
          my-app-clippy = craneLib.cargoClippy (commonArgs // {
            inherit cargoArtifacts;
            cargoClippyExtraArgs = "--all-targets -- --deny warnings";
          });

          # Check formatting
          my-app-fmt = craneLib.cargoFmt {
            inherit src;
          };
        };

        packages.default = my-app;

        apps.default = flake-utils.lib.mkApp {
          drv = serve-app;
        };

        devShells.default = craneLib.devShell {
          # Inherit inputs from checks.
          checks = self.checks.${system};

          # Additional dev-shell environment variables can be set directly
          # MY_CUSTOM_DEVELOPMENT_VAR = "something else";

          # Extra inputs can be added here; cargo and rustc are provided by default.
          packages = [
            pkgs.trunk
          ];
        };
      });
}

Trunk is a tool that allow you to build web apps using Rust and webassembly, including compiling scss, and distributing other assets. It can be used in conjunction with any of Rust's web frameworks for the development of full stack web applications.

In this example we have a workspace with three members:

  • client: a Yew application compiled using Trunk
  • server: a Axum server built using Cargo
  • shared: a library that contains types to be imported in both the client and server

For a quick-start run the following in a fresh directory:

nix flake init -t github:ipetkov/crane#trunk-workspace

Alternatively, if you have an existing project already, copy and paste the following flake.nix and modify it to build your workspace's packages:

{
  description = "Build a cargo project";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";

    # The version of wasm-bindgen-cli needs to match the version in Cargo.lock
    # Update this to include the version you need
    nixpkgs-for-wasm-bindgen.url = "github:NixOS/nixpkgs/4e6868b1aa3766ab1de169922bb3826143941973";

    crane = {
      url = "github:ipetkov/crane";
      inputs.nixpkgs.follows = "nixpkgs";
    };

    flake-utils.url = "github:numtide/flake-utils";

    rust-overlay = {
      url = "github:oxalica/rust-overlay";
      inputs = {
        nixpkgs.follows = "nixpkgs";
        flake-utils.follows = "flake-utils";
      };
    };
  };

  outputs = { self, nixpkgs, crane, flake-utils, rust-overlay, nixpkgs-for-wasm-bindgen, ... }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = import nixpkgs {
          inherit system;
          overlays = [ (import rust-overlay) ];
        };

        inherit (pkgs) lib;

        rustToolchain = pkgs.rust-bin.stable.latest.default.override {
          # Set the build targets supported by the toolchain,
          # wasm32-unknown-unknown is required for trunk.
          targets = [ "wasm32-unknown-unknown" ];
        };
        craneLib = ((crane.mkLib pkgs).overrideToolchain rustToolchain).overrideScope (_final: _prev: {
          # The version of wasm-bindgen-cli needs to match the version in Cargo.lock. You
          # can unpin this if your nixpkgs commit contains the appropriate wasm-bindgen-cli version
          inherit (import nixpkgs-for-wasm-bindgen { inherit system; }) wasm-bindgen-cli;
        });

        # When filtering sources, we want to allow assets other than .rs files
        src = lib.cleanSourceWith {
          src = ./.; # The original, unfiltered source
          filter = path: type:
            (lib.hasSuffix "\.html" path) ||
            (lib.hasSuffix "\.scss" path) ||
            # Example of a folder for images, icons, etc
            (lib.hasInfix "/assets/" path) ||
            # Default filter from crane (allow .rs files)
            (craneLib.filterCargoSources path type)
          ;
        };


        # Arguments to be used by both the client and the server
        # When building a workspace with crane, it's a good idea
        # to set "pname" and "version".
        commonArgs = {
          inherit src;
          pname = "trunk-workspace";
          version = "0.1.0";
          strictDeps = true;

          buildInputs = [
            # Add additional build inputs here
          ] ++ lib.optionals pkgs.stdenv.isDarwin [
            # Additional darwin specific inputs can be set here
            pkgs.libiconv
          ];
        };

        # Native packages

        nativeArgs = commonArgs // {
          pname = "trunk-workspace-native";
        };

        # Build *just* the cargo dependencies, so we can reuse
        # all of that work (e.g. via cachix) when running in CI
        cargoArtifacts = craneLib.buildDepsOnly nativeArgs;

        # Simple JSON API that can be queried by the client
        myServer = craneLib.buildPackage (nativeArgs // {
          inherit cargoArtifacts;
          # The server needs to know where the client's dist dir is to
          # serve it, so we pass it as an environment variable at build time
          CLIENT_DIST = myClient;
        });

        # Wasm packages

        # it's not possible to build the server on the
        # wasm32 target, so we only build the client.
        wasmArgs = commonArgs // {
          pname = "trunk-workspace-wasm";
          cargoExtraArgs = "--package=client";
          CARGO_BUILD_TARGET = "wasm32-unknown-unknown";
        };

        cargoArtifactsWasm = craneLib.buildDepsOnly (wasmArgs // {
          doCheck = false;
        });

        # Build the frontend of the application.
        # This derivation is a directory you can put on a webserver.
        myClient = craneLib.buildTrunkPackage (wasmArgs // {
          pname = "trunk-workspace-client";
          cargoArtifacts = cargoArtifactsWasm;
          trunkIndexPath = "client/index.html";
          # The version of wasm-bindgen-cli here must match the one from Cargo.lock.
          wasm-bindgen-cli = pkgs.wasm-bindgen-cli.override {
            version = "0.2.90";
            hash = "sha256-X8+DVX7dmKh7BgXqP7Fp0smhup5OO8eWEhn26ODYbkQ=";
            cargoHash = "sha256-ckJxAR20GuVGstzXzIj1M0WBFj5eJjrO2/DRMUK5dwM=";
          };
        });
      in
      {
        checks = {
          # Build the crate as part of `nix flake check` for convenience
          inherit myServer myClient;

          # Run clippy (and deny all warnings) on the crate source,
          # again, reusing the dependency artifacts from above.
          #
          # Note that this is done as a separate derivation so that
          # we can block the CI if there are issues here, but not
          # prevent downstream consumers from building our crate by itself.
          my-app-clippy = craneLib.cargoClippy (commonArgs // {
            inherit cargoArtifacts;
            cargoClippyExtraArgs = "--all-targets -- --deny warnings";
            # Here we don't care about serving the frontend
            CLIENT_DIST = "";
          });

          # Check formatting
          my-app-fmt = craneLib.cargoFmt commonArgs;
        };

        apps.default = flake-utils.lib.mkApp {
          name = "server";
          drv = myServer;
        };

        devShells.default = craneLib.devShell {
          # Inherit inputs from checks.
          checks = self.checks.${system};

          shellHook = ''
            export CLIENT_DIST=$PWD/client/dist;
          '';

          # Extra inputs can be added here; cargo and rustc are provided by default.
          packages = [
            pkgs.trunk
          ];
        };
      });
}

In addition to Unit and Integration tests, you can also write tests that interact with your application as a real user would. That technique is called End to End(E2E) testing.

In this example we have a workspace with two members:

  • server: a web server that uses Axum for HTTP and Sqlx connect to an instance of PostgreSQL
  • e2e: a end-to-end test "script" that drives Firefox into interacting with the sever

Quick-start an E2E project in a fresh directory with:

nix flake init -t github:ipetkov/crane#end-to-end-testing

Alternatively, if you have an existing project already, copy and paste the following flake.nix and modify it to build your workspace's packages:

{
  description = "Example E2E testing";
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";

    crane = {
      url = "github:ipetkov/crane";
      inputs = {
        nixpkgs.follows = "nixpkgs";
        flake-utils.follows = "flake-utils";
      };
    };

    flake-utils.url = "github:numtide/flake-utils";
  };
  outputs = { nixpkgs, crane, flake-utils, ... }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = nixpkgs.legacyPackages.${system};
        inherit (pkgs) lib;
        craneLib = crane.lib.${system};
        src = craneLib.cleanCargoSource (craneLib.path ./.);

        workspace = craneLib.buildPackage {
          inherit src;
          pname = "example-e2e";
          version = "0.1";
          doCheck = false;
          nativeBuildInputs = lib.optionals pkgs.stdenv.isDarwin
            (with pkgs.darwin.apple_sdk.frameworks; [
              pkgs.libiconv
              CoreFoundation
              Security
              SystemConfiguration
            ]);
        };

        # The script inlined for brevity, consider extracting it
        # so that it becomes independent of nix
        runE2ETests = pkgs.runCommand "e2e-tests"
          {
            nativeBuildInputs = with pkgs; [
              retry
              curl
              geckodriver
              firefox
              cacert
              postgresql
            ];
          } ''

          wait-for-connection() {
            timeout 5s \
              retry --until=success --delay "1" -- \
                curl -s "$@"
          }

          initdb postgres-data
          pg_ctl --pgdata=postgres-data --options "-c unix_socket_directories=$PWD" start
          export DATABASE_URL="postgres:///postgres?host=$PWD"
          psql "$DATABASE_URL" <<EOF
            CREATE TABLE users(name TEXT);
          EOF

          ${workspace}/bin/server &
          wait-for-connection --fail localhost:8000

          # Firefox likes to write to $HOME
          HOME="$(mktemp -d)" geckodriver &
          wait-for-connection localhost:4444

          ${workspace}/bin/e2e_tests

          touch $out
        '';

        pkgsSupportsPackage = pkg:
          lib.any (s: s == pkgs.stdenv.hostPlatform.config) pkg;
      in
      {
        checks = {
          inherit workspace;
          # Firefox is broken in some platforms (namely "aarch64-apple-darwin"), skip those
        } // (lib.optionalAttrs (pkgsSupportsPackage pkgs.firefox.meta.platforms) {
          inherit runE2ETests;
        });

        devShells.default = pkgs.mkShell {
          BuildInputs = with pkgs; [
            rustc
            cargo
          ] ++ (lib.optionals (!pkgs.stdenv.isDarwin) [
            geckodriver
            firefox
          ]);
        };
      });
}

To build a cargo project which depends on the SQLx crate, run the following in a fresh directory:

nix flake init -t github:ipetkov/crane#sqlx

Alternatively, if you have an existing project already, copy and paste the following flake.nix:

{
  description = "Build a cargo project which uses SQLx";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";

    crane = {
      url = "github:ipetkov/crane";
      inputs.nixpkgs.follows = "nixpkgs";
    };

    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = { self, nixpkgs, crane, flake-utils, ... }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = nixpkgs.legacyPackages.${system};

        inherit (pkgs) lib;

        craneLib = crane.lib.${system};

        sqlFilter = path: _type: null != builtins.match ".*sql$" path;
        sqlOrCargo = path: type: (sqlFilter path type) || (craneLib.filterCargoSources path type);

        src = lib.cleanSourceWith {
          src = craneLib.path ./.; # The original, unfiltered source
          filter = sqlOrCargo;
        };

        # Common arguments can be set here to avoid repeating them later
        commonArgs = {
          inherit src;
          strictDeps = true;

          nativeBuildInputs = [
            pkgs.pkg-config
          ];

          buildInputs = [
            # Add additional build inputs here
            pkgs.openssl
          ] ++ lib.optionals pkgs.stdenv.isDarwin [
            # Additional darwin specific inputs can be set here
            pkgs.libiconv
            pkgs.darwin.apple_sdk.frameworks.Security
          ];

          # Additional environment variables can be set directly
          # MY_CUSTOM_VAR = "some value";
        };

        # Build *just* the cargo dependencies, so we can reuse
        # all of that work (e.g. via cachix) when running in CI
        cargoArtifacts = craneLib.buildDepsOnly commonArgs;

        # Build the actual crate itself, reusing the dependency
        # artifacts from above.
        my-crate = craneLib.buildPackage (commonArgs // {
          inherit cargoArtifacts;

          nativeBuildInputs = (commonArgs.nativeBuildInputs or [ ]) ++ [
            pkgs.sqlx-cli
          ];

          preBuild = ''
            export DATABASE_URL=sqlite:./db.sqlite3
            sqlx database create
            sqlx migrate run
          '';
        });
      in
      {
        checks = {
          # Build the crate as part of `nix flake check` for convenience
          inherit my-crate;
        };

        packages = {
          default = my-crate;
          inherit my-crate;
        };

        devShells.default = craneLib.devShell {
          # Inherit inputs from checks.
          checks = self.checks.${system};

          # Additional dev-shell environment variables can be set directly
          # MY_CUSTOM_DEVELOPMENT_VAR = "something else";

          # Extra inputs can be added here; cargo and rustc are provided by default.
          packages = [
            pkgs.sqlx-cli
          ];
        };
      });
}

Source filtering

Nix considers that a derivation must be rebuilt whenever any of its inputs change, including all source files passed into the build. Unfortunately, this means that changes to any "irrelevant" files (such as the project README) would end up rebuilding the project even if the final outputs don't actually care about their contents!

Source filtering is a technique Nix employs that allows for better caching by programmatically filtering out files which are known to not apply to the build before the inputs are hashed.

A default source cleaner is available via craneLib.cleanCargoSource: it cleans a source tree to omit things like version control directories as well omit any non-Rust/non-cargo related files. It can be used like so:

craneLib.buildPackage {
  # other attributes omitted
  src = craneLib.cleanCargoSource (craneLib.path ./.);
}

It is possible to customize the filter to use when cleaning the source by leveraging craneLib.filterCargoSources. By default this filter will only keep files whose names end with .rs or .toml. Though it is possible to compose it with other filters, especially if it is necessary to include additional files which it might otherwise omit:

let
  # Only keeps markdown files
  markdownFilter = path: _type: builtins.match ".*md$" path != null;
  markdownOrCargo = path: type:
    (markdownFilter path type) || (craneLib.filterCargoSources path type);
in
craneLib.buildPackage {
  # other attributes omitted
  src = lib.cleanSourceWith {
    src = craneLib.path ./.; # The original, unfiltered source
    filter = markdownOrCargo;
  };
}

Local Development

Nix shells (or development shells) are extremely powerful when it comes to locally developing with the exact same dependencies used when building packages.

To get started, declare a default devShell in flake.nix using craneLib.devShell and run nix develop in the project directory. Then, you can use something like direnv or nix-direnv to automatically enter and exit a development shell when you enter or exit the project directory!

Sample flake.nix:

{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";

    crane = {
      url = "github:ipetkov/crane";
      inputs.nixpkgs.follows = "nixpkgs";
    };

    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = { self, nixpkgs, crane, flake-utils, ... }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = import nixpkgs {
          inherit system;
        };

        craneLib = crane.lib.${system};
        my-crate = craneLib.buildPackage {
          src = craneLib.cleanCargoSource (craneLib.path ./.);

          buildInputs = [
            # Add additional build inputs here
          ] ++ pkgs.lib.optionals pkgs.stdenv.isDarwin [
            # Additional darwin specific inputs can be set here
            pkgs.libiconv
          ];

          # Additional environment variables can be set directly
          # MY_CUSTOM_VAR = "some value";
        };
      in
      {
        packages.default = my-crate;

        devShells.default = craneLib.devShell {
          # Additional dev-shell environment variables can be set directly
          MY_CUSTOM_DEV_URL = "http://localhost:3000";

          # Automatically inherit any build inputs from `my-crate`
          inputsFrom = [ my-crate ];

          # Extra inputs (only used for interactive development)
          # can be added here; cargo and rustc are provided by default.
          packages = [
            pkgs.cargo-audit
            pkgs.cargo-watch
          ];
        };
      });
}

Then, after integrating direnv into your shell:

echo "use flake" > .envrc
direnv allow

Custom cargo commands

Although it is possible to customize exactly what build commands and flags are used by the provided functions like buildPackage, or cargoBuild, sometimes it is useful to encapsulate a cargo invocation that crane does not know about. Doing so allows that helper function to be used across different crates, or even different Nix flakes without having to duplicate the logic in multiple build definitions.

mkCargoDerivation allows building such extensions. Below is a short example to illustrate the approach. The reference also explores the inputs and behavior of mkCargoDerivation in greater depth.

{ pkgs, craneLib }:

# Let's assume we want to add a helper for a fictitious `cargo awesome` command
let
  cargoAwesome = {
    cargoArtifacts,
    cargoAwesomeExtraArgs ? "", # Arguments that are generally useful default
    cargoExtraArgs ? "" # Other cargo-general flags (e.g. for features or targets)
  }@origArgs: let
    # Clean the original arguments for good hygiene (i.e. so the flags specific
    # to this helper don't pollute the environment variables of the derivation)
    args = builtins.removeAttrs origArgs [
      "cargoAwesomeExtraArgs"
      "cargoExtraArgs"
    ];
  in
  craneLib.mkCargoDerivation (args // {
    # Additional overrides we want to explicitly set in this helper

    # Require the caller to specify cargoArtifacts we can use
    inherit cargoArtifacts;

    # A suffix name used by the derivation, useful for logging
    pnameSuffix = "-awesome";

    # Set the cargo command we will use and pass through the flags
    buildPhaseCargoCommand = "cargo awesome ${cargoExtraArgs} ${cargoAwesomeExtraArgs}";

    # Append the `cargo-awesome` package to the nativeBuildInputs set by the
    # caller (or default to an empty list if none were set)
    nativeBuildInputs = (args.nativeBuildInputs or [ ]) ++ [ pkgs.cargo-awesome ];
  });
in
cargoAwesome {
  src = craneLib.cleanCargoSource (craneLib.path ./.);
}

Customizing builds

All derivations, whether they are configured through buildPackage, cargoBuild, or even mkCargoDerivation, eventually delegate to mkDerivation which is defined by nixpkgs.

At its heart, mkDerivation builds up a big bash script which is executed by the builder. Inputs are added to the execution $PATH, libraries are added to include paths, and all other variables are set as shell variables. But these scripts also come with a small framework for running various different phases. Many of these phases also come with their own hooks which are shell functions which can be subscribed to execute before and/or after a particular phase has run.

Although build phases and their hooks allow for easily extending and customizing the build instructions for a particular derivation, it can become difficult to identify exactly where a bit of logic should execute. The following are a good set of resources to consult when in doubt:

  1. The nixpkgs manual for describing the default set of build phases and their hooks
  2. The crane API reference for additional hooks it introduces
  3. Setting NIX_DEBUG to a non-zero value will cause the builder to print out various variables and commands it will run (increasing values will increase the verbosity).
  4. When all else fails source for the generic build scripts themselves can be useful

All that out of the way, here's a quick example of how to use the build phases and hooks to customize a particular build:

craneLib.buildPackage {
  src = craneLib.cleanCargoSource (craneLib.path ./.);

  # Define a list of function names to execute before the `configurePhase` runs
  preConfigurePhases = [
    "foo"
    "bar"
  ];

  # Define the functions themselves
  foo = ''
    # double the amount of rust test threads we can use
    # Note that crane will set these defaults as a `postPatchHook` which
    # should have already run by the time the preConfigurePhases are called
    export RUST_TEST_THREADS=$((RUST_TEST_THREADS * 2))
  '';

  bar = ''
    # decrement by one test thread if running in release mode
    if [[ "${CARGO_PROFILE}" == "release" ]]; then
      export RUST_TEST_THREADS=$((RUST_TEST_THREADS - 2))
    fi
  '';

  # Lastly, add postInstall to install additional items after
  # the default installPhase has run and installed the package binaries
  postInstall = ''
    echo "hello world" > $out/hello.txt
    # also install the README.md for good measure
    cp README.md $out/
  '';
}

API Documentation

mkLib

mkLib :: pkgs -> set

Creates a lib instance bound to the specified (and instantiated) pkgs set. This is a convenience escape hatch in case you want to use your own custom instantiation of nixpkgs with the overlays you may need.

mkLib (import inputs.nixpkgs { system = "armv7l-linux"; })

Note that if you wish to override a particular package without having to overlay it across all of nixpkgs, consider using overrideScope:

(mkLib pkgs).overrideScope (final: prev: {
  cargo-tarpaulin = myCustomCargoTarpaulinVersion;
})

To overlay an entire rust toolchain (e.g. cargo, rustc, clippy, rustfmt, etc.) consider using overrideToolchain.

craneLib

craneLib represents an instantiated value crated by mkLib above.

craneLib.appendCrateRegistries

appendCrateRegistries :: [registry mapping] -> new lib

Creates a new lib instance which will make additional registries available for use when downloading crate sources. Each entry can be defined using:

  • registryFromDownloadUrl: if you know the exact dl URL as defined in the registry's config.json file
  • registryFromGitIndex: if you would like the download URL to be inferred from the index's source directly.
  • registryFromSparse: if you would like the download URL to be inferred from the index's source directly, and the index is a sparse index.

See the documentation on each function for more specifics.

newLib = craneLib.appendCrateRegistries [
  (craneLib.registryFromDownloadUrl {
    indexUrl = "https://github.com/rust-lang/crates.io-index";
    dl = "https://static.crates.io/crates";
    fetchurlExtraArgs = {};
  })

  # Or, alternatively
  (craneLib.registryFromGitIndex {
    indexUrl = "https://github.com/Hirevo/alexandrie-index";
    rev = "90df25daf291d402d1ded8c32c23d5e1498c6725";
    fetchurlExtraArgs = {};
  })

  # Or even
  (lib.registryFromSparse {
    url = "https://index.crates.io/";
    sha256 = "d16740883624df970adac38c70e35cf077a2a105faa3862f8f99a65da96b14a3";
  })
];

craneLib.buildDepsOnly

buildDepsOnly :: set -> drv

Create a derivation which will only build all dependencies of a cargo workspace.

Useful for splitting up cargo projects into two derivations: one which only builds dependencies and needs to be rebuilt when a Cargo.lock file changes, and another which inherits the cargo artifacts from the first and (quickly) builds just the application itself.

The exact cargo commands being run (or the arguments passed into it) can be easily updated to suit your needs. By default all artifacts from running cargo {check,build,test} will be cached.

In addition to all default and overridden values being set as documented below, all derivation attributes are delegated to mkCargoDerivation, and can be used to influence its behavior.

  • cargoArtifacts: set to null since this is our entry point for generating cargo artifacts
  • doInstallCargoArtifacts: set to true
  • pnameSuffix: set to "-deps"
  • src: set to the result of mkDummySrc after applying the arguments set. This ensures that we do not need to rebuild the cargo artifacts derivation whenever the application source changes.

Optional attributes

  • buildPhaseCargoCommand: A command to run during the derivation's build phase. Pre and post build hooks will automatically be run.
    • Default value: "${cargoCheckCommand} ${cargoExtraArgs}\n${cargoBuildCommand} ${cargoExtraArgs}"
  • cargoBuildCommand: A cargo (build) invocation to run during the derivation's build phase
    • Default value: "cargo build --profile release"
      • CARGO_PROFILE can be set on the derivation to alter which cargo profile is selected; setting it to "" will omit specifying a profile altogether.
  • cargoCheckCommand: A cargo (check) invocation to run during the derivation's build phase (in order to cache additional artifacts)
    • Default value: "cargo check --profile release ${cargoCheckExtraArgs}"
      • CARGO_PROFILE can be set on the derivation to alter which cargo profile is selected; setting it to "" will omit specifying a profile altogether.
  • cargoCheckExtraArgs: additional flags to be passed in the cargoCheckCommand invocation
    • Default value: "--all-targets" if doCheck is set to true, "" otherwise
  • cargoExtraArgs: additional flags to be passed in the cargo invocation (e.g. enabling specific features)
    • Default value: "--locked"
  • cargoTestCommand: A cargo invocation to run during the derivation's check phase
    • Default value: "cargo test --profile release"
      • CARGO_PROFILE can be set on the derivation to alter which cargo profile is selected; setting it to "" will omit specifying a profile altogether.
  • cargoTestExtraArgs: additional flags to be passed in the cargoTestCommand invocation (e.g. enabling specific tests)
    • Default value: "--no-run"
  • cargoVendorDir: A path (or derivation) of vendored cargo sources which can be consumed without network access. Directory structure should basically follow the output of cargo vendor.
    • Default value: the result of vendorCargoDeps after applying the arguments set (with the respective default values)
  • checkPhaseCargoCommand: A command to run during the derivation's check phase. Pre and post check hooks will automatically be run.
    • Default value: "${cargoTestCommand} ${cargoExtraArgs}"
  • doCheck: whether the derivation's check phase should be run
    • Default value: true
  • dummySrc: the "dummy" source to use when building this derivation. Automatically derived if not passed in
    • Default value: mkDummySrc args.src
  • pname: package name of the derivation
    • Default value: inherited from calling crateNameFromCargoToml
  • version: version of the derivation
    • Default value: inherited from calling crateNameFromCargoToml

Remove attributes

The following attributes will be removed before being lowered to mkCargoDerivation. If you absolutely need these attributes present as environment variables during the build, you can bring them back via .overrideAttrs.

  • cargoBuildCommand
  • cargoCheckCommand
  • cargoCheckExtraArgs
  • cargoExtraArgs
  • cargoTestCommand
  • cargoTestExtraArgs
  • dummySrc
  • outputHashes
  • outputs

craneLib.buildPackage

buildPackage :: set -> drv

A(n opinionated) version of mkCargoDerivation which will install to the output any binaries which were built by cargo in this invocation. All options understood by mkCargoDerivation apply here as well, with the only difference being some additional book keeping necessary to log cargo's results and subsequently install from that log.

Note that only bin, cdylib, dylib, and staticlib, targets will be installed by default (namely rlib targets will be ignored), though it is possible to adjust the behavior by changing the installPhaseCommand or registering additional install hooks.

Optional attributes

  • buildPhaseCargoCommand: A command to run during the derivation's build phase. Pre and post build hooks will automatically be run.
    • Default value: cargoBuildCommand will be invoked along with cargoExtraArgs passed in, except cargo's build steps will also be captured and written to a log so that it can be used to find the build binaries.
    • Note that the default install hook assumes that the build phase will create a log of cargo's build results. If you wish to customize this command completely, make sure that cargo is run with --message-format json-render-diagnostics and the standard output captured and saved to a file. The cargoBuildLog shell variable should point to this log.
  • cargoArtifacts: A path (or derivation) which contains an existing cargo target directory, which will be reused at the start of the derivation. Useful for caching incremental cargo builds.
    • Default value: the result of buildDepsOnly after applying the arguments set (with the respective default values).
    • installPhase and installPhaseCommand will be removed, and no installation hooks will be run
  • cargoBuildCommand: A cargo invocation to run during the derivation's build phase
    • Default value: "cargo build --profile release"
      • CARGO_PROFILE can be set on the derivation to alter which cargo profile is selected; setting it to "" will omit specifying a profile altogether.
  • cargoExtraArgs: additional flags to be passed in the cargo invocation (e.g. enabling specific features)
    • Default value: "--locked"
  • cargoTestCommand: A cargo invocation to run during the derivation's check phase
    • Default value: "cargo test --profile release"
      • CARGO_PROFILE can be set on the derivation to alter which cargo profile is selected; setting it to "" will omit specifying a profile altogether.
  • cargoTestExtraArgs: additional flags to be passed in the cargoTestCommand invocation (e.g. enabling specific tests)
    • Default value: ""
  • doCheck: whether the derivation's check phase should be run
    • Default value: true
  • doInstallCargoArtifacts: controls whether cargo's target directory should be copied as an output
    • Default value: false
  • installPhaseCommand: the command(s) which are expected to install the derivation's outputs.
    • Default value: will look for a cargo build log and install all binary targets listed there

Remove attributes

The following attributes will be removed before being lowered to mkCargoDerivation. If you absolutely need these attributes present as environment variables during the build, you can bring them back via .overrideAttrs.

  • cargoBuildCommand
  • cargoExtraArgs
  • cargoTestCommand
  • cargoTestExtraArgs
  • outputHashes

Native build dependencies and included hooks

The following hooks are automatically added as native build inputs:

  • installFromCargoBuildLogHook
  • jq
  • removeReferencesToVendoredSourcesHook

craneLib.buildTrunkPackage

buildTrunkPackage :: set -> drv

Create a derivation which will build a distributable directory for a WASM application.

Except where noted below, all derivation attributes are delegated to mkCargoDerivation, and can be used to influence its behavior.

Optional attributes

  • buildPhaseCargoCommand: A command to run during the derivation's build phase. Pre and post build hooks will automatically be run.
    • Default value: trunk build will be invoked along with trunkExtraArgs, trunkExtraBuildArgs, and trunkIndexpath passed in. If $CARGO_PROFILE is set to release then the --release flag will also be set for the build
  • cargoArtifacts: A path (or derivation) which contains an existing cargo target directory, which will be reused at the start of the derivation. Useful for caching incremental cargo builds.
    • Default value: the result of buildDepsOnly after applying the arguments set (with the respective default values).
    • CARGO_BUILD_TARGET will be set to "wasm32-unknown-unknown" if not specified.
    • doCheck will be set to false if not specified.
    • installPhase and installPhaseCommand will be removed (in favor of their default values provided by buildDepsOnly)
  • installPhaseCommand: the command(s) which are expected to install the derivation's outputs.
    • Default value: will install trunk's dist output directory
  • trunkExtraArgs pass additional arguments to trunk
    • Default value: ""
  • trunkExtraBuildArgs pass additional arguments to trunk build
    • Default value: ""
  • trunkIndexPath A path to the index.html of your trunk project
    • Default value: "./index.html"
  • wasm-bindgen-cli The package used to satisfy the wasm-bindgen-cli dependency of trunk, the version used here must match the version of wasm-bindgen in the Cargo.lock file of your project exactly.
    • Default value: pkgs.wasm-bindgen-cli

Remove attributes

The following attributes will be removed before being lowered to mkCargoDerivation. If you absolutely need these attributes present as environment variables during the build, you can bring them back via .overrideAttrs.

  • trunkExtraArgs
  • trunkExtraBuildArgs
  • trunkIndexPath

Native build dependencies and included hooks

The following hooks are automatically added as native build inputs:

  • binaryen
  • dart-sass
  • trunk

craneLib.cargoAudit

cargoAudit :: set -> drv

Create a derivation which will run a cargo audit invocation in a cargo workspace.

Except where noted below, all derivation attributes are delegated to mkCargoDerivation, and can be used to influence its behavior.

  • buildPhaseCargoCommand will be set to run cargo audit -n -d ${advisory-db} in the workspace.
  • cargoArtifacts will be set to null as they are not needed
  • cargoVendorDir will be set to null as it is not needed
  • doInstallCargoArtifacts is disabled
  • pnameSuffix will be set to "-audit"
  • src will be filtered to only keep Cargo.lock files

Required attributes

  • advisory-db: A path (or derivation) which contains the advisory database
    • It is possible to track the advisory database as a flake input and avoid having to manually update hashes or specific revisions to check out
  • src: The project source to audit, it must contain a Cargo.lock file
    • Note that the source will internally be filtered to omit any files besides Cargo.lock. This avoids having to audit the project again until either the advisory database or the dependencies change.

Optional attributes

  • cargoAuditExtraArgs: additional flags to be passed in the cargo-audit invocation
    • Default value: "--ignore yanked"
  • cargoExtraArgs: additional flags to be passed in the cargo invocation
    • Default value: ""
  • pname: the name of the derivation; will not be introspected from a Cargo.toml file
    • Default value: "crate"
  • version: the version of the derivation, will not be introspected from a Cargo.toml file
    • Default value: "0.0.0"

Native build dependencies

The cargo-audit package is automatically appended as a native build input to any other nativeBuildInputs specified by the caller.

Remove attributes

The following attributes will be removed before being lowered to mkCargoDerivation. If you absolutely need these attributes present as environment variables during the build, you can bring them back via .overrideAttrs.

  • cargoAuditExtraArgs
  • cargoExtraArgs

craneLib.cargoDeny

cargoDeny :: set -> drv

Create a derivation which will run a cargo deny invocation in a cargo workspace.

Note that although cargo deny can serve as a replacement for cargo audit, craneLib.cargoDeny does not expose this functionality because cargo deny requires the full source tree, rather than working from just the Cargo.lock file, meaning it will be re-run when any source file changes, rather than only when dependencies change.

Except where noted below, all derivation attributes are delegated to mkCargoDerivation, and can be used to influence its behavior.

  • buildPhaseCargoCommand will be set to run cargo --offline $cargoExtraArgs deny $cargoDenyExtraArgs check $cargoDenyChecks in the workspace.
  • cargoArtifacts will be set to null
  • doInstallCargoArtifacts will be set to false
  • pnameSuffix will be set to "-deny"

Optional attributes

  • cargoDenyChecks: check types to run
    • Default value: "bans licenses sources"
  • cargoDenyExtraArgs: additional flags to be passed in the cargo-deny invocation
    • Default value: ""
  • cargoExtraArgs: additional flags to be passed in the cargo invocation
    • Default value: ""

Native build dependencies

The cargo-deny package is automatically appended as a native build input to any other nativeBuildInputs specified by the caller.

Remove attributes

The following attributes will be removed before being lowered to mkCargoDerivation. If you absolutely need these attributes present as environment variables during the build, you can bring them back via .overrideAttrs.

  • cargoDenyExtraArgs
  • cargoExtraArgs

craneLib.cargoBuild

cargoBuild :: set -> drv

Create a derivation which will run a cargo build invocation in a cargo workspace. Consider using buildPackage if all you need is to build the workspace and install the resulting application binaries.

Except where noted below, all derivation attributes are delegated to mkCargoDerivation, and can be used to influence its behavior.

  • buildPhaseCargoCommand will be set to run cargo build --profile release for the workspace.
    • CARGO_PROFILE can be set on the derivation to alter which cargo profile is selected; setting it to "" will omit specifying a profile altogether.
  • pnameSuffix will be set to "-build"

Required attributes

  • cargoArtifacts: A path (or derivation) which contains an existing cargo target directory, which will be reused at the start of the derivation. Useful for caching incremental cargo builds.
    • This can be prepared via buildDepsOnly
    • Alternatively, any cargo-based derivation which was built with doInstallCargoArtifacts = true will work as well

Optional attributes

  • cargoExtraArgs: additional flags to be passed in the cargo invocation (e.g. enabling specific features)
    • Default value: "--locked"

Remove attributes

The following attributes will be removed before being lowered to mkCargoDerivation. If you absolutely need these attributes present as environment variables during the build, you can bring them back via .overrideAttrs.

  • cargoExtraArgs

craneLib.cargoClippy

cargoClippy :: set -> drv

Create a derivation which will run a cargo clippy invocation in a cargo workspace.

Except where noted below, all derivation attributes are delegated to mkCargoDerivation, and can be used to influence its behavior.

  • buildPhaseCargoCommand will be set to run cargo clippy --profile release for the workspace.
    • CARGO_PROFILE can be set on the derivation to alter which cargo profile is selected; setting it to "" will omit specifying a profile altogether.
  • pnameSuffix will be set to "-clippy"

Required attributes

  • cargoArtifacts: A path (or derivation) which contains an existing cargo target directory, which will be reused at the start of the derivation. Useful for caching incremental cargo builds.
    • This can be prepared via buildDepsOnly
    • Alternatively, any cargo-based derivation which was built with doInstallCargoArtifacts = true will work as well

Optional attributes

  • cargoClippyExtraArgs: additional flags to be passed in the clippy invocation (e.g. deny specific lints)
    • Default value: "--all-targets"
  • cargoExtraArgs: additional flags to be passed in the cargo invocation (e.g. enabling specific features)
    • Default value: "--locked"

Native build dependencies

The clippy package is automatically appended as a native build input to any other nativeBuildInputs specified by the caller.

Remove attributes

The following attributes will be removed before being lowered to mkCargoDerivation. If you absolutely need these attributes present as environment variables during the build, you can bring them back via .overrideAttrs.

  • cargoClippyExtraArgs
  • cargoExtraArgs

craneLib.cargoDoc

cargoDoc :: set -> drv

Create a derivation which will run a cargo doc invocation in a cargo workspace.

Except where noted below, all derivation attributes are delegated to mkCargoDerivation, and can be used to influence its behavior.

  • buildPhaseCargoCommand will be set to run cargo doc --profile release for the workspace.
    • CARGO_PROFILE can be set on the derivation to alter which cargo profile is selected; setting it to "" will omit specifying a profile altogether.
  • doInstallCargoArtifacts will default to false if not specified
  • pnameSuffix will be set to "-doc"

Required attributes

  • cargoArtifacts: A path (or derivation) which contains an existing cargo target directory, which will be reused at the start of the derivation. Useful for caching incremental cargo builds.
    • This can be prepared via buildDepsOnly
    • Alternatively, any cargo-based derivation which was built with doInstallCargoArtifacts = true will work as well

Optional attributes

  • cargoDocExtraArgs: additional flags to be passed in the rustdoc invocation (e.g. deny specific lints)
    • Default value: "--no-deps"
  • cargoExtraArgs: additional flags to be passed in the cargo invocation (e.g. enabling specific features)
    • Default value: "--locked"

Remove attributes

The following attributes will be removed before being lowered to mkCargoDerivation. If you absolutely need these attributes present as environment variables during the build, you can bring them back via .overrideAttrs.

  • cargoDocExtraArgs
  • cargoExtraArgs

craneLib.cargoFmt

cargoFmt :: set -> drv

Create a derivation which will run a cargo fmt invocation in a cargo workspace.

Except where noted below, all derivation attributes are delegated to mkCargoDerivation, and can be used to influence its behavior.

  • buildPhaseCargoCommand will be set to run cargo fmt (in check mode) in the workspace.
  • cargoArtifacts is disabled/cleared
  • cargoVendorDir is disabled/cleared
  • pnameSuffix will be set to "-fmt"

Optional attributes

  • cargoExtraArgs: additional flags to be passed in the cargo invocation
    • Default value: ""
  • rustFmtExtraArgs: additional flags to be passed in the rustfmt invocation
    • Default value: ""

Native build dependencies

The rustfmt package is automatically appended as a native build input to any other nativeBuildInputs specified by the caller.

Remove attributes

The following attributes will be removed before being lowered to mkCargoDerivation. If you absolutely need these attributes present as environment variables during the build, you can bring them back via .overrideAttrs.

  • cargoExtraArgs
  • rustFmtExtraArgs

craneLib.cargoLlvmCov

cargoLlvmCov :: set -> drv

Create a derivation which will run a cargo llvm-cov invocation in a cargo workspace.

Except where noted below, all derivation attributes are delegated to mkCargoDerivation, and can be used to influence its behavior.

  • buildPhaseCargoCommand will be set to run cargo llvm-cov test --release in the workspace.
  • installPhaseCommand will be set to "", as the default settings creates a file instead of directory at $out.
  • doInstallCargoArtifacts will be set to false for the same reason as installPhaseCommand
  • pnameSuffix will be set to "-llvm-cov"

Required attributes

  • cargoArtifacts: A path (or derivation) which contains an existing cargo target directory, which will be reused at the start of the derivation. Useful for caching incremental cargo builds.
    • This can be prepared via buildDepsOnly
    • Alternatively, any cargo-based derivation which was built with doInstallCargoArtifacts = true will work as well

Optional attributes

  • cargoExtraArgs: additional flags to be passed in the cargo invocation
    • Default value: "--locked"
  • cargoLlvmCovCommand: cargo-llvm-cov command to run
    • Default value: "test"
  • cargoLlvmCovExtraArgs: additional flags to be passed in the cargo llvm-cov invocation
    • Default value: "--lcov --output-path $out"

Native build dependencies

The cargo-llvm-cov package is automatically appended as a native build input to any other nativeBuildInputs specified by the caller.

Note that this would require the llvm-tools-preview component for the Rust toolchain, which you would need to provide yourself using fenix or rust-overlay.

Remove attributes

The following attributes will be removed before being lowered to mkCargoDerivation. If you absolutely need these attributes present as environment variables during the build, you can bring them back via .overrideAttrs.

  • cargoExtraArgs
  • cargoLlvmCovCommand
  • cargoLlvmCovExtraArgs

craneLib.cargoNextest

cargoNextest :: set -> drv

Create a derivation which will run a cargo nextest invocation in a cargo workspace.

Except where noted below, all derivation attributes are delegated to mkCargoDerivation, and can be used to influence its behavior.

  • checkPhaseCargoCommand will be set to run cargo nextest run --profile release for the workspace.
    • CARGO_PROFILE can be set on the derivation to alter which cargo profile is selected; setting it to "" will omit specifying a profile altogether.
  • pnameSuffix will be set to "-nextest" and may include partition numbers

Required attributes

  • cargoArtifacts: A path (or derivation) which contains an existing cargo target directory, which will be reused at the start of the derivation. Useful for caching incremental cargo builds.
    • This can be prepared via buildDepsOnly
    • Alternatively, any cargo-based derivation which was built with doInstallCargoArtifacts = true will work as well

Optional attributes

  • buildPhaseCargoCommand, unless specified, will be set to print the nextest version
  • cargoExtraArgs: additional flags to be passed in the cargo invocation (e.g. enabling specific features)
    • Default value: ""
  • cargoLlvmCovExtraArgs: additional flags to be passed in the cargo llvm-cov invocation
    • Default value: "--lcov --output-path $out/coverage"
  • cargoNextestExtraArgs: additional flags to be passed in the clippy invocation (e.g. deny specific lints)
    • Default value: ""
  • partitions: The number of separate nextest partitions to run. Useful if the test suite takes a long time and can be parallelized across multiple build nodes.
    • Default value: 1
  • partitionType: The kind of nextest partition to run (e.g. "count" or "hash" based).
    • Default value: "count"
  • withLlvmCov: Whether or not to run nextest through cargo llvm-cov
    • Default value: false
    • Note that setting withLlvmCov = true; is not currently supported if partitions > 1.

Native build dependencies

The cargo-nextest package is automatically appended as a native build input to any other nativeBuildInputs specified by the caller.

Remove attributes

The following attributes will be removed before being lowered to mkCargoDerivation. If you absolutely need these attributes present as environment variables during the build, you can bring them back via .overrideAttrs.

  • cargoExtraArgs
  • cargoLlvmCovExtraArgs
  • cargoNextestExtraArgs
  • partitions
  • partitionType
  • withLlvmCov

craneLib.cargoTarpaulin

cargoTarpaulin :: set -> drv

Create a derivation which will run a cargo tarpaulin invocation in a cargo workspace.

Except where noted below, all derivation attributes are delegated to mkCargoDerivation, and can be used to influence its behavior.

  • buildPhaseCargoCommand will be set to run cargo tarpaulin --profile release in the workspace.
    • CARGO_PROFILE can be set on the derivation to alter which cargo profile is selected; setting it to "" will omit specifying a profile altogether.
  • pnameSuffix will be set to "-tarpaulin"

Required attributes

  • cargoArtifacts: A path (or derivation) which contains an existing cargo target directory, which will be reused at the start of the derivation. Useful for caching incremental cargo builds.
    • This can be prepared via buildDepsOnly
    • Alternatively, any cargo-based derivation which was built with doInstallCargoArtifacts = true will work as well

Optional attributes

  • cargoExtraArgs: additional flags to be passed in the cargo invocation
    • Default value: ""
  • cargoTarpaulinExtraArgs: additional flags to be passed in the cargo tarpaulin invocation
    • Default value: "--skip-clean --out xml --output-dir $out"
  • doNotLinkInheritedArtifacts will be set to true if not specified.

Native build dependencies

The cargo-tarpaulin package is automatically appended as a native build input to any other nativeBuildInputs specified by the caller.

Remove attributes

The following attributes will be removed before being lowered to mkCargoDerivation. If you absolutely need these attributes present as environment variables during the build, you can bring them back via .overrideAttrs.

  • cargoExtraArgs
  • cargoTarpaulinExtraArgs

craneLib.cargoTest

cargoTest :: set -> drv

Create a derivation which will run a cargo test invocation in a cargo workspace.

Except where noted below, all derivation attributes are delegated to

  • buildPhaseCargoCommand will be set to run cargo test --profile release in the workspace.
    • CARGO_PROFILE can be set on the derivation to alter which cargo profile is selected; setting it to "" will omit specifying a profile altogether.
  • pnameSuffix will be set to "-test"

Optional attributes

  • cargoExtraArgs: additional flags to be passed in the cargo invocation
    • Default value: "--locked"
  • cargoTestArgs: additional flags to be passed in the cargo invocation
    • Default value: ""

Remove attributes

The following attributes will be removed before being lowered to mkCargoDerivation. If you absolutely need these attributes present as environment variables during the build, you can bring them back via .overrideAttrs.

  • cargoExtraArgs
  • cargoTestExtraArgs

craneLib.cleanCargoSource

cleanCargoSource :: path or drv -> drv

Cleans a source tree to omit things like version control directories as well omit any non-Rust/non-cargo related files. Useful to avoid rebuilding a project when unrelated files are changed (e.g. flake.nix or any other nix files).

The final output will be cleaned by both cleanSource (from nixpkgs) and craneLib.filterCargoSources. See each of them for more details on which files are kept.

If it is necessary to customize which files are kept, a custom filter can be written (which may want to also call craneLib.filterCargoSources) to achieve the desired behavior.

craneLib.cleanCargoSource (craneLib.path ./.)

craneLib.cleanCargoToml

cleanCargoToml :: set -> set

Cleans all definitions from a Cargo.toml file which are irrelevant for a minimal build of a package's dependencies. See mkDummySrc for more information on how the result is applied.

In general, the following types of attributes are kept from the original input:

  • basic package definitions (like name and version)
  • dependency definitions
  • feature definitions
  • workspace definitions
  • anything pertaining to project structure (like bin/lib targets, tests, etc.)
craneLib.cleanCargoToml { cargoToml = ./Cargo.toml; }
# { dependencies = { byteorder = "*"; }; package = { edition = "2021"; name = "simple"; version = "0.1.0"; }; }

Input attributes

  • cargoToml: a path to a Cargo.toml file
  • cargoTomlContents: the contents of a Cargo.toml file as a string

At least one of the above attributes must be specified, or an error will be raised during evaluation.

craneLib.crateNameFromCargoToml

crateNameFromCargoToml :: set -> set

Extract a crate's name and version from its Cargo.toml file.

The resulting pname attribute will be populated with the value of the Cargo.toml's (top-level) package.name attribute, if present and if the value is a string. Otherwise workspace.package.name will be used if it is present and the value is a string. Otherwise a placeholder version field will be used.

The resulting version attribute will be populated with the value of the Cargo.toml's (top-level) package.version attribute, if present and if the value is a string. Otherwise workspace.package.version will be used if it is present and the value is a string. Otherwise a placeholder version field will be used.

Note that only the root Cargo.toml of the specified source will be checked. Directories will not be crawled to resolve potential workspace inheritance.

craneLib.crateNameFromCargoToml { cargoToml = ./Cargo.toml; }
# { pname = "simple"; version = "0.1.0"; }

craneLib.crateRegistries

crateRegistries :: set

A set of crate registries made available for use in downloading crate sources. The keys are registry URLs as used in the Cargo.lock file (e.g. "registry+https://...") and the values are the download URL for that registry, including any placeholder values cargo is expected to populate for downloads.

This definition can be updated via appendCrateRegistries.

Input attributes

  • src: a directory which includes a Cargo.toml file at its root.
  • cargoToml: a path to a Cargo.toml file
  • cargoTomlContents: the contents of a Cargo.toml file as a string

At least one of the above attributes must be specified, or an error will be raised during evaluation.

Output attributes

  • pname: the name of the crate
    • Default value: "cargo-package" if the specified Cargo.toml file did not include a name
  • version: the version of the crate
    • Default value: "0.0.1" if the specified Cargo.toml file did not include a version

craneLib.devShell

devShell :: set -> drv

A thin wrapper around pkgs.mkShell for creating development shells for use with nix develop (see “Local Development”). Except where noted below, all derivation attributes are passed straight through, so any mkShell behavior can be used as expected: namely, all key-value pairs other than those mkShell consumes will be set as environment variables in the resulting shell.

Note that the current toolchain's cargo, clippy, rustc, and rustfmt packages will automatically be added to the devShell.

Optional attributes

  • checks: A set of checks to inherit inputs from, typically self.checks.${system}. Build inputs from the values in this attribute set are added to the created shell environment for interactive use.
  • inputsFrom: A list of extra packages to inherit inputs from. Note that these packages are not added to the result environment; use packages for that.
  • packages: A list of extra packages to add to the created shell environment.
  • shellHook: A string of bash statements that will be executed when the shell is entered with nix develop.

See the quick start example for usage in a flake.nix file.

craneLib.devShell {
  checks = self.checks.${system};

  packages = [
    pkgs.ripgrep
  ];

  # Set a `cargo-nextest` profile:
  NEXTEST_PROFILE = "local";
}
craneLib.devShell {
  checks = {
    my-package-clippy = craneLib.cargoClippy commonArgs;
    my-package-doc = craneLib.cargoDoc commonArgs;
    my-package-nextest = craneLib.cargoNextest commonArgs;
  };
}

craneLib.downloadCargoPackage

downloadCargoPackage :: set -> drv

Download a packaged cargo crate (e.g. from crates.io) and prepare it for vendoring.

The registry's fetchurlExtraArgs will be passed through to fetchurl when downloading the crate, making it possible to influence interacting with the registry's API if necessary.

Required input attributes

  • checksum: the (sha256) checksum recorded in the Cargo.lock file
  • name: the name of the crate
  • source: the source key recorded in the Cargo.lock file
  • version: the version of the crate

craneLib.downloadCargoPackageFromGit

downloadCargoPackageFromGit :: set -> drv

Download a git repository containing a cargo crate or workspace, and prepare it any crates it contains for vendoring.

Required input attributes

  • git: the URL to the repository
  • rev: the exact revision to check out

Optional attributes

  • allRefs: whether all git refs should be fetched in order to look for the specified rev
    • Default value: true if ref is set to null, false otherwise
  • ref: the ref (i.e. branch or tag) to which rev belongs to. For branches it should be "refs/head/${branch}" and for tags it should be "refs/tags/${tag}"
    • Default value: null
  • sha256: the sha256 hash of the (unpacked) download. If provided fetchgit will be used (instead of builtins.fetchGit) which allows for offline evaluations.
    • Default value: null

craneLib.findCargoFiles

findCargoFiles :: path -> set of lists

Given a path, recursively search it for any Cargo.toml, .cargo/config or .cargo/config.toml files.

craneLib.findCargoFiles ./src
# { cargoTomls = [ "..." ]; cargoConfigs = [ "..." ]; }

craneLib.filterCargoSources

filterCargoSources :: path -> string -> bool

A source filter which when used with cleanSourceWith (from nixpkgs's lib) will retain the following files from a given source:

  • Cargo files (Cargo.toml, Cargo.lock, .cargo/config.toml, .cargo/config)
  • Rust files (files whose name end with .rs)
  • TOML files (files whose name end with .toml)
cleanSourceWith {
  src = craneLib.path ./.;
  filter = craneLib.filterCargoSources;
}

Note that it is possible to compose source filters, especially if filterCargoSources omits files which are relevant to the build. For example:

let
  # Only keeps markdown files
  markdownFilter = path: _type: builtins.match ".*md$" path != null;
  markdownOrCargo = path: type:
    (markdownFilter path type) || (craneLib.filterCargoSources path type);
in
cleanSourceWith {
  src = craneLib.path ./.;
  filter = markdownOrCargo;
}

craneLib.mkCargoDerivation

mkCargoDerivation :: set -> drv

A thin wrapper around stdenv.mkDerivation which includes common hooks for building a derivation using cargo. Except where noted below, all derivation attributes are passed straight through, so any common derivation behavior can be used as expected: namely all key-value pairs will be set as environment variables for the derivation's build script.

This is a fairly low-level abstraction, so consider using buildPackage or cargoBuild if they fit your needs.

Required attributes

  • buildPhaseCargoCommand: A command (likely a cargo invocation) to run during the derivation's build phase. Pre and post build hooks will automatically be run.
  • cargoArtifacts: A path (or derivation) which contains an existing cargo target directory, which will be reused at the start of the derivation. Useful for caching incremental cargo builds.
    • This can be prepared via buildDepsOnly
    • Alternatively, any cargo-based derivation which was built with doInstallCargoArtifacts = true will work as well

Optional attributes

  • buildPhase: the commands used by the build phase of the derivation
    • Default value: the build phase will run preBuild hooks, print the cargo version, log and evaluate buildPhaseCargoCommand, and run postBuild hooks
  • cargoLock: if set will be passed through to the derivation and the path it points to will be copied as the workspace Cargo.lock
    • Unset by default
  • cargoLockContents: if set and cargoLock is missing or null, its value will be written as the workspace Cargo.lock
    • Unset by default
  • cargoLockParsed: if set and both cargoLock and cargoLockContents are missing or null, its value will be serialized as TOML and the result written as the workspace Cargo.lock
    • Unset by default
  • cargoVendorDir: A path (or derivation) of vendored cargo sources which can be consumed without network access. Directory structure should basically follow the output of cargo vendor.
    • Default value: the result of vendorCargoDeps after applying the arguments set (with the respective default values)
  • checkPhase: the commands used by the check phase of the derivation
    • Default value: the check phase will run preCheck hooks, log and evaluate checkPhaseCargoCommand, and run postCheck hooks
  • checkPhaseCargoCommand: A command (likely a cargo invocation) to run during the derivation's check phase. Pre and post check hooks will automatically be run.
    • Default value: ""
  • configurePhase: the commands used by the configure phase of the derivation
    • Default value: the configure phase will run preConfigureHooks hooks, then run postConfigure hooks
  • doInstallCargoArtifacts: controls whether cargo's target directory should be copied as an output
    • Default value: true
  • installPhase: the commands used by the install phase of the derivation
    • Default value: the install phase will run preInstall hooks, log and evaluate installPhaseCommand, and run postInstall hooks
  • installPhaseCommand: the command(s) which are expected to install the derivation's outputs.
    • Default value: "mkdir -p $out"
    • By default an output directory is created such that any other postInstall hooks can successfully run. Consider overriding this value with an appropriate installation commands for the package being built.
  • pname: the name of the derivation
    • Default value: the package name listed in Cargo.toml
  • pnameSuffix: a suffix appended to pname
    • Default value: ""
  • stdenv: the standard build environment to use for this derivation
    • Default value: pkgs.stdenv
  • version: the version of the derivation
    • Default value: the version listed in Cargo.toml

Remove attributes

The following attributes will be removed before being lowered to stdenv.mkDerivation. If you absolutely need these attributes present as environment variables during the build, you can bring them back via .overrideAttrs.

  • buildPhaseCargoCommand
  • cargoLock
  • cargoLockContents
  • cargoLockParsed
  • checkPhaseCargoCommand
  • installPhaseCommand
  • outputHashes
  • pnameSuffix
  • stdenv

Native build dependencies and included hooks

The cargo package is automatically appended as a native build input to any other nativeBuildInputs specified by the caller, along with the following hooks:

  • cargoHelperFunctionsHook
  • configureCargoCommonVarsHook
  • configureCargoVendoredDepsHook
  • inheritCargoArtifactsHook
  • installCargoArtifactsHook
  • replaceCargoLockHook
  • rsync
  • zstd

craneLib.mkDummySrc

mkDummySrc :: set -> drv

Converts a given source directory of a cargo workspace to the smallest, most trivial form needed to build all dependencies such that their artifacts can be cached.

The actual source files of the project itself are ignored/replaced with empty programs, such that changes to the source files does not invalidate any build caches. More specifically:

  • The Cargo.lock file is kept as-is
    • Any changes to it will invalidate the build cache
  • Any cargo configuration files (i.e. files name config or config.toml whose parent directory is named .cargo) are kept as-is.
    • Any changes to these files will invalidate the build cache
  • Any files named Cargo.toml are reduced via cleanCargoToml and the result is kept. Only the following changes will result in invalidating the build cache:
    • Any changes to listed dependencies
    • Any changes to feature definitions
    • Any changes to the workspace member metadata
    • Any changes to the [package] definition such as name and version
    • Any changes to the name or path of any target (such as benches, bins, examples, libs, or tests)

Required attributes

  • src: a source directory which should be turned into a "dummy" form

Optional attributes

  • cargoLock: a path to a Cargo.lock file
    • Default value: src + /Cargo.lock
  • dummyrs: a path to a file which will be used in place of all dummy rust files (e.g. main.rs, lib.rs, etc.). This can be useful to customize dummy source files (e.g. enable certain lang features for a given target).
    • Default value: an empty fn main declaration and conditionally enabled #![no_std] if the target_os cfg is set to "none" or "uefi".
  • extraDummyScript: additional shell script which will be run inside the builder verbatim. Useful for customizing what the dummy sources include by running any arbitrary commands.
    • Default value: ""
    • Note that this script will run in an environment where the original source is not present as doing so would cause a rebuild if any part of the source changed. Additional files can be copied to the derivation's result, but care must be taken that the derivation only depends on (i.e. is rebuilt if) the smallest subset of the original source as required.
    • Here is an example of how to include an entire directory, in this case .cargo, but any other directory would work as well:
      let
        # The _entire_ source of the project. mkDummySrc will automatically
        # filter out irrelevant files as described above
        src = craneLib.path ./.;
      
        dotCargoOnly = lib.cleanSourceWith {
          inherit src;
          # Only keep `*/.cargo/*`
          filter = path: _type: lib.hasInfix ".cargo" path;
        };
      in
      mkDummySrc {
        inherit src;
      
        # Note that here we scope the path to only contain any `.cargo` directory
        # and its contents and not any other  directories which may exist at the
        # root of the project. Also note that the entire path is inside of the
        # `${ }` which ensures that the derivation only consumes that directory.
        # Writing `${./.}/.cargo` would incorrectly consume the entire source root,
        # and therefore rebuild everything when any file changes, which defeats
        # artifact caching.
        #
        # Also note the `--no-target-directory` flag which ensures the results are
        # copied to `$out/.cargo` instead of something like `$out/HASH-.cargo`
        extraDummyScript = ''
          cp -r ${dotCargoOnly} --no-target-directory $out/
        '';
      }
      

craneLib.overrideToolchain

overrideToolchain :: drv -> set

A convenience method to override and use tools (like cargo, clippy, rustfmt, rustc, etc.) from one specific toolchain. The input should be a single derivation which contains all the tools as binaries. For example, this can be the output of oxalica/rust-overlay.

craneLib.overrideToolchain myCustomToolchain

craneLib.path

path :: path -> drv

path :: set -> drv

A convenience wrapper around builtins.path which will automatically set the path's name to the workspace's package name (or a placeholder value of "source" if a name cannot be determined).

It should be used anywhere a relative path like ./. or ./.. is needed so that the result is reproducible and caches can be reused. Otherwise the store path will depend on the name of the parent directory which may cause unnecessary rebuilds.

craneLib.path ./.
# "/nix/store/wbhf6c7wiw9z53hsn487a8wswivwdw81-source"
craneLib.path ./checks/simple
# "/nix/store/s9scn97c86kqskf7yv5n2k85in5y5cmy-simple"

It is also possible to use as a drop in replacement for builtins.path:

craneLib.path {
  path = ./.;
  name = "asdf";
}
# "/nix/store/23zy3c68v789cg8sysgba0rbgbfcjfhn-asdf"

craneLib.registryFromDownloadUrl

registryFromDownloadUrl :: set -> set

Prepares a crate registry into a format that can be passed directly to appendCrateRegistries using the registry's download URL.

If the registry in question has a stable download URL (which either never changes, or it does so very infrequently), then registryFromDownloadUrl is a great and lightweight choice for including the registry. To get started, look up the config.json at the registry's root and copy the value of the dl entry.

If the registry's download endpoint changes more frequently and you would like to infer the configuration directly from a git revision, consider using registryFromGitIndex as an alternative.

If the registry needs a special way of accessing crate sources the fetchurlExtraArgs set can be used to influence the behavior of fetching the crate sources (e.g. by setting curlOptsList)

Required attributes

  • dl: the value of the dl entry in the registry's config.json file
  • indexUrl: an HTTP URL to the index

Optional attributes

  • fetchurlExtraArgs: a set of arguments which will be passed on to the fetchurl for each crate being sourced from this registry
craneLib.registryFromDownloadUrl {
  dl = "https://static.crates.io/crates";
  indexUrl = "https://github.com/rust-lang/crates.io-index";
}
# {
#   "registry+https://github.com/rust-lang/crates.io-index" = {
#     downloadUrl = "https://static.crates.io/crates/{crate}/{version}/download";
#     fetchurlExtraArgs = {};
#   };
# }

craneLib.registryFromGitIndex

registryFromGitIndex :: set -> set

Prepares a crate registry into a format that can be passed directly to appendCrateRegistries using a revision of the registry index to infer the download URL.

Note that the specified git revision does not need to track updates to the index itself as long as the pinned revision contains the most recent version of the config.json file. In other words, this commit revision only needs to be updated if the config.json file changes.

Also note that this approach means that the contents of the entire index at the specified revision will be added to the Nix store during evaluation time, and that IFD will need to be enabled. If this is unsatisfactory, consider using registryFromDownloadUrl as a simpler alternative.

If the registry needs a special way of accessing crate sources the fetchurlExtraArgs set can be used to influence the behavior of fetching the crate sources (e.g. by setting curlOptsList)

Required attributes

  • indexUrl: an HTTP URL to the index
  • rev: any git revision which contains the latest config.json definition

Optional attributes

  • fetchurlExtraArgs: a set of arguments which will be passed on to the fetchurl for each crate being sourced from this registry
craneLib.registryFromGitIndex {
  url = "https://github.com/Hirevo/alexandrie-index";
  rev = "90df25daf291d402d1ded8c32c23d5e1498c6725";
}
# {
#   "registry+https://github.com/Hirevo/alexandrie-index" = {
#     downloadUrl = "https://crates.polomack.eu/api/v1/crates/{crate}/{version}/download";
#     fetchurlExtraArgs = {};
#   };
# }

craneLib.urlForCargoPackage

urlForCargoPackage :: set -> set

Returns info pertaining to the URL for downloading a particular crate if the crate's registry is configured (an error will be thrown if it is not).

The result will contain two attributes:

  • url: A string representing the URL at which the crate can be fetched
  • fetchurlExtraArgs: A set of attributes specific to this registry which will be passed on to the fetchurl invocation.

Required input attributes

  • name: the name of the crate
  • source: the source key recorded in the Cargo.lock file
  • version: the version of the crate

craneLib.vendorCargoDeps

vendorCargoDeps :: set -> drv

Creates a derivation which will download all crates referenced by a Cargo.lock file, and prepare a vendored directory which cargo can use for subsequent builds without needing network access.

Each unique crate index will be vendored as its own subdirectory within the output of the derivation. A config.toml file will also be placed at the root of the output which will contain the necessary configurations to point cargo to the vendored directories (i.e. this configuration can be appended to the .cargo/config.toml definition of the project).

Input attributes

  • src: a directory which includes a Cargo.lock file at its root.
  • cargoLock: a path to a Cargo.lock file
  • cargoLockContents: the contents of a Cargo.lock file as a string
  • cargoLockParsed: the parsed contents of Cargo.lock as an attribute set

At least one of the above attributes must be specified, or an error will be raised during evaluation.

Optional attributes

  • outputHashes: a mapping of package-source to the sha256 of the (unpacked) download. Useful for supporting fully offline evaluations.
    • Default value: []

craneLib.vendorCargoRegistries

vendorCargoRegistries :: set -> set

Creates the derivations necessary to download all crates from all registries referenced by a Cargo.lock file, and prepare the vendored directories which cargo can use for subsequent builds without needing network access.

Input attributes

  • lockPackages: a list of all [[package]] entries found in the project's Cargo.lock file (parsed via builtins.fromTOML)

Optional attributes

  • cargoConfigs: a list of paths to all .cargo/config.toml files which may appear in the project. Ignored if registries is set.
    • Default value: []
  • registries: an attrset of registry names to their index URL. The default ("crates-io") registry need not be specified, as it will automatically be available, but it can be overridden if required.
    • Default value: if not specified, cargoConfigs will be used to identify any configured registries

Output attributes

  • config: the configuration entires needed to point cargo to the vendored crates. This is intended to be appended to $CARGO_HOME/config.toml verbatim
  • sources: an attribute set of all the newly created cargo sources' names to their location in the Nix store

craneLib.vendorGitDeps

vendorGitDeps :: set -> set

Creates the derivations necessary to download all crates from all git dependencies referenced by a Cargo.lock file, and prepare the vendored directories which cargo can use for subsequent builds without needing network access.

Input attributes

  • lockPackages: a list of all [[package]] entries found in the project's Cargo.lock file (parsed via builtins.fromTOML)

Optional attributes

  • outputHashes: a mapping of package-source to the sha256 of the (unpacked) download. Useful for supporting fully offline evaluations.
    • Default value: []

Output attributes

  • config: the configuration entires needed to point cargo to the vendored sources. This is intended to be appended to $CARGO_HOME/config.toml verbatim
  • sources: an attribute set of all the newly created cargo sources' names to their location in the Nix store

craneLib.vendorMultipleCargoDeps

vendorMultipleCargoDeps :: set -> drv

Creates a derivation which will download all crates referenced by several Cargo.lock files, and prepare a vendored directory which cargo can use for subsequent builds without needing network access. Duplicate packages listed in different Cargo.lock files will automatically be filtered out.

Each unique crate index will be vendored as its own subdirectory within the output of the derivation. A config.toml file will also be placed at the root of the output which will contain the necessary configurations to point cargo to the vendored directories (i.e. this configuration can be appended to the .cargo/config.toml definition of the project).

Optional attributes

  • cargoConfigs: a list of paths to all .cargo/config.toml files which may appear in the project. Ignored if registries is set.
    • Default value: []
  • cargoLockContentsList: a list of strings representing the contents of different Cargo.lock files to be included while vendoring. The strings will automatically be parsed during evaluation.
    • Default value: []
  • cargoLockList: a list of paths to different Cargo.lock files to be included while vendoring. The paths will automatically be read and parsed during evaluation.
    • Default value: []
  • cargoLockParsedList: a list of attrsets representing the parsed contents of different Cargo.lock files to be included while vendoring.
    • Default value: []
  • outputHashes: a mapping of package-source to the sha256 of the (unpacked) download. Useful for supporting fully offline evaluations.
    • Default value: []
  • registries: an attrset of registry names to their index URL. The default ("crates-io") registry need not be specified, as it will automatically be available, but it can be overridden if required.
    • Default value: if not specified, cargoConfigs will be used to identify any configured registries

craneLib.writeTOML

writeTOML :: String -> String -> drv

Takes a file name and an attribute set, converts the set to a TOML document and writes it to a file with the given name.

craneLib.writeTOML "foo.toml" { foo.bar = "baz"; }
# «derivation /nix/store/...-foo.toml.drv»

Hooks

craneLib.cargoHelperFunctionsHook

Defines helper functions for internal use. It is probably not a great idea to depend on these directly as their behavior can change at any time, but it is worth documenting them just in case:

  • Defines a cargo() function which will immediately invoke the cargo command found on the $PATH after echoing the exact arguments that were passed in. Useful for automatically logging all cargo invocations to the log.
  • Defines a cargoWithProfile() function which will invoke cargo with the provided arguments. If $CARGO_PROFILE is set, then --profile $CARGO_PROFILE will be injected into the cargo invocation
    • Note: a default value of $CARGO_PROFILE is set via configureCargoCommonVarsHook. You can set CARGO_PROFILE = "something" in your derivation to change which profile is used, or set CARGO_PROFILE = ""; to omit it altogether.

craneLib.configureCargoCommonVarsHook

Defines configureCargoCommonVars() which will set various common cargo-related variables, such as honoring the amount of parallelism dictated by Nix, disabling incremental artifacts, etc. More specifically:

  • CARGO_BUILD_INCREMENTAL is set to false if not already defined
  • CARGO_BUILD_JOBS is set to $NIX_BUILD_CORES if not already defined
  • CARGO_HOME is set to $PWD/.cargo-home if not already defined.
    • The directory that CARGO_HOME points to will be created
  • CARGO_PROFILE is set to release if not already defined.
    • Note that this is is used internally specify a cargo profile (e.g. cargo build --profile release) and not something natively understood by cargo.
  • RUST_TEST_THREADS is set to $NIX_BUILD_CORES if not already defined

Automatic behavior: runs as a post-patch hook

craneLib.configureCargoVendoredDepsHook

Defines configureCargoVendoredDeps() which will prepare cargo to use a directory of vendored crate sources. It takes two positional arguments:

  1. a path to the vendored sources
    • If not specified, the value of $cargoVendorDir will be used
    • If cargoVendorDir is not specified, an error will be raised
  2. a path to a cargo config file to modify
    • If not specified, the value of $CARGO_HOME/config.toml will be used
    • This cargo config file will be appended with a stanza which will instruct cargo to use the vendored sources (instead of downloading the sources directly) as follows:
      • If the vendored directory path contains a file named config.toml, then its contents will be appended to the specified cargo config path.
      • Otherwise the entire vendored directory path will be treated as if it only vendors the crates.io index and will be configured as such.

Automatic behavior: if cargoVendorDir is set, then configureCargoVendoredDeps "$cargoVendorDir" "$CARGO_HOME/config.toml" will be run as a pre configure hook.

craneLib.inheritCargoArtifactsHook

Defines inheritCargoArtifacts() which will pre-populate cargo's artifact directory using a previous derivation. It takes two positional arguments:

  1. a path to the previously prepared artifacts
    • If not specified, the value of $cargoArtifacts will be used
    • If cargoArtifacts is not specified, an error will be raised
    • If the specified path is a directory which contains a file called target.tar.zst, then that file will be used as specified below
    • If the specified path is a file (and not a directory) it is assumed that it contains a zstd compressed tarball and will be decompressed and unpacked into the specified cargo artifacts directory
    • If the specified path is a directory which contains another directory called target, then that directory will be used as specified below
    • If the specified path is a directory, its contents will be copied into the specified cargo artifacts directory
    • The previously prepared artifacts are expected to be a zstd compressed tarball
  2. the path to cargo's artifact directory, where the previously prepared artifacts should be unpacked
    • If not specified, the value of $CARGO_TARGET_DIR will be used
    • If CARGO_TARGET_DIR is not set, cargo's default target location (i.e. ./target) will be used.

Note that as an optimization, some dependency artifacts will be symlinked instead of (deeply) copied to $CARGO_TARGET_DIR. To disable this behavior set doNotLinkInheritedArtifacts, and all artifacts will be copied as plain, writable files.

Automatic behavior: if cargoArtifacts is set, then inheritCargoArtifacts "$cargoArtifacts" "$CARGO_TARGET_DIR" will be run as a post patch hook.

Required nativeBuildInputs: assumes zstd is available on the $PATH

craneLib.installCargoArtifactsHook

Defines compressAndInstallCargoArtifactsDir() which handles installing cargo's artifact directory to the derivation's output as a zstd compressed tarball. It takes two positional arguments:

  1. the installation directory for the output.
    • An error will be raised if not specified
    • Cargo's artifact directory will be compressed as a reproducible tarball with zstd compression. It will be written to this directory and named target.tar.zstd
  2. the path to cargo's artifact directory
    • An error will be raised if not specified

If $zstdCompressionExtraArgs is set, compressAndInstallCargoArtifactsDir() will pass its contents along to zstd when compressing artifacts.

Defines dedupAndInstallCargoArtifactsDir() which handles installing cargo's artifact directory to the derivation's output after deduplicating identical files against a directory of previously prepared cargo artifacts. It takes three positional arguments:

  1. the installation directory for the output.
    • An error will be raised if not specified
    • If the specified path is a directory which exists then the current cargo artifacts will be compared with the contents of said directory. Any files whose contents and paths match will be symbolically linked together to reduce the size of the data stored in the Nix store.
  2. the path to cargo's artifact directory
    • An error will be raised if not specified
  3. a path to the previously prepared cargo artifacts
    • An error will be raised if not specified
    • /dev/null can be specified here if there is no previous directory to deduplicate against

Defines prepareAndInstallCargoArtifactsDir() which handles installing cargo's artifact directory to the derivation's output. It takes three positional arguments:

  1. the installation directory for the output.
    • If not specified, the value of $out will be used
    • Cargo's artifact directory will be installed based on the installation mode selected below
  2. the path to cargo's artifact directory
    • If not specified, the value of $CARGO_TARGET_DIR will be used
    • If CARGO_TARGET_DIR is not set, cargo's default target location (i.e. ./target) will be used.
  3. the installation mode to apply
    • If specified, the value of $installCargoArtifactsMode will be used, otherwise, a default value of "use-zstd" will be used
    • If set to "use-symlink" then dedupAndInstallCargoArtifactsDir() will be used.
      • If $cargoArtifacts is defined and $cargoArtifacts/target is a valid directory, it will be used during file deduplication
    • If set to "use-zstd" then compressAndInstallCargoArtifactsDir() will be used.
    • Otherwise an error will be raised if the mode is not recognized

Automatic behavior: if doInstallCargoArtifacts is set to 1, then prepareAndInstallCargoArtifactsDir "$out" "$CARGO_TARGET_DIR" will be run as a post install hook.

Required nativeBuildInputs: assumes zstd is available on the $PATH

craneLib.installFromCargoBuildLogHook

Defines installFromCargoBuildLog() which will use a build log produced by cargo to find and install any binaries and libraries which have been built. It takes two positional arguments:

  1. a path to where artifacts should be installed
    • If not specified, the value of $out will be used
    • Binaries will be installed in a bin subdirectory
    • Libraries will be installed in a lib subdirectory
      • Note that only library targets with the staticlib and cdylib crate-types will be installed. Library targets with the rlib crate-type will be ignored
  2. a path to a JSON formatted build log written by cargo
    • If not specified, the value of $cargoBuildLog will be used
    • If cargoBuildLog is not set, an error will be raised
    • This log can be captured, for example, via cargo build --message-format json-render-diagnostics >cargo-build.json

Automatic behavior: none

Required nativeBuildInputs: assumes cargo and jq are available on the $PATH

craneLib.removeReferencesToVendoredSourcesHook

Defines removeReferencesToVendoredSources() which handles removing all references to vendored sources from the installed binaries, which ensures that nix does not consider the binaries as having a (runtime) dependency on the sources themselves. It takes two positional arguments:

  1. the installation directory for the output.
    • If not specified, the value of $out will be used
    • If out is not specified, an error will be raised
  2. a path to the vendored sources
    • If not specified, the value of $cargoVendorDir will be used
    • If cargoVendorDir is not specified, an error will be raised
    • Note: it is expected that this directory has the exact structure as would be produced by craneLib.vendorCargoDeps

Any patched binaries on aarch64-darwin will be signed. You can disable this functionality by setting doNotSign.

Automatic behavior: if cargoVendorDir is set and doNotRemoveReferencesToVendorDir is not set, then removeReferencesToVendoredSources "$out" "$cargoVendorDir" will be run as a post install hook.

craneLib.replaceCargoLockHook

Defines replaceCargoLock() which handles replacing or inserting a specified Cargo.lock file in the current directory. It takes one positional argument:

  1. a file which will be copied to Cargo.lock in the current directory
    • If not specified, the value of $cargoLock will be used
    • If $cargoLock is not set, an error will be raised

Automatic behavior: if cargoLock is set and doNotReplaceCargoLock is not set, then replaceCargoLock "$cargoLock" will be run as a pre patch hook.

Troubleshooting and Frequently Asked Questions

This chapter captures a list of common questions or issues and how to resolve them. If you happen to run into an issue that is not documented here, please consider submitting a pull request!

The crane library can be instantiated with a specific version of nixpkgs as follows. For more information, see the API docs for mkLib.

# Instantiating for a specific `system`
crane.mkLib (import nixpkgs {
  system = "armv7l-linux";
})
# Instantiating for cross compiling
crane.mkLib (import nixpkgs {
  localSystem = "x86_64-linux";
  crossSystem = "aarch64-linux";
})

The crane library can also be instantiated with a particular rust toolchain:

# For example, using rust-overlay
let
  system = "x86_64-linux";
  pkgs = import nixpkgs {
    inherit system;
    overlays = [ (import rust-overlay) ];
  };

  rustToolchain = pkgs.rust-bin.stable.latest.default.override {
    targets = [ "wasm32-wasi" ];
  };
in
(crane.mkLib pkgs).overrideToolchain rustToolchain

Finally, specific inputs can be overridden for the entire library via the overrideScope API as follows. For more information, see the API docs for mkLib/overrideToolchain, or checkout the custom-toolchain example.

crane.lib.${system}.overrideScope (final: prev: {
  cargo-tarpaulin = myCustomCargoTarpaulinVersion;
})

Nix is complaining about IFD (import from derivation)

If a derivation's pname and version attributes are not explicitly set, crane will inspect the project's Cargo.toml file to set them as a convenience to avoid duplicating that information by hand. This works well when the source is a local path, but can cause issues if the source is being fetched remotely, or flakes are not being used (since flakes have IFD enabled on by default).

One easy workaround for this issue (besides enabling the allow-import-from-derivation option in Nix) is to explicitly set { pname = "..."; version = "..."; } in the derivation.

You'll know you've run into this issue if you see error messages along the lines of:

  • cannot build '/nix/store/...-source.drv' during evaluation because the option 'allow-import-from-derivation' is disabled
  • a 'aarch64-darwin' with features {} is required to build '/nix/store/...', but I am a 'x86_64-linux' with features {}

I'm getting rebuilds all of the time, especially when I change flake.nix

Nix will rebuild a derivation if any of its inputs change, which includes any file contained by the source that is passed in. For example, if the build expression specifies src = ./.; then the crate will be rebuilt when any file changes (including "unrelated" changes to flake.nix)!

There are two main ways to avoid unnecessary builds:

  1. Use a source cleaning function which can omit any files know to not be needed while building the crate (for example, all *.nix sources, flake.lock, and so on). For example cleanCargoSource (see [API docs] for details) implements some good defaults for ignoring irrelevant files which are not needed by cargo.
  2. Another option is to put the crate's source files into its own subdirectory (e.g. ./mycrate) and then set the build expression's source to that subdirectory (e.g. src = ./mycrate;). Then, changes to files outside of that directory will be ignored and will not cause a rebuild

In certain (especially non-trivial) crane-based workflows, it's possible that a change to a given file might trigger rebuilds of certain seemingly unrelated derivations. This is most often caused by a subtle bug introducing undesired derivation inputs.

Debugging with nix-diff

An efficient way to debug such problems is to use nix-diff to compare the derivation build plans:

# nix-diff does not support direct flake-urls so we'll need
# to get the actual derivation name
nix show-derivation .#affectedOutput | nix run nixpkgs#jq -- -r 'keys[0]' > before_drv
echo >> ./file/triggering/rebuild # cause a rebuild
nix show-derivation .#affectedOutput | nix run nixpkgs#jq -- -r 'keys[0]' > after_drv
nix run nixpkgs#nix-diff "$(cat before_drv)" "$(cat after_drv)"

Debugging with just nix

Another way to debug such problems is to use nix derivation show -r to compare the derivation build plans:

nix derivation show -r .#affectedOutput > before
echo >> ./file/triggering/rebuild # cause a rebuild
nix derivation show -r .#affectedOutput > after
diff -u before after

The difference in the highest-level derivation should point to a direct cause of the rebuild (possibly a different lower-level input derivation which can be compared recursively).

I've used a source filter but cargo is still rebuilding all dependencies from scratch!

Another source of artifact invalidation is if

  • A different set of dependency crates are being built between derivations
let
  src = ...;
in
craneLib.buildPackage {
  inherit src;

  cargoArtifacts = craneLib.buildDepsOnly {
    inherit src;
    cargoExtraArgs = "-p foo"; # Only build the `foo` crate
  };

  # Oops, we're only building the `bar` crate now
  # any dependency crates used by `bar` but not by `foo`
  # will get built from scratch!
  cargoExtraArgs = "-p bar";
}
  • Another reason could be using different feature flags between derivations, which result in setting different feature flags for dependency crates themselves and causing a rebuild
let
  src = ...;
in
craneLib.buildPackage {
  inherit src;

  cargoArtifacts = craneLib.buildDepsOnly {
    inherit src;
    cargoExtraArgs = "--no-default-features"; # Don't use any workspace features
  };

  # Oops, we're now building with an additional downstream feature flag which
  # needs to build more crates which we do not have cached!
  cargoExtraArgs = "--features feature-which-enables-downstream-feature";
}

If in doubt, double check that the same set of -p/--package and --features/--no-default-features/--all-features flags are used between all buildDepsOnly/cargoBuild/cargoClippy/buildPackage derivations.

Mixing [package] and [workspace] definitions in the top-level Cargo.toml

Another potential pitfall is defining both [package] and [workspace] in the project's top-level Cargo.toml file. Although cargo allows both to be defined, doing so results in cargo only operating on that package by default (unless the --workspace flag is passed in).

Any subsequent derivations which attempt to build with -p another-crate might not have their dependencies fully cached. Our recommendation is to only define [package] in the top-level Cargo.toml if the workspace contains a single crate; otherwise only [workspace] should be defined.

Dependencies being rebuilt even with proper source filtering applied

If the dependency crates are being rebuilt even after proper source filtering has been applied (i.e. the crate-depsOnly derivation is NOT being rebuilt) check that the same Rust/Cargo toolchain is being used when building artifacts and vendoring crate sources.

The crate artifacts can only be used for the same compiler version, so if cargo sees artifacts for the wrong toolchain it will rebuild everything from scratch.

Note that each instance of crane tied to a single Rust toolchain (by default the one available in nixpkgs, but this can be overridden by the caller). If you are using multiple craneLib instantiations and you see this occurring, double check that they aren't being created with a different toolchain (especially if cross-compilation is being used for the project).

Constantly rebuilding proc-macro dependencies dev mode

A regression was introduced sometime around Rust 1.71.1 which changed how debuginfo flags are passed to proc-macro crates when using a dev profile.

If you are building with a dev profile (i.e. not using release builds), you may want to set the following in .cargo/config.toml:

[profile.dev.build-override]
debug = false

I see the pyo3 crate constantly rebuilding

The pyo3 crate uses checks $PYO3_PYTHON for a path to the python binary it should use during the build. If this environment variable is not set, pyo3 will look for whatever version of python is on the $PATH, which unfortunately results in the crate being rebuilt when $PATH changes (i.e. whenever the cargo artifacts are used in a derivation which may have different build inputs).

The way to remedy this is to explicitly set PYO3_PYTHON to point to the version of python that will be used by the derivation:

let
  chosenPython = pkgs.python3;
in
craneLib.buildPackage {
  env.PYO3_PYTHON = "${chosenPython}/bin/python";
  nativeBuildInputs = [
    chosenPython
  ];

  # etc...
}

I see the bindgen crate constantly rebuilding

If you are using rustPlatform.bindgenHook it is worth noting that it will propagate NIX_CFLAGS_COMPILE via BINDGEN_EXTRA_CLANG_ARGS.

In order to support reproducible builds, this build hook will add -frandom-seed=... to NIX_CFLAGS_COMPILE based on the current derivation's hash.

Since dependencies are built in a separate derivation as the main package, each derivation essentially gets a different value for -frandom-seed. The bindgen crate will observe this change and rebuild itself.

A workaround for this is to set NIX_OUTPATH_USED_AS_RANDOM_SEED to any arbitrary 10 character string for all derivations which share artifacts together.

buildPackage {
  NIX_OUTPATH_USED_AS_RANDOM_SEED = "aaaaaaaaaa";
  # other attributes omitted
}

I'm trying to build another cargo project from source which has no lock file

First consider if there is a release of this project available with a lock file as it may be simpler and more consistent to use the exact dependencies published by the project itself. Projects published on crates.io always come with a lock file and nixpkgs has a fetchCrate fetcher which pulls straight from crates.io.

If that is not an option, the next best thing is to generate your own Cargo.lock file and pass it in as an override by setting cargoLock = ./path/to/Cargo.lock. If you are calling buildDepsOnly or vendorCargoDeps directly the value must be passed there; otherwise you can pass it into buildPackage or cargoBuild and it will automatically passed through.

Note that the Cargo.lock file must be accessible at evaluation time for the dependency vendoring to work, meaning the file cannot be generated within the same derivation that builds the project. It may come from another derivation, but it may require enabling IFD if flakes are not used.

I need to patch Cargo.lock but when I do the build fails

Dependency crates are vendored by reading Cargo.lock at evaluation time and not at build time. Thus using patches = [ ./patch-which-updates-lockfile.patch ]; may result in a situation where any new crates introduced by the patch cannot be found by cargo.

It is possible to work around this limitation by patching Cargo.lock in a stand-alone derivation and passing that result to vendorCargoDeps before building the rest of the workspace.

let
  patchedCargoLock = src = pkgs.stdenv.mkDerivation {
    src = ./path/to/Cargo.lock;
    patches = [
      ./update-cargo-lock.patch
    ];
    installPhase = ''
      runHook preInstall
      mkdir -p $out
      cp Cargo.lock $out
      runHook postInstall
    '';
  };
in
craneLib.buildPackage {
  cargoVendorDir = craneLib.vendorCargoDeps {
    src = patchedCargoLock;
  };

  src = craneLib.cleanCargoSource (craneLib.path ./.);

  patches = [
    ./update-cargo-lock.patch
    ./some-other.patch
  ];
}

How can I build only a subset of a given cargo workspace?

By default, cargo will build the crate at the current directory when invoked; if the current directory holds a workspace, cargo will then build all crates within that workspace.

Sometimes it can be useful to only build a subset of a given workspace (e.g. only specific binaries are needed, or some crates cannot be built for certain platforms, etc.), and cargo can be instructed to do so.

Notably, it is possible to set:

  • cargoExtraArgs = "-p foo -p bar"; to only build the foo and bar crates only, but nothing else in the workspace
  • cargoExtraArgs = "--bin baz"; to only build the baz binary (from whatever crate defines it)
  • cargoExtraArgs = "--workspace --exclude qux"; to build the entire cargo workspace except for the qux crate.

Consider setting pname = "NAME_OF_THE_EXECUTABLE"; when building a single executable from the workspace. Having the name of the package match the executable name will allow the result to easily run via nix run without further configuration.

I'm having trouble building a project which uses include_str!

Double check if the source passed into the derivation is being cleaned or filtered in anyway. Using craneLib.cleanCargoSource (or craneLib.filterCargoSources directly) will omit any non-cargo and non-rust files before trying to build the derivation. Thus if the project is trying to use include_str!, include_bytes!, or any other attempt at accessing such a file you may need to tweak the source filter to ensure the files are included.

Check out the source filtering section for more info!

Dealing with sandbox-unfriendly build scripts

In general, most build scripts used by popular Rust projects are pretty good at only attempting to write to cargo's output directory. But every once in a while it is possible to find a build script somewhere deep in the dependency tree which assumes it can happily write to any directory it wants to (i.e. wherever its own sources happen to be present). For build scripts like these the best long term approach is almost always to fix them upstream; cargo's own documentation also warns against this:

In general, build scripts should not modify any files outside of OUT_DIR. It may seem fine on the first blush, but it does cause problems when you use such crate as a dependency, because there's an implicit invariant that sources in .cargo/registry should be immutable. cargo won't allow such scripts when packaging.

As a dire last resort it is possible to copy all vendored sources out of the (read-only) Nix store and into a writable directory. Keep in mind that doing so requires recursively copying all sources of all crates the project depends on during every single build; it comes with a performance and energy cost, and as such it is not recommended.

# You have been warned
buildPackage {
  # other attributes omitted
  postPatch = ''
    mkdir -p "$TMPDIR/nix-vendor"
    cp -Lr "$cargoVendorDir" -T "$TMPDIR/nix-vendor"
    sed -i "s|$cargoVendorDir|$TMPDIR/nix-vendor/|g" "$TMPDIR/nix-vendor/config.toml"
    chmod -R +w "$TMPDIR/nix-vendor"
    cargoVendorDir="$TMPDIR/nix-vendor"
  '';
}

Cargo workspace root (Cargo.toml) is not at the root of the derivation's source

Most cargo projects have their Cargo.toml at the root of the source, but it's still possible to build a project where the Cargo.toml file is nested in a deeper directory:

# Assuming that we have the following directory structure:
# ./flake.nix
# ./flake.lock
# ./nested
# ./nested/Cargo.toml
# ./nested/Cargo.lock
# ./nested/src/*.rs
craneLib.buildPackage {
 src = myLib.cleanCargoSource (craneLib.path ./.);
 cargoLock = ./nested/Cargo.lock;
 cargoToml = ./nested/Cargo.toml;
 # Use a postUnpack hook to jump into our nested directory. This will work
 # regardless of what the unpacked source is named (i.e. will avoid hashes
 # when using the root path of a flake).
 #
 # The unpackPhase sets `$sourceRoot` to the directory that was unpacked
 # but unfortunately `postUnpack` runs before the directory is actually
 # changed so we'll do two things:
 # 1. Jump into the directory we want (replace `nested` with your directory)
 # 2. Overwrite the variable so when the default build scripts run they don't
 # end up changing to a different directory again
 postUnpack = ''
   cd $sourceRoot/nested
   sourceRoot="."
 '';
}

found invalid metadata files for crate errors

This error can occur when mixing components from two different Rust toolchains, for example, using clippy with artifacts produced from a different cargo version. Check the configuration for specifying the exact Rust toolchain to be used in the build:

let
  rustToolchain = ...;
in
# Incorrect usage, missing `clippy` override!
#(crane.mkLib pkgs).overrideScope (final: prev: {
#  rustc = rustToolchain;
#  cargo = rustToolchain;
#  rustfmt = rustToolchain;
#});

# Correct usage (`overrideToolchain` handles the details for us)
(crane.mkLib pkgs).overrideToolchain rustToolchain

Advanced Techniques

This chapter contains various "advanced" techniques for configuring and modifying the behaviors of crane.

Most projects will likely not need to apply these patterns as they may require extensive familiarity with Nix as well as crane internals.

Overriding function behavior

At it's core, crane is instantiated via pkgs.lib.newScope which allows any internal definition to be changed or replaced via .overrideScope (which behaves very much like applying overlays to nixpkgs). Although this mechanism is incredibly powerful, care should be taken to avoid creating confusing or brittle integrations built on undocumented details.

Note that crane's stability guarantees (with respect to semantic versioning) only apply to what has been documented at the API level. For example, buildPackage is documented to delegate to mkCargoDerivation, so any changes or overrides to mkCargoDerivation's behavior will be observed by buildPackage. Other non-documented internal details, however, may change at any time, so take care when reaching this deep into the internals.

Here is an example:

let
  craneLib = (inputs.crane.mkLib pkgs).overrideScope (final: prev: {
    # We override the behavior of `mkCargoDerivation` by adding a wrapper which
    # will set a default value of `CARGO_PROFILE` when not set by the caller.
    # This change will automatically be propagated to any other functions built
    # on top of it (like `buildPackage`, `cargoBuild`, etc.)
    mkCargoDerivation = args: prev.mkCargoDerivation ({
      CARGO_PROFILE = "bench"; # E.g. always build in benchmark mode unless overridden
    } // args);
  });
in
{
    # Build two different workspaces with the modified behavior above

    foo = craneLib.buildPackage {
      src = craneLib.cleanCargoSource (craneLib.path ./foo);
    };

    bar = craneLib.buildPackage {
      src = craneLib.cleanCargoSource (craneLib.path ./bar);
    };
}