Merge "fix: good errors for failures caused by allowSubstitutes" into main

This commit is contained in:
jade 2024-08-25 20:00:58 +00:00 committed by Gerrit Code Review
commit 72f91767a8
5 changed files with 101 additions and 23 deletions

View file

@ -0,0 +1,21 @@
---
synopsis: "Build failures caused by `allowSubstitutes = false` while being the wrong system now produce a decent error"
issues: [fj#484]
cls: [1841]
category: Fixes
credits: jade
---
Nix allows derivations to set `allowSubstitutes = false` in order to force them to be built locally without querying substituters for them.
This is useful for derivations that are very fast to build (especially if they produce large output).
However, this can shoot you in the foot if the derivation *has* to be substituted such as if the derivation is for another architecture, which is what `--always-allow-substitutes` is for.
Perhaps such derivations that are known to be impossible to build locally should ignore `allowSubstitutes` (irrespective of remote builders) in the future, but this at least reports the failure and solution directly.
```
$ nix build -f fail.nix
error: a 'unicornsandrainbows-linux' with features {} is required to build '/nix/store/...-meow.drv', but I am a 'x86_64-linux' with features {...}
Hint: the failing derivation has allowSubstitutes set to false, forcing it to be built rather than substituted.
Passing --always-allow-substitutes to force substitution may resolve this failure if the path is available in a substituter.
```

View file

@ -261,7 +261,8 @@ Goal::WorkResult DerivationGoal::haveDerivation(bool inBuildSlot)
through substitutes. If that doesn't work, we'll build through substitutes. If that doesn't work, we'll build
them. */ them. */
WaitForGoals result; WaitForGoals result;
if (settings.useSubstitutes && parsedDrv->substitutesAllowed()) if (settings.useSubstitutes) {
if (parsedDrv->substitutesAllowed()) {
for (auto & [outputName, status] : initialOutputs) { for (auto & [outputName, status] : initialOutputs) {
if (!status.wanted) continue; if (!status.wanted) continue;
if (!status.known) if (!status.known)
@ -279,6 +280,10 @@ Goal::WorkResult DerivationGoal::haveDerivation(bool inBuildSlot)
cap ? std::optional { *cap } : std::nullopt)); cap ? std::optional { *cap } : std::nullopt));
} }
} }
} else {
trace("skipping substitute because allowSubstitutes is false");
}
}
if (result.goals.empty()) { /* to prevent hang (no wake-up event) */ if (result.goals.empty()) { /* to prevent hang (no wake-up event) */
return outputsSubstitutionTried(inBuildSlot); return outputsSubstitutionTried(inBuildSlot);

View file

@ -451,13 +451,23 @@ std::set<int> LocalDerivationGoal::startBuilder()
killSandbox(false); killSandbox(false);
/* Right platform? */ /* Right platform? */
if (!parsedDrv->canBuildLocally(worker.store)) if (!parsedDrv->canBuildLocally(worker.store)) {
throw Error("a '%s' with features {%s} is required to build '%s', but I am a '%s' with features {%s}", HintFmt addendum{""};
if (settings.useSubstitutes && !parsedDrv->substitutesAllowed()) {
addendum = HintFmt("\n\nHint: the failing derivation has %s set to %s, forcing it to be built rather than substituted.\n"
"Passing %s to force substitution may resolve this failure if the path is available in a substituter.",
"allowSubstitutes", "false", "--always-allow-substitutes");
}
throw Error({
.msg = HintFmt("a '%s' with features {%s} is required to build '%s', but I am a '%s' with features {%s}%s",
drv->platform, drv->platform,
concatStringsSep(", ", parsedDrv->getRequiredSystemFeatures()), concatStringsSep(", ", parsedDrv->getRequiredSystemFeatures()),
worker.store.printStorePath(drvPath), worker.store.printStorePath(drvPath),
settings.thisSystem, settings.thisSystem,
concatStringsSep<StringSet>(", ", worker.store.systemFeatures)); concatStringsSep<StringSet>(", ", worker.store.systemFeatures),
Uncolored(addendum))
});
}
/* Create a temporary directory where the build will take /* Create a temporary directory where the build will take
place. */ place. */

View file

@ -190,6 +190,7 @@ functional_tests_scripts = [
'test-libstoreconsumer.sh', 'test-libstoreconsumer.sh',
'extra-sandbox-profile.sh', 'extra-sandbox-profile.sh',
'substitute-truncated-nar.sh', 'substitute-truncated-nar.sh',
'regression-484.sh',
] ]
# Plugin tests require shared libraries support. # Plugin tests require shared libraries support.

View file

@ -0,0 +1,41 @@
source common.sh
# Ensures that nix build will deliver a usable error message when it encounters
# a build failure potentially caused by allowSubstitutes.
clearStore
cd $TEST_HOME
putDrv() {
cat > "$1" <<EOF
builtins.derivation {
name = "meow";
builder = "/bin/sh";
args = [];
system = "unicornsandrainbows-linux";
allowSubstitutes = ${2};
preferLocalBuild = true;
}
EOF
}
putDrv drv-disallow-substitutes.nix false
putDrv drv-allow-substitutes.nix true
expect 1 nix build --substitute --substituters '' --offline -f drv-disallow-substitutes.nix 2> output.txt
# error: a 'unicornsandrainbows-linux' with features {} is required to build '$TMPDIR/regression-484/store/...-meow.drv', but I am a 'x86_64-linux' with features {benchmark, big-parallel, kvm, nixos-test, uid-range}
#
# Hint: the failing derivation has allowSubstitutes set to false, forcing it to be built rather than substituted.
# Passing --always-allow-substitutes to force substitution may resolve this failure if the path is available in a substituter.
cat output.txt
grepQuiet unicornsandrainbows-linux output.txt
grepQuiet always-allow-substitutes output.txt
grepQuiet allowSubstitutes output.txt
expect 1 nix build --substitute --substituters '' --offline -f drv-allow-substitutes.nix 2> output.txt
cat output.txt
grepQuiet unicornsandrainbows-linux output.txt
grepQuiet -v allowSubstitutes output.txt