Star 历史趋势
数据来源: GitHub API · 生成自 Stargazers.cn
README.md

cargo-nix-plugin

A Nix plugin that resolves Cargo workspaces natively, replacing the generated Cargo.nix file from crate2nix with a single builtins.resolveCargoWorkspace primop.

What It Does

  • Reads Cargo.lock and the sparse registry index directly, so there is no cargo binary at eval time (you can also feed it pre-generated cargo metadata JSON)
  • Evaluates cfg() target expressions for your platform during resolution
  • Produces an attrset that plugs into buildRustCrate
  • No more crate2nix generate step or checked-in 50K-100K line Cargo.nix

Install

Add the plugin to your Nix configuration:

# nix.conf or via --option — point at the directory so the right
# extension (.so/.dylib) is picked up automatically
plugin-files = /path/to/cargo-nix-plugin/lib/nix/plugins

Or use the flake output:

{
  inputs.cargo-nix-plugin.url = "github:anthropics/cargo-nix-plugin";
}

Usage

Default (lockfile resolve)

Point at your workspace root:

cargoNix = cargo-nix-plugin.lib {
  inherit pkgs;
  src = ./.;  # must contain Cargo.toml + Cargo.lock
};

The plugin reads Cargo.lock plus the sparse registry index directly — no cargo binary, no crate sources at eval time. On first use it fetches each crate's index entry (a few hundred bytes) into $CARGO_HOME and reuses it on later runs.

If you already redirect cargo to a mirror (CARGO_REGISTRIES_CRATES_IO_INDEX or [source.crates-io] replace-with in .cargo/config.toml), the resolver picks that up too:

# .cargo/config.toml — used by both cargo and the plugin
[source.crates-io]
replace-with = "mirror"
[source.mirror]
registry = "sparse+https://artifactory.example/api/cargo/crates/index/"

If every index lookup fails (e.g. egress to index.crates.io is blocked and no mirror is configured), evaluation fails rather than silently producing derivations with missing features.

Explicit metadata

You can also generate cargo's resolution up front and pass it in:

cargo metadata --format-version 1 --locked > metadata.json

Then:

cargoNix = cargo-nix-plugin.lib {
  inherit pkgs;
  metadata = builtins.readFile ./metadata.json;
  cargoLock = builtins.readFile ./Cargo.lock;
  src = ./.;
};

Or use the helper:

nix run .#generate-metadata -- > metadata.json

Pre-fetching the index cache

If the machine doing the evaluation can't reach any index, run cargo-nix-prefetch on one that can. It fills $CARGO_HOME and follows the same mirror configuration as the plugin:

nix run .#cargo-nix-prefetch -- --manifest-path ./Cargo.toml
nix run .#cargo-nix-prefetch -- --manifest-path ./Cargo.toml --check   # verify

Use --output DIR to write to a separate directory instead of $CARGO_HOME and point the resolver at it:

nix run .#cargo-nix-prefetch -- --manifest-path ./Cargo.toml --output ./.cargo-index
cargoNix = cargo-nix-plugin.lib {
  inherit pkgs;
  src = ./.;
  cargoHome = ./.cargo-index;   # pre-warmed by cargo-nix-prefetch
};

You can also wrap this in a fixed-output derivation if you'd rather pin the cache by hash than check it in.

Git dependencies

git+… entries in Cargo.lock are fetched at eval time with builtins.fetchGit { url; rev; allRefs = true; submodules = true; } so the resolver can read each crate's Cargo.toml (the registry index has no record of them). Submodules are pulled to match cargo, which always recurses them for git deps. When the upstream repo is a Cargo workspace, the resolver locates the right member and passes its sub-directory to buildRustCrate as workspace_member.

Override gitSources when fetchGit can't reach the repo (private auth, vendored fixture), to pin a narHash/use a FOD fetcher, or to skip submodules for a repo that doesn't need them:

cargoNix = cargo-nix-plugin.lib {
  inherit pkgs;
  src = ./.;
  gitSources = {
    # key = "${url}#${rev}" with git+ and ?query stripped — exactly what
    # appears in Cargo.lock after `git+` and before `?`, plus `#REV`.
    "https://github.com/Byron/gitoxide#abcdef…" = pkgs.fetchgit {
      url = "git@github.com:Byron/gitoxide";
      rev = "abcdef…";
      hash = "sha256-…";
    };
  };
};

