1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-11-28 13:11:00 +01:00

Merge pull request #14560 from obsidiansystems/fill-in-outputs

Dedup some derivation initialization logic, and test
This commit is contained in:
John Ericson 2025-11-24 21:10:38 +00:00 committed by GitHub
commit 3ba51bf61b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 774 additions and 140 deletions

View file

@ -1203,6 +1203,136 @@ std::optional<BasicDerivation> Derivation::tryResolve(
return resolved;
}
/**
* Process `InputAddressed`, `Deferred`, and `CAFixed` outputs.
*
* For `InputAddressed` outputs or `Deferred` outputs:
*
* - with `Regular` hash kind, validate `InputAddressed` outputs have
* the correct path (throws if mismatch). For `Deferred` outputs:
* - if `fillIn` is true, fill in the output path to make `InputAddressed`
* - if `fillIn` is false, throw an error
* Then validate or fill in the environment variable with the path.
*
* - with `Deferred` hash kind, validate that the output is either
* `InputAddressed` (error) or `Deferred` (correct).
*
* For `CAFixed` outputs, validate or fill in the environment variable
* with the computed path.
*
* @tparam fillIn If true, fill in missing output paths and environment
* variables. If false, validate that all paths are correct (throws on
* mismatch).
*/
template<bool fillIn>
static void processDerivationOutputPaths(Store & store, auto && drv, std::string_view drvName)
{
std::optional<DrvHash> hashesModulo;
for (auto & [outputName, output] : drv.outputs) {
auto envHasRightPath = [&](const StorePath & actual) {
if constexpr (fillIn) {
auto j = drv.env.find(outputName);
/* Fill in mode: fill in missing or empty environment
variables */
if (j == drv.env.end())
drv.env.insert(j, {outputName, store.printStorePath(actual)});
else if (j->second == "")
j->second = store.printStorePath(actual);
/* We know validation will succeed after fill-in, but
just to be extra sure, validate unconditionally */
}
auto j = drv.env.find(outputName);
if (j == drv.env.end())
throw Error(
"derivation has missing environment variable '%s', should be '%s' but is not present",
outputName,
store.printStorePath(actual));
if (j->second != store.printStorePath(actual))
throw Error(
"derivation has incorrect environment variable '%s', should be '%s' but is actually '%s'",
outputName,
store.printStorePath(actual),
j->second);
};
auto hash = [&]<typename Output>(const Output & outputVariant) {
if (!hashesModulo) {
// somewhat expensive so we do lazily
hashesModulo = hashDerivationModulo(store, drv, true);
}
switch (hashesModulo->kind) {
case DrvHash::Kind::Regular: {
auto h = get(hashesModulo->hashes, outputName);
if (!h)
throw Error("derivation produced no hash for output '%s'", outputName);
auto outPath = store.makeOutputPath(outputName, *h, drvName);
if constexpr (std::is_same_v<Output, DerivationOutput::InputAddressed>) {
if (outputVariant.path == outPath) {
return; // Correct case
}
/* Error case, an explicitly wrong path is
always an error. */
throw Error(
"derivation has incorrect output '%s', should be '%s'",
store.printStorePath(outputVariant.path),
store.printStorePath(outPath));
} else if constexpr (std::is_same_v<Output, DerivationOutput::Deferred>) {
if constexpr (fillIn)
/* Fill in output path for Deferred
outputs */
output = DerivationOutput::InputAddressed{
.path = outPath,
};
else
/* Validation mode: deferred outputs
should have been filled in */
throw Error(
"derivation has incorrect deferred output, should be '%s'", store.printStorePath(outPath));
} else {
/* Will never happen, based on where
`hash` is called. */
static_assert(false);
}
envHasRightPath(outPath);
break;
}
case DrvHash::Kind::Deferred:
if constexpr (std::is_same_v<Output, DerivationOutput::InputAddressed>) {
/* Error case, an explicitly wrong path is
always an error. */
throw Error(
"derivation has incorrect output '%s', should be deferred",
store.printStorePath(outputVariant.path));
} else if constexpr (std::is_same_v<Output, DerivationOutput::Deferred>) {
/* Correct: Deferred output with Deferred
hash kind. */
} else {
/* Will never happen, based on where
`hash` is called. */
static_assert(false);
}
break;
}
};
std::visit(
overloaded{
[&](const DerivationOutput::InputAddressed & o) { hash(o); },
[&](const DerivationOutput::Deferred & o) { hash(o); },
[&](const DerivationOutput::CAFixed & dof) { envHasRightPath(dof.path(store, drvName, outputName)); },
[&](const auto &) {
// Nothing to do for other output types
},
},
output.raw);
}
/* Don't need the answer, but do this anyways to assert is proper
combination. The code above is more general and naturally allows
combinations that are currently prohibited. */
drv.type();
}
void Derivation::checkInvariants(Store & store, const StorePath & drvPath) const
{
assert(drvPath.isDerivation());
@ -1210,67 +1340,43 @@ void Derivation::checkInvariants(Store & store, const StorePath & drvPath) const
drvName = drvName.substr(0, drvName.size() - drvExtension.size());
if (drvName != name) {
throw Error("Derivation '%s' has name '%s' which does not match its path", store.printStorePath(drvPath), name);
throw Error("derivation '%s' has name '%s' which does not match its path", store.printStorePath(drvPath), name);
}
auto envHasRightPath = [&](const StorePath & actual, const std::string & varName) {
auto j = env.find(varName);
if (j == env.end() || store.parseStorePath(j->second) != actual)
throw Error(
"derivation '%s' has incorrect environment variable '%s', should be '%s'",
store.printStorePath(drvPath),
varName,
store.printStorePath(actual));
};
// Don't need the answer, but do this anyways to assert is proper
// combination. The code below is more general and naturally allows
// combinations that are currently prohibited.
type();
std::optional<DrvHash> hashesModulo;
for (auto & i : outputs) {
std::visit(
overloaded{
[&](const DerivationOutput::InputAddressed & doia) {
if (!hashesModulo) {
// somewhat expensive so we do lazily
hashesModulo = hashDerivationModulo(store, *this, true);
}
auto currentOutputHash = get(hashesModulo->hashes, i.first);
if (!currentOutputHash)
throw Error(
"derivation '%s' has unexpected output '%s' (local-store / hashesModulo) named '%s'",
store.printStorePath(drvPath),
store.printStorePath(doia.path),
i.first);
StorePath recomputed = store.makeOutputPath(i.first, *currentOutputHash, drvName);
if (doia.path != recomputed)
throw Error(
"derivation '%s' has incorrect output '%s', should be '%s'",
store.printStorePath(drvPath),
store.printStorePath(doia.path),
store.printStorePath(recomputed));
envHasRightPath(doia.path, i.first);
},
[&](const DerivationOutput::CAFixed & dof) {
auto path = dof.path(store, drvName, i.first);
envHasRightPath(path, i.first);
},
[&](const DerivationOutput::CAFloating &) {
/* Nothing to check */
},
[&](const DerivationOutput::Deferred &) {
/* Nothing to check */
},
[&](const DerivationOutput::Impure &) {
/* Nothing to check */
},
},
i.second.raw);
try {
checkInvariants(store);
} catch (Error & e) {
e.addTrace({}, "while checking derivation '%s'", store.printStorePath(drvPath));
throw;
}
}
void Derivation::checkInvariants(Store & store) const
{
processDerivationOutputPaths<false>(store, *this, name);
}
void Derivation::fillInOutputPaths(Store & store)
{
processDerivationOutputPaths<true>(store, *this, name);
}
Derivation Derivation::parseJsonAndValidate(Store & store, const nlohmann::json & json)
{
auto drv = static_cast<Derivation>(json);
drv.fillInOutputPaths(store);
try {
drv.checkInvariants(store);
} catch (Error & e) {
e.addTrace({}, "while checking derivation from JSON with name '%s'", drv.name);
throw;
}
return drv;
}
const Hash impureOutputHash = hashString(HashAlgorithm::SHA256, "impure");
} // namespace nix

