buildRustCrate: add devDependencies parameter for buildTests

Cargo links [dev-dependencies] only when building test or bench
targets. buildRustCrate models normal deps (`dependencies`) and
build-script deps (`buildDependencies`) but has no equivalent: callers
that set `buildTests = true` for a crate with dev-deps must compute the
dev-dep set themselves and splice it into `dependencies` via .override.

Add a `devDependencies` parameter that is appended to the linked
dependency list only when `buildTests = true`. The append happens at the
single point where `dependencies` is normalized, so it threads through
to the --extern flags, the deps directory in configurePhase, the
metadata hash, and propagated build inputs without further changes.

When `buildTests = false` the parameter is dropped, so existing lib/bin
derivations are byte-identical to before — no rebuilds for users that
don't pass it.

Includes a test case in build-rust-crate/test that fails without the
change (`can't find crate for dev_dep`).
This commit is contained in:
Bernardo Meurer
2026-05-19 10:16:51 -04:00
parent 8cd8ee769a
commit a6fc69fe40
2 changed files with 35 additions and 1 deletions
@@ -198,6 +198,13 @@ lib.makeOverridable
# Rust build dependencies, i.e. other libraries that were built
# with buildRustCrate and are used by a build script.
buildDependencies,
# Rust dev-dependencies, i.e. other libraries that were built
# with buildRustCrate and are linked only when `buildTests = true`.
# Mirrors Cargo's `[dev-dependencies]`: ignored for the regular
# lib/bin build, appended to `dependencies` for the test build.
#
# Default: []
devDependencies,
# Specify the "extern" name of a library if it differs from the library target.
# See above for an extended explanation.
#
@@ -323,6 +330,7 @@ lib.makeOverridable
crate = crate_ // (lib.attrByPath [ crate_.crateName ] (attr: { }) crateOverrides crate_);
dependencies_ = dependencies;
buildDependencies_ = buildDependencies;
devDependencies_ = devDependencies;
processedAttrs = [
"src"
"propagatedBuildInputs"
@@ -334,6 +342,7 @@ lib.makeOverridable
"libPath"
"buildDependencies"
"dependencies"
"devDependencies"
"features"
"crateRenames"
"crateName"
@@ -421,7 +430,10 @@ lib.makeOverridable
++ (crate.buildInputs or [ ])
++ buildInputs_
++ completePropagatedBuildInputs;
dependencies = map lib.getLib dependencies_;
# Dev-dependencies are only linked when building tests, mirroring
# Cargo. When buildTests is false this is a no-op, so the metadata
# hash and store path of normal lib/bin builds are unchanged.
dependencies = map lib.getLib (dependencies_ ++ lib.optionals buildTests_ devDependencies_);
buildDependencies = map lib.getLib buildDependencies_;
completeDeps = lib.unique (dependencies ++ lib.concatMap (dep: dep.completeDeps) dependencies);
@@ -617,6 +629,7 @@ lib.makeOverridable
postInstall = crate_.postInstall or "";
dependencies = crate_.dependencies or [ ];
buildDependencies = crate_.buildDependencies or [ ];
devDependencies = crate_.devDependencies or [ ];
crateRenames = crate_.crateRenames or { };
buildTests = crate_.buildTests or false;
}
@@ -404,6 +404,27 @@ rec {
"test something ... ok"
];
};
rustLibTestsWithDevDependency =
let
devDep = mkHostCrate {
crateName = "dev-dep";
src = mkLib "src/lib.rs";
};
in
{
src = mkFile "src/lib.rs" ''
#[cfg(test)]
mod tests {
#[test]
fn uses_dev_dep() {
assert_eq!(dev_dep::test(), 23);
}
}
'';
devDependencies = [ devDep ];
buildTests = true;
expectedTestOutputs = [ "test tests::uses_dev_dep ... ok" ];
};
rustBinTestsCombined = {
src = symlinkJoin {
name = "rust-bin-tests-combined";