From 3c610df550be35d9696efe9dd3217a6e1ec100f2 Mon Sep 17 00:00:00 2001 From: John Ericson Date: Fri, 26 Sep 2025 00:22:54 -0400 Subject: [PATCH 1/2] Delete scratch data for CA derivation that produced already-extant output In the case where the store object doesn't exist, we do correctly move (rather than copy) the scratch data into place. In this case, the destination store object already exists, but we still want to clean up after ourselves. --- src/libstore/unix/build/derivation-builder.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libstore/unix/build/derivation-builder.cc b/src/libstore/unix/build/derivation-builder.cc index 770bdad4d..3a6f71555 100644 --- a/src/libstore/unix/build/derivation-builder.cc +++ b/src/libstore/unix/build/derivation-builder.cc @@ -1712,6 +1712,8 @@ SingleDrvOutputs DerivationBuilderImpl::registerOutputs() /* Path already exists because CA path produced by something else. No moving needed. */ assert(newInfo.ca); + /* Can delete our scratch copy now. */ + deletePath(actualPath); } else { auto destPath = store.toRealPath(finalDestPath); deletePath(destPath); From e35abb110264c692b1d442f1433f691e4d0efbc2 Mon Sep 17 00:00:00 2001 From: John Ericson Date: Fri, 26 Sep 2025 00:21:41 -0400 Subject: [PATCH 2/2] Create test for issue 13247 This test ends up being skipped, since the bug has not yet been fixed. A future commit will fix the bug. Progress on #13247, naturally. --- tests/functional/ca/issue-13247.nix | 46 +++++++++++++++++++ tests/functional/ca/issue-13247.sh | 71 +++++++++++++++++++++++++++++ tests/functional/ca/meson.build | 1 + 3 files changed, 118 insertions(+) create mode 100644 tests/functional/ca/issue-13247.nix create mode 100755 tests/functional/ca/issue-13247.sh diff --git a/tests/functional/ca/issue-13247.nix b/tests/functional/ca/issue-13247.nix new file mode 100644 index 000000000..78c622ed9 --- /dev/null +++ b/tests/functional/ca/issue-13247.nix @@ -0,0 +1,46 @@ +with import ./config.nix; + +rec { + + a = mkDerivation { + name = "issue-13247-a"; + builder = builtins.toFile "builder.sh" '' + mkdir $out + test -z $all + echo "output" > $out/file + ''; + }; + + # Same output, different drv + a-prime = mkDerivation { + name = "issue-13247-a"; + builder = builtins.toFile "builder.sh" '' + echo 'will make the same stuff as `a`, but different drv hash' + + mkdir $out + test -z $all + echo "output" > $out/file + ''; + }; + + # Multiple outputs in a derivation that depends on other derivations + f = + dep: + mkDerivation { + name = "use-a-more-outputs"; + outputs = [ + "first" + "second" + ]; + inherit dep; + builder = builtins.toFile "builder.sh" '' + ln -s $dep/file $first + ln -s $first $second + ''; + }; + + use-a-more-outputs = f a; + + use-a-prime-more-outputs = f a-prime; + +} diff --git a/tests/functional/ca/issue-13247.sh b/tests/functional/ca/issue-13247.sh new file mode 100755 index 000000000..686d90ced --- /dev/null +++ b/tests/functional/ca/issue-13247.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash + +# https://github.com/NixOS/nix/issues/13247 + +export NIX_TESTS_CA_BY_DEFAULT=1 + +source common.sh + +clearStoreIfPossible + +set -x + +# Build derivation (both outputs) +nix build -f issue-13247.nix --json a a-prime use-a-more-outputs --no-link > "$TEST_ROOT"/a.json + +cache="file://$TEST_ROOT/cache" + +# Copy all outputs and realisations to cache +declare -a drvs +for d in "$NIX_STORE_DIR"/*-issue-13247-a.drv "$NIX_STORE_DIR"/*-use-a-more-outputs.drv; do + drvs+=("$d" "$d"^*) +done +nix copy --to "$cache" "${drvs[@]}" + +function delete () { + # Delete local copy + # shellcheck disable=SC2046 + nix-store --delete \ + $(jq -r <"$TEST_ROOT"/a.json '.[] | .drvPath, .outputs.[]') \ + "$NIX_STORE_DIR"/*-issue-13247-a.drv \ + "$NIX_STORE_DIR"/*-use-a-more-outputs.drv + + [[ ! -e "$(jq -r <"$TEST_ROOT"/a.json '.[0].outputs.out')" ]] + [[ ! -e "$(jq -r <"$TEST_ROOT"/a.json '.[1].outputs.out')" ]] + [[ ! -e "$(jq -r <"$TEST_ROOT"/a.json '.[2].outputs.first')" ]] + [[ ! -e "$(jq -r <"$TEST_ROOT"/a.json '.[2].outputs.second')" ]] +} + +delete + +buildViaSubstitute () { + nix build -f issue-13247.nix "$1" --no-link --max-jobs 0 --substituters "$cache" --no-require-sigs --offline --substitute +} + +# Substitue just the first output +buildViaSubstitute use-a-more-outputs^first + +# Should only fetch the output we asked for +[[ -d "$(jq -r <"$TEST_ROOT"/a.json '.[0].outputs.out')" ]] +[[ -f "$(jq -r <"$TEST_ROOT"/a.json '.[2].outputs.first')" ]] +[[ ! -e "$(jq -r <"$TEST_ROOT"/a.json '.[2].outputs.second')" ]] + +delete + +# Failure with 2.28 encountered in CI +requireDaemonNewerThan "2.29" + +# Substitue just the first output +# +# This derivation is the same after normalization, so we should get +# early cut-off, and thus a chance to download just the output we want +# rather than building more +buildViaSubstitute use-a-prime-more-outputs^first + +# Should only fetch the output we asked for +[[ -d "$(jq -r <"$TEST_ROOT"/a.json '.[0].outputs.out')" ]] +[[ -f "$(jq -r <"$TEST_ROOT"/a.json '.[2].outputs.first')" ]] + +# Output should *not* be here, this is the bug +[[ -e "$(jq -r <"$TEST_ROOT"/a.json '.[2].outputs.second')" ]] +skipTest "bug is not yet fixed" diff --git a/tests/functional/ca/meson.build b/tests/functional/ca/meson.build index 06aa19b22..b1912fd86 100644 --- a/tests/functional/ca/meson.build +++ b/tests/functional/ca/meson.build @@ -19,6 +19,7 @@ suites += { 'eval-store.sh', 'gc.sh', 'import-from-derivation.sh', + 'issue-13247.sh', 'multiple-outputs.sh', 'new-build-cmd.sh', 'nix-copy.sh',