View file

@ -368,9 +368,48 @@ struct Derivation : BasicDerivation
* This is mainly a matter of checking the outputs, where our C++
* representation supports all sorts of combinations we do not yet
* allow.
*
* This overload does not validate the derivation name or add path
* context to errors. Use this when you don't have a `StorePath` or
* when you want to handle error context yourself.
*
* @param store The store to use for validation
*/
void checkInvariants(Store & store) const;
/**
* This overload does everything the base `checkInvariants` does,
* but also validates that the derivation name matches the path, and
* improves any error messages that occur using the derivation path.
*
* @param store The store to use for validation
* @param drvPath The path to this derivation
*/
void checkInvariants(Store & store, const StorePath & drvPath) const;
/**
* Fill in output paths as needed.
*
* For input-addressed derivations (ready or deferred), it computes
* the derivation hash modulo and based on the result:
*
* - If `Regular`: converts `Deferred` outputs to `InputAddressed`,
* and ensures all `InputAddressed` outputs (whether preexisting
* or newly computed) have the right computed paths. Likewise
* defines (if absent or the empty string) or checks (if
* preexisting and non-empty) environment variables for each
* output with their path.
*
* - If `Deferred`: converts `InputAddressed` to `Deferred`.
*
* Also for fixed-output content-addressed derivations, likewise
* updates output paths in env vars.
*
* @param store The store to use for path computation
* @param drvName The derivation name (without .drv extension)
*/
void fillInOutputPaths(Store & store);
Derivation() = default;
Derivation(const BasicDerivation & bd)
@ -383,6 +422,29 @@ struct Derivation : BasicDerivation
{
}
/**
* Parse a derivation from JSON, and also perform various
* conveniences such as:
*
* 1. Filling in output paths in as needed/required.
*
* 2. Checking invariants in general.
*
* In the future it might also do things like:
*
* - assist with the migration from older JSON formats.
*
* - (a somewhat example of the above) initialize
* `DerivationOptions` from their traditional encoding inside the
* `env` and `structuredAttrs`.
*
* @param store The store to use for path computation and validation
* @param json The JSON representation of the derivation
* @return A validated derivation with output paths filled in
* @throws Error if parsing fails, output paths can't be computed, or validation fails
*/
static Derivation parseJsonAndValidate(Store & store, const nlohmann::json & json);
bool operator==(const Derivation &) const = default;
// TODO libc++ 16 (used by darwin) missing `std::map::operator <=>`, can't do yet.
// auto operator <=> (const Derivation &) const = default;