native modules: build each crate separately with crate2nix - #3861
Conversation
Cargo.nix gives every crate its own derivation, so editing one module does not rebuild the others and each crate caches on its own. Measured: a cold ray_tracing build is 11m43s, and mls_planner right after is 21.8s. It also removes the aggregate cargoHash, which had to be re-pasted by hand whenever Cargo.lock moved -- including from main, which breaks branches that never touched rust, because CI builds the merge commit. The replacement failure mode is a stale generated file, which the new cargo-nix-current job prints as a diff instead of a number to re-paste. Regenerate with bin/regen-cargo-nix. The generated pair is excluded from the byte-rewriting pre-commit hooks so it stays reproducible, and Cargo.nix is exempted from the large-files check -- LFS would be wrong for a file CI has to diff as text.
Adding a module is now a deps.nix and nothing else, so the examples can be real build targets instead of a workspace member nothing referenced. They build in 42s here because every crate under them is already a store path. Doubles as the regression test for that claim: if adding a module ever starts requiring a flake.nix edit again, this file is the one that stops building.
Its keys are scoped to a git dependency's branch and carry no rev, so a committed copy silently goes stale the second that branch moves, pinning a hash that no longer matches the revision Cargo.lock resolves to. That is the exact failure this whole change set exists to remove. It is also redundant: it is only crate2nix's prefetch cache, and the hashes are already inlined in Cargo.nix. Regenerating with the file absent produces a byte-identical Cargo.nix, so nothing in the repo depends on it. Dropping it also retires two problems rather than papering over them: it no longer needs a pre-commit exclusion (pretty-format-json was rewriting it on every commit, which had already introduced a trailing newline the generator never emits), and it no longer needs an editorconfig exception for that missing final newline.
crate2nix shells out to `cargo metadata`, so the generated file depends on whichever cargo is on PATH. The generator itself was pinned by flake.lock but cargo was not: locally that was a rustup 1.98.0, while the toolchain this repo pins for its clippy and fmt hooks is 1.94.0, and CI would have used whatever the runner image ships. The two happen to agree today -- checked by regenerating under 1.94.0 and getting a byte-identical Cargo.nix -- so this fixes a latent problem rather than a live one. Worth doing anyway: the failure it prevents is a diff that only appears on the machine with the odd cargo, which is the hardest kind to read when cargo-nix-current is what reports it.
I had commented it as crate2nix's per-crate override hook. It is not: the `crateConfig` argument Cargo.nix defaults to this path is declared and then never read anywhere in the generated file. Overrides added here would have silently done nothing, which is a worse trap than having no file at all. The file still has to exist -- `bin/build-native-modules --inputs-hash` walks nix path literals textually, cannot evaluate the `builtins.pathExists` guard around this one, and exits 1 with "reference './crate-config.nix' does not exist". Verified by moving the file away. Kept an empty file rather than teaching that parser about conditional paths, since a parser that learns to skip guarded references can also skip one that matters.
Greptile SummaryThis change moves Rust native-module builds to generated per-crate Nix derivations, exposes dedicated flake outputs for module executables, and adds a freshness check for generated Cargo metadata. It also updates native-module launch configuration, makes native LCM transport honor The LCM URL parsing behavior was exercised with compiled tests covering valid UDP multicast URLs, optional TTL values, and malformed URL fallback behavior. The MLS morphology implementation was compared against direct L1 dilation and erosion across exhaustive, randomized, and regression geometry; the compared behavior matched. T-Rex validation blocked
Confidence Score: 5/5No confirmed defect blocks merging. No review findings remain after the focused LCM parsing and surface-closing failure paths were exercised and contradicted the predicted regressions. The unavailable Nix and Python dependency limited coverage of the output-link launch path but did not demonstrate a defect. Files Needing Attention: The root-flake launch path in
What T-Rex did
Reviews (1): Last reviewed commit: "Describe crate-config.nix as the placeho..." | Re-trigger Greptile |
Codecov Report✅ All modified and coverable lines are covered by tests. @@ Coverage Diff @@
## jeff/fix/native_build_cargo_path #3861 +/- ##
====================================================================
+ Coverage 77.60% 77.66% +0.06%
====================================================================
Files 1317 1323 +6
Lines 124792 125487 +695
Branches 10868 10931 +63
====================================================================
+ Hits 96841 97457 +616
- Misses 24816 24885 +69
- Partials 3135 3145 +10
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 11 files with indirect coverage changes 🚀 New features to boost your workflow:
|
Stacked on #3770
jeff/fix/native_build_cargo_path.Replaces the single workspace
cargoHashwith crate2nix.Cargo.nixisgenerated from
Cargo.lockand gives one derivation per crate, so each crategets its own store path and is cached individually. Previously any
Cargo.lockchange invalidated the one aggregate hash and rebuilt the whole workspace, and
a lock bump on
mainbroke the hash on branches that never touched rust.Side effect: this also drops the
importCargoLockdownload path, which returns403 for
crates.io/api/v1/crates/<name>/<version>/download. crate2nix fetchesstatic.crates.io, which does not.Adding a native module no longer touches
flake.nix.examples/native-modules/deps.nixis added here as the case that proves it:
crate-hashes.jsonis no longer committed. Its keys name a git dependency'sbranch with no rev, so a committed copy goes stale as soon as that branch moves;
the hashes are already inlined in
Cargo.nix.Regenerate after any
Cargo.lockor memberCargo.tomlchange:The
cargo-nix-currentjob fails if the committedCargo.nixdoes not match.Build times measured locally:
ray_tracing, coldmls_planner, immediately afterexamples, brand-new module