diff --git a/pkgs/build-support/rust/build-rust-crate/build-crate.nix b/pkgs/build-support/rust/build-rust-crate/build-crate.nix index 94fc853c663a..d5b7166207fa 100644 --- a/pkgs/build-support/rust/build-rust-crate/build-crate.nix +++ b/pkgs/build-support/rust/build-rust-crate/build-crate.nix @@ -70,6 +70,25 @@ let binRustcOpts = lib.concatStringsSep " " baseRustcOpts; build_bin = if buildTests then "build_bin_test" else "build_bin"; + + # Shell snippet that builds a binary target to target/cargo-bin-exe/ + # so integration tests can exec it via CARGO_BIN_EXE_. + buildBinForTests = bin: '' + mkdir -p target/cargo-bin-exe + BIN_NAME='${bin.name or crateName}' + ${ + if !bin ? path then + '' + BIN_PATH="" + search_for_bin_path "$BIN_NAME" + '' + else + '' + BIN_PATH='${bin.path}' + '' + } + build_bin "$BIN_NAME" "$BIN_PATH" target/cargo-bin-exe + ''; in '' runHook preBuild @@ -88,13 +107,60 @@ in if [[ -e "$LIB_PATH" ]]; then build_lib "$LIB_PATH" - ${lib.optionalString buildTests ''build_lib_test "$LIB_PATH"''} elif [[ -e src/lib.rs ]]; then build_lib src/lib.rs - ${lib.optionalString buildTests "build_lib_test src/lib.rs"} fi + ${ + # When building tests, first build the real (non-test) binaries so + # integration tests can exec them via CARGO_BIN_EXE_. They go + # to target/cargo-bin-exe/ to avoid colliding with the --test + # harnesses written to target/bin/. After building, populate the + # CARGO_BIN_EXE_ENV array so subsequent rustc invocations see the + # env vars (via `env` prefix in lib.sh, which unlike bash `export` + # accepts hyphenated names). + lib.optionalString buildTests ( + lib.concatMapStringsSep "\n" ( + bin: + let + haveRequiredFeature = + if bin ? requiredFeatures then + lib.intersectLists bin.requiredFeatures crateFeatures == bin.requiredFeatures + else + true; + in + lib.optionalString haveRequiredFeature (buildBinForTests bin) + ) crateBin + + lib.optionalString (lib.length crateBin == 0 && !hasCrateBin) '' + if [[ -e src/main.rs ]]; then + mkdir -p target/cargo-bin-exe + build_bin ${crateName} src/main.rs target/cargo-bin-exe + fi + for i in src/bin/*.rs; do + [ -e "$i" ] || continue + mkdir -p target/cargo-bin-exe + build_bin "$(basename $i .rs)" "$i" target/cargo-bin-exe + done + '' + + '' + if [ -d target/cargo-bin-exe ]; then + for b in target/cargo-bin-exe/*; do + [ -x "$b" ] || continue + name=$(basename "$b") + CARGO_BIN_EXE_ENV+=("CARGO_BIN_EXE_$name=$out/bin/$name") + done + fi + '' + ) + } + ${lib.optionalString buildTests '' + if [[ -e "$LIB_PATH" ]]; then + build_lib_test "$LIB_PATH" + elif [[ -e src/lib.rs ]]; then + build_lib_test src/lib.rs + fi + ''} ${lib.optionalString (lib.length crateBin > 0) ( lib.concatMapStringsSep "\n" ( diff --git a/pkgs/build-support/rust/build-rust-crate/install-crate.nix b/pkgs/build-support/rust/build-rust-crate/install-crate.nix index b35652ad454a..4c67dd977696 100644 --- a/pkgs/build-support/rust/build-rust-crate/install-crate.nix +++ b/pkgs/build-support/rust/build-rust-crate/install-crate.nix @@ -48,6 +48,12 @@ else -executable \ -print0 | xargs --no-run-if-empty --null install --target $out/tests; fi + # Real (non-test) binaries that integration tests may exec via + # CARGO_BIN_EXE_. + if [ -d target/cargo-bin-exe ]; then + mkdir -p $out/bin + cp -rP target/cargo-bin-exe/* $out/bin + fi runHook postInstall '' diff --git a/pkgs/build-support/rust/build-rust-crate/lib.sh b/pkgs/build-support/rust/build-rust-crate/lib.sh index 09731f29f0c3..4d736302a0c9 100644 --- a/pkgs/build-support/rust/build-rust-crate/lib.sh +++ b/pkgs/build-support/rust/build-rust-crate/lib.sh @@ -10,7 +10,7 @@ build_lib() { lib_src=$1 echo_build_heading $lib_src ${libName} - noisily rustc \ + noisily env "${CARGO_BIN_EXE_ENV[@]}" rustc \ --crate-name $CRATE_NAME \ $lib_src \ --out-dir target/lib \ @@ -36,17 +36,18 @@ build_bin() { local crate_name=$1 local crate_name_=$(echo $crate_name | tr '-' '_') local main_file="" + local out_dir="${3:-target/bin}" if [[ ! -z $2 ]]; then main_file=$2 fi - echo_build_heading $@ - noisily rustc \ + echo_build_heading $crate_name $main_file + noisily env "${CARGO_BIN_EXE_ENV[@]}" rustc \ --crate-name $crate_name_ \ $main_file \ --crate-type bin \ $BIN_RUSTC_OPTS \ - --out-dir target/bin \ + --out-dir "$out_dir" \ -L dependency=target/deps \ $LINK \ $EXTRA_LINK_ARGS \ @@ -60,10 +61,10 @@ build_bin() { --color ${colors} \ if [ "$crate_name_" != "$crate_name" ]; then - if [ -f "target/bin/$crate_name_.wasm" ]; then - mv target/bin/$crate_name_.wasm target/bin/$crate_name.wasm + if [ -f "$out_dir/$crate_name_.wasm" ]; then + mv "$out_dir/$crate_name_.wasm" "$out_dir/$crate_name.wasm" else - mv target/bin/$crate_name_ target/bin/$crate_name + mv "$out_dir/$crate_name_" "$out_dir/$crate_name" fi fi } diff --git a/pkgs/build-support/rust/build-rust-crate/test/default.nix b/pkgs/build-support/rust/build-rust-crate/test/default.nix index 3e6bc92882d8..2c9ada4e3dfd 100644 --- a/pkgs/build-support/rust/build-rust-crate/test/default.nix +++ b/pkgs/build-support/rust/build-rust-crate/test/default.nix @@ -458,6 +458,74 @@ rec { "test flat_test ... ok" ]; }; + rustBinTestsCargoBinExe = { + # Integration tests locate the crate's own binary via + # `env!("CARGO_BIN_EXE_")`, which cargo sets automatically. + crateName = "my-crate"; + src = symlinkJoin { + name = "rust-bin-tests-cargo-bin-exe"; + paths = [ + (mkFile "src/main.rs" '' + fn main() { println!("hello from my-crate"); } + '') + (mkFile "tests/run_bin.rs" '' + #[test] + fn runs_binary() { + let bin = env!("CARGO_BIN_EXE_my-crate"); + let out = std::process::Command::new(bin) + .output() + .expect("spawn"); + assert!(out.status.success()); + assert_eq!( + String::from_utf8_lossy(&out.stdout).trim(), + "hello from my-crate" + ); + } + '') + ]; + }; + buildTests = true; + expectedTestOutputs = [ + "test runs_binary ... ok" + ]; + }; + rustBinTestsCargoBinExeAutoDetect = { + # Verify CARGO_BIN_EXE_ is also set for auto-detected + # src/bin/*.rs binaries, not just src/main.rs or explicit + # crateBin entries. + crateName = "multi-bin"; + src = symlinkJoin { + name = "rust-bin-tests-cargo-bin-exe-auto"; + paths = [ + (mkFile "src/lib.rs" "") + (mkFile "src/bin/tool-a.rs" '' + fn main() { println!("tool-a ran"); } + '') + (mkFile "src/bin/tool-b.rs" '' + fn main() { println!("tool-b ran"); } + '') + (mkFile "tests/run_tools.rs" '' + #[test] + fn runs_both() { + for (bin, want) in [ + (env!("CARGO_BIN_EXE_tool-a"), "tool-a ran"), + (env!("CARGO_BIN_EXE_tool-b"), "tool-b ran"), + ] { + let out = std::process::Command::new(bin) + .output() + .expect("spawn"); + assert!(out.status.success()); + assert_eq!(String::from_utf8_lossy(&out.stdout).trim(), want); + } + } + '') + ]; + }; + buildTests = true; + expectedTestOutputs = [ + "test runs_both ... ok" + ]; + }; linkAgainstRlibCrate = { crateName = "foo"; src = mkFile "src/main.rs" ''