A git+ source without a pinned #rev is rejected; Cargo.lock always pins one.

Debug logging

The resolver is quiet by default. Set CARGO_NIX_DEBUG=1 to get informational logs (mirror selection, index prefetch timings, per-crate retries) on stderr. Warnings and errors are always printed.

Example

The plugin must be loaded by the same Nix version it was compiled against (see Compatibility). Evaluate with the plugin loaded via --option:

PLUGIN=$(nix build .#cargo-nix-plugin --print-out-paths)
NIX=$(nix build nixpkgs#nixVersions.nix_2_34 --print-out-paths | grep -v man)

$NIX/bin/nix-instantiate --eval \
  --option plugin-files "$PLUGIN/lib/nix/plugins" \
  -E '(import ./lib { pkgs = import <nixpkgs> {}; src = ./.; }).workspaceMembers'

Or permanently in nix.conf / ~/.config/nix/nix.conf (only if your system Nix matches the plugin's build version):

plugin-files = /path/to/cargo-nix-plugin/lib/nix/plugins

flake.nix

{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
    cargo-nix-plugin.url = "github:anthropics/cargo-nix-plugin";
  };

  outputs = { self, nixpkgs, cargo-nix-plugin }:
    let
      pkgs = import nixpkgs { system = "x86_64-linux"; };

      cargoNix = cargo-nix-plugin.lib {
        inherit pkgs;
        src = ./.;
      };
    in {
      packages.x86_64-linux.default = cargoNix.rootCrate.build;
    };
}

Clippy

The wrapper provides cached clippy checks via cargoNix.clippy. Dependencies are compiled once with rustc and cached in the Nix store; only workspace members are re-checked with clippy-driver. This means running clippy on a large workspace is as fast as compiling just your local crates.

Pass clippyAllFeatures = true (lockfile-resolve mode only) to lint with every workspace feature enabled, like cargo clippy --all-features, so feature-gated code is checked too.

cargoNix = cargo-nix-plugin.lib {
  inherit pkgs;
  src = ./.;
};

# Check all workspace members
cargoNix.clippy.allWorkspaceMembers

# Check a single member
cargoNix.clippy.workspaceMembers.my-crate.build

# Collect cached JSON diagnostics from all workspace members
cargoNix.clippy.report

To fail on warnings, pass extra clippy flags:

cargoNix = cargo-nix-plugin.lib {
  inherit pkgs;
  src = ./.;
  clippyArgs = [ "-D" "warnings" ];
};

Workspace members are built with --cap-lints forbid (no effective cap, matching cargo). Pass clippyCapLints = "warn" to demote all findings to warnings instead.

Each clippy member build retains its raw rustc JSON diagnostics at cargoNix.clippy.workspaceMembers.<name>.report. The aggregate cargoNix.clippy.report derivation collects those cached diagnostics as one <name>.jsonl file per workspace member, so CI can publish annotations without re-running clippy. The build log still shows the rendered diagnostics; only the on-disk report is JSON.

The report is an output of the clippy build, so it only exists when that build succeeds. If you need both the JSON and a failing CI gate, leave clippyArgs without -D warnings and build cargoNix.clippy.reportCheck instead — it depends on clippy.report and fails if any member has warning- or error-level findings.

How clippy caching works

clippy-driver is a drop-in replacement for rustc — it accepts identical command-line flags and produces the same artifacts, but also runs lint passes. The wrapper creates a small shim package where bin/rustc calls clippy-driver, and passes it as the rust override to buildRustCrate for workspace members only. Non-workspace dependencies use the normal rustc and resolve to the exact same Nix store paths as a regular build — no redundant compilation. With clippyAllFeatures = true, dependencies whose feature set grows are rebuilt once for the clippy path; everything else is still shared with the regular build.

Tests

checks.x86_64-linux.my-crate-tests =
  cargoNix.workspaceMembers.my-crate.runTests;

runTests compiles lib unit tests and integration tests under tests/ (with [dev-dependencies] wired in) and runs them sequentially. The regular .build derivation is unchanged. Integration tests can spawn the crate's binaries via env!("CARGO_BIN_EXE_<name>") exactly as under cargo test.

Tests that shell out to external tools at runtime declare them via nativeCheckInputs in crateOverrides; runTests puts them on PATH:

cargoNix = cargo-nix-plugin.lib {
  inherit pkgs;
  src = ./.;
  crateOverrides = pkgs.defaultCrateOverrides // {
    my-crate = _: { nativeCheckInputs = [ pkgs.sqlite ]; };
  };
};

The runner sets RUST_BACKTRACE=1 and points CARGO_TARGET_TMPDIR at a fresh temp dir. If you need different behaviour (test filters, --nocapture, a custom harness), the compiled artefacts are at .buildTests$out/tests/* are the test executables, $out/bin/* the real binaries — and runTests.passthru.testsDrv points there too.

Known limitations: doctests are not built, per-[[bin]] unit tests are not compiled, and tests under examples/ / benches/ are not discovered.

How It Works

  1. Nix plugin: Adds a builtins.resolveCargoWorkspace primop to Nix. When you call cargo-nix-plugin.lib { ... }, this primop resolves your entire Cargo workspace — dependencies, features, platform-specific conditionals — and returns the crate graph as a Nix attrset. In the default mode it reads Cargo.lock and the sparse registry index directly; in explicit mode it parses pre-provided cargo metadata JSON.

  2. Nix wrapper: Takes the resolved crate graph and builds each crate with buildRustCrate, wiring up dependencies automatically. Supports proc-macro cross-compilation, crate overrides, and the standard workspaceMembers/rootCrate interface.

Target Platform

The plugin accepts a target description attrset:

target = {
  name = "x86_64-unknown-linux-gnu";
  os = "linux"; arch = "x86_64"; vendor = "unknown"; env = "gnu";
  family = ["unix"]; pointer_width = "64"; endian = "little";
  unix = true; windows = false;
};

The wrapper auto-detects this from stdenv.hostPlatform.

Custom cfgs

To set custom cfgs during [target.'cfg(...)'] dependency resolution (equivalent to RUSTFLAGS="--cfg foo" at cargo-metadata time), pass extraCfgs:

extraCfgs = [ "my_platform" ];

Pair with passing the same --cfg via rustc opts so #[cfg(foo)] in source compiles too — extraCfgs only affects dependency resolution.

Compatibility

  • Nix: The plugin must be loaded by the same Nix version it was compiled against — the Nix plugin ABI is not stable across versions. If you see errors like expected a set but found a set, you have a version mismatch. .#cargo-nix-plugin (the default) is built against Nix 2.34, so use Nix 2.34.x to evaluate:

    # Get the matching nix
    NIX=$(nix build nixpkgs#nixVersions.nix_2_34 --print-out-paths | grep -v man)
    PLUGIN=$(nix build .#cargo-nix-plugin --print-out-paths)
    
    $NIX/bin/nix build .#myPackage \
      --option plugin-files "$PLUGIN/lib/nix/plugins"

    For other Nix versions, build the matching per-version attribute, e.g. .#cargo-nix-plugin-nix_2_31 to pair with nixVersions.nix_2_31. The flake's nixVersions set (in flake.nix) lists what's currently built; Nix >= 2.30 is required.

  • Platforms: x86_64-linux, aarch64-linux, and aarch64-darwin. Cross-compilation to other target platforms is supported.

  • API level: lib/ checks that the loaded plugin speaks the same contract version before resolving and warns on mismatch (e.g. when the plugin baked into your Nix lags the lib/ checkout). The wrapper result exposes both sides so you can turn that into a hard failure:

    let cargoNix = import ./lib { inherit pkgs; src = ./.; }; in
    assert cargoNix.apiLevel == cargoNix.resolverApiLevel;
    cargoNix.workspaceMembers

    apiLevel is what this lib/ speaks; resolverApiLevel is what the loaded plugin reports (0 if the plugin predates the check).

  • buildRustCrate: Compatible with nixpkgs buildRustCrate and defaultCrateOverrides

Status

Maintained by Anthropic. Provided AS IS without warranty (see LICENSE). We triage issues and review pull requests but do not commit to fixing every bug or accepting every feature request. For security issues, see SECURITY.md.

License

Apache License 2.0. See LICENSE.

关于 About

A Nix plugin that resolves Cargo workspaces natively and allows for crate level builds

语言 Languages

Rust61.5%
Nix36.2%
Python0.9%
C++0.9%
CMake0.4%

提交活跃度 Commit Activity

代码提交热力图
过去 52 周的开发活跃度
24
Total Commits
峰值: 9次/周
Less
More

核心贡献者 Contributors