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

Parse deriving paths in DerivationOptions

This is an example of "Parse, don't validate" principle [1].

Before, we had a number of `StringSet`s in `DerivationOptions` that
were not *actually* allowed to be arbitrary sets of strings. Instead,
each set member had to be one of:

- a store path

- a CA "downstream placeholder"

- an output name

Only later, in the code that checks outputs, would these strings be
further parsed to match these cases. (Actually, only 2 by that point,
because the placeholders must be rewritten away by then.)

Now, we fully parse everything up front, and have an "honest" data type
that reflects these invariants:

- store paths are parsed, stored as (opaque) deriving paths

- CA "downstream placeholders" are rewritten to the output deriving
  paths they denote

- output names are the only arbitrary strings left

Since the first two cases both become deriving paths, that leaves us
with a `std::variant<SingleDerivedPath, String>` data type, which we use
in our sets instead.

Getting rid of placeholders is especially nice because we are replacing
them with something much more internally-structured / transparent.

[1]: https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/

Co-authored-by: Sergei Zimmerman <sergei@zimmerman.foo>
Co-authored-by: Eelco Dolstra <edolstra@gmail.com>
This commit is contained in:
John Ericson 2025-04-14 14:26:13 -04:00
parent 72dbd43882
commit 76bd600302
23 changed files with 731 additions and 260 deletions

View file

@ -32,14 +32,6 @@ DerivationBuildingGoal::DerivationBuildingGoal(
, drv{std::make_unique<Derivation>(drv)}
, buildMode(buildMode)
{
try {
drvOptions =
std::make_unique<DerivationOptions>(DerivationOptions::fromStructuredAttrs(drv.env, drv.structuredAttrs));
} catch (Error & e) {
e.addTrace({}, "while parsing derivation '%s'", worker.store.printStorePath(drvPath));
throw;
}
name = fmt("building derivation '%s'", worker.store.printStorePath(drvPath));
trace("created");
@ -206,6 +198,38 @@ Goal::Co DerivationBuildingGoal::gaveUpOnSubstitution(bool storeDerivation)
Goal::Co DerivationBuildingGoal::tryToBuild()
{
auto drvOptions = [&] {
DerivationOptions<SingleDerivedPath> temp;
try {
temp =
derivationOptionsFromStructuredAttrs(worker.store, drv->inputDrvs, drv->env, get(drv->structuredAttrs));
} catch (Error & e) {
e.addTrace({}, "while parsing derivation '%s'", worker.store.printStorePath(drvPath));
throw;
}
auto res = tryResolve(
temp,
[&](ref<const SingleDerivedPath> drvPath, const std::string & outputName) -> std::optional<StorePath> {
try {
return resolveDerivedPath(
worker.store, SingleDerivedPath::Built{drvPath, outputName}, &worker.evalStore);
} catch (Error &) {
return std::nullopt;
}
});
/* The derivation must have all of its inputs gotten this point,
so the resolution will surely succeed.
(Actually, we shouldn't even enter this goal until we have a
resolved derivation, or derivation with only input addressed
transitive inputs, so this should be a no-opt anyways.)
*/
assert(res);
return *res;
}();
std::map<std::string, InitialOutput> initialOutputs;
/* Recheck at this point. In particular, whereas before we were
@ -344,13 +368,13 @@ Goal::Co DerivationBuildingGoal::tryToBuild()
/* Don't do a remote build if the derivation has the attribute
`preferLocalBuild' set. Also, check and repair modes are only
supported for local builds. */
bool buildLocally = (buildMode != bmNormal || drvOptions->willBuildLocally(worker.store, *drv))
bool buildLocally = (buildMode != bmNormal || drvOptions.willBuildLocally(worker.store, *drv))
&& settings.maxBuildJobs.get() != 0;
if (buildLocally) {
useHook = false;
} else {
switch (tryBuildHook(initialOutputs)) {
switch (tryBuildHook(initialOutputs, drvOptions)) {
case rpAccept:
/* Yes, it has started doing so. Wait until we get
EOF from the hook. */
@ -379,7 +403,7 @@ Goal::Co DerivationBuildingGoal::tryToBuild()
externalBuilder = settings.findExternalDerivationBuilderIfSupported(*drv);
if (!externalBuilder && !drvOptions->canBuildLocally(worker.store, *drv)) {
if (!externalBuilder && !drvOptions.canBuildLocally(worker.store, *drv)) {
auto msg =
fmt("Cannot build '%s'.\n"
"Reason: " ANSI_RED "required system or feature not available" ANSI_NORMAL
@ -388,7 +412,7 @@ Goal::Co DerivationBuildingGoal::tryToBuild()
"Current system: '%s' with features {%s}",
Magenta(worker.store.printStorePath(drvPath)),
Magenta(drv->platform),
concatStringsSep(", ", drvOptions->getRequiredSystemFeatures(*drv)),
concatStringsSep(", ", drvOptions.getRequiredSystemFeatures(*drv)),
Magenta(settings.thisSystem),
concatStringsSep<StringSet>(", ", worker.store.Store::config.systemFeatures));
@ -586,7 +610,7 @@ Goal::Co DerivationBuildingGoal::tryToBuild()
}
try {
desugaredEnv = DesugaredEnv::create(worker.store, *drv, *drvOptions, inputPaths);
desugaredEnv = DesugaredEnv::create(worker.store, *drv, drvOptions, inputPaths);
} catch (BuildError & e) {
outputLocks.unlock();
worker.permanentFailure = true;
@ -597,7 +621,7 @@ Goal::Co DerivationBuildingGoal::tryToBuild()
.drvPath = drvPath,
.buildResult = buildResult,
.drv = *drv,
.drvOptions = *drvOptions,
.drvOptions = drvOptions,
.inputPaths = inputPaths,
.initialOutputs = initialOutputs,
.buildMode = buildMode,
@ -803,7 +827,8 @@ BuildError DerivationBuildingGoal::fixupBuilderFailureErrorMessage(BuilderFailur
return BuildError{e.status, msg};
}
HookReply DerivationBuildingGoal::tryBuildHook(const std::map<std::string, InitialOutput> & initialOutputs)
HookReply DerivationBuildingGoal::tryBuildHook(
const std::map<std::string, InitialOutput> & initialOutputs, const DerivationOptions<StorePath> & drvOptions)
{
#ifdef _WIN32 // TODO enable build hook on Windows
return rpDecline;
@ -820,7 +845,7 @@ HookReply DerivationBuildingGoal::tryBuildHook(const std::map<std::string, Initi
/* Send the request to the hook. */
worker.hook->sink << "try" << (worker.getNrLocalBuilds() < settings.maxBuildJobs ? 1 : 0) << drv->platform
<< worker.store.printStorePath(drvPath) << drvOptions->getRequiredSystemFeatures(*drv);
<< worker.store.printStorePath(drvPath) << drvOptions.getRequiredSystemFeatures(*drv);
worker.hook->sink.flush();
/* Read the first line of input, which should be a word indicating

View file

@ -11,7 +11,7 @@ void checkOutputs(
Store & store,
const StorePath & drvPath,
const decltype(Derivation::outputs) & drvOutputs,
const decltype(DerivationOptions::outputChecks) & outputChecks,
const decltype(DerivationOptions<StorePath>::outputChecks) & outputChecks,
const std::map<std::string, ValidPathInfo> & outputs)
{
std::map<Path, const ValidPathInfo &> outputsByPath;
@ -85,7 +85,7 @@ void checkOutputs(
return std::make_pair(std::move(pathsDone), closureSize);
};
auto applyChecks = [&](const DerivationOptions::OutputChecks & checks) {
auto applyChecks = [&](const DerivationOptions<StorePath>::OutputChecks & checks) {
if (checks.maxSize && info.narSize > *checks.maxSize)
throw BuildError(
BuildResult::Failure::OutputRejected,
@ -105,28 +105,33 @@ void checkOutputs(
*checks.maxClosureSize);
}
auto checkRefs = [&](const StringSet & value, bool allowed, bool recursive) {
auto checkRefs = [&](const std::set<DrvRef<StorePath>> & value, bool allowed, bool recursive) {
/* Parse a list of reference specifiers. Each element must
either be a store path, or the symbolic name of the output
of the derivation (such as `out'). */
StorePathSet spec;
for (auto & i : value) {
if (store.isStorePath(i))
spec.insert(store.parseStorePath(i));
else if (auto output = get(outputs, i))
spec.insert(output->path);
else {
std::string outputsListing =
concatMapStringsSep(", ", outputs, [](auto & o) { return o.first; });
throw BuildError(
BuildResult::Failure::OutputRejected,
"derivation '%s' output check for '%s' contains an illegal reference specifier '%s',"
" expected store path or output name (one of [%s])",
store.printStorePath(drvPath),
outputName,
i,
outputsListing);
}
std::visit(
overloaded{
[&](const StorePath & path) { spec.insert(path); },
[&](const OutputName & refOutputName) {
if (auto output = get(outputs, refOutputName))
spec.insert(output->path);
else {
std::string outputsListing =
concatMapStringsSep(", ", outputs, [](auto & o) { return o.first; });
throw BuildError(
BuildResult::Failure::OutputRejected,
"derivation '%s' output check for '%s' contains output name '%s',"
" but this is not a valid output of this derivation."
" (Valid outputs are [%s].)",
store.printStorePath(drvPath),
outputName,
refOutputName,
outputsListing);
}
}},
i);
}
auto used = recursive ? getClosure(info.path).first : info.references;
@ -180,8 +185,8 @@ void checkOutputs(
std::visit(
overloaded{
[&](const DerivationOptions::OutputChecks & checks) { applyChecks(checks); },
[&](const std::map<std::string, DerivationOptions::OutputChecks> & checksPerOutput) {
[&](const DerivationOptions<StorePath>::OutputChecks & checks) { applyChecks(checks); },
[&](const std::map<std::string, DerivationOptions<StorePath>::OutputChecks> & checksPerOutput) {
if (auto outputChecks = get(checksPerOutput, outputName))
applyChecks(*outputChecks);

View file

@ -21,7 +21,7 @@ void checkOutputs(
Store & store,
const StorePath & drvPath,
const decltype(Derivation::outputs) & drvOutputs,
const decltype(DerivationOptions::outputChecks) & drvOptions,
const decltype(DerivationOptions<StorePath>::outputChecks) & drvOptions,
const std::map<std::string, ValidPathInfo> & outputs);
} // namespace nix

View file

@ -18,7 +18,10 @@ std::string & DesugaredEnv::atFileEnvPair(std::string_view name, std::string fil
}
DesugaredEnv DesugaredEnv::create(
Store & store, const Derivation & drv, const DerivationOptions & drvOptions, const StorePathSet & inputPaths)
Store & store,
const Derivation & drv,
const DerivationOptions<StorePath> & drvOptions,
const StorePathSet & inputPaths)
{
DesugaredEnv res;
@ -46,7 +49,7 @@ DesugaredEnv DesugaredEnv::create(
}
/* Handle exportReferencesGraph(), if set. */
for (auto & [fileName, storePaths] : drvOptions.getParsedExportReferencesGraph(store)) {
for (auto & [fileName, storePaths] : drvOptions.exportReferencesGraph) {
/* Write closure info to <fileName>. */
res.extraFiles.insert_or_assign(
fileName, store.makeValidityRegistration(store.exportReferences(storePaths, inputPaths), false, false));

View file

@ -64,9 +64,10 @@ Goal::Co DerivationGoal::haveDerivation(bool storeDerivation)
{
trace("have derivation");
auto drvOptions = [&]() -> DerivationOptions {
auto drvOptions = [&]() -> DerivationOptions<SingleDerivedPath> {
try {
return DerivationOptions::fromStructuredAttrs(drv->env, drv->structuredAttrs);
return derivationOptionsFromStructuredAttrs(
worker.store, drv->inputDrvs, drv->env, get(drv->structuredAttrs));
} catch (Error & e) {
e.addTrace({}, "while parsing derivation '%s'", worker.store.printStorePath(drvPath));
throw;

View file

@ -2,15 +2,18 @@
#include "nix/util/json-utils.hh"
#include "nix/store/parsed-derivations.hh"
#include "nix/store/derivations.hh"
#include "nix/store/derived-path.hh"
#include "nix/store/store-api.hh"
#include "nix/util/types.hh"
#include "nix/util/util.hh"
#include "nix/store/globals.hh"
#include "nix/util/variant-wrapper.hh"
#include <optional>
#include <string>
#include <variant>
#include <regex>
#include <ranges>
namespace nix {
@ -90,14 +93,38 @@ getStringSetAttr(const StringMap & env, const StructuredAttrs * parsed, const st
return ss ? (std::optional{StringSet{ss->begin(), ss->end()}}) : (std::optional<StringSet>{});
}
using OutputChecks = DerivationOptions::OutputChecks;
template<typename Inputs>
using OutputChecks = DerivationOptions<Inputs>::OutputChecks;
using OutputChecksVariant = std::variant<OutputChecks, std::map<std::string, OutputChecks>>;
template<typename Inputs>
using OutputChecksVariant = std::variant<OutputChecks<Inputs>, std::map<std::string, OutputChecks<Inputs>>>;
DerivationOptions DerivationOptions::fromStructuredAttrs(
const StringMap & env, const std::optional<StructuredAttrs> & parsed, bool shouldWarn)
DerivationOptions<StorePath> derivationOptionsFromStructuredAttrs(
const StoreDirConfig & store,
const StringMap & env,
const StructuredAttrs * parsed,
bool shouldWarn,
const ExperimentalFeatureSettings & mockXpSettings)
{
return fromStructuredAttrs(env, parsed ? &*parsed : nullptr);
/* Use the SingleDerivedPath version with empty inputDrvs, then
resolve. */
DerivedPathMap<StringSet> emptyInputDrvs{};
auto singleDerivedPathOptions =
derivationOptionsFromStructuredAttrs(store, emptyInputDrvs, env, parsed, shouldWarn, mockXpSettings);
/* "Resolve" all SingleDerivedPath inputs to StorePath. */
auto resolved = tryResolve(
singleDerivedPathOptions,
[&](ref<const SingleDerivedPath> drvPath, const std::string & outputName) -> std::optional<StorePath> {
// there should be nothing to resolve
assert(false);
});
/* Since we should never need to call the call back, there should be
no way it fails. */
assert(resolved);
return *resolved;
}
static void flatten(const nlohmann::json & value, StringSet & res)
@ -111,10 +138,63 @@ static void flatten(const nlohmann::json & value, StringSet & res)
throw Error("'exportReferencesGraph' value is not an array or a string");
}
DerivationOptions
DerivationOptions::fromStructuredAttrs(const StringMap & env, const StructuredAttrs * parsed, bool shouldWarn)
DerivationOptions<SingleDerivedPath> derivationOptionsFromStructuredAttrs(
const StoreDirConfig & store,
const DerivedPathMap<StringSet> & inputDrvs,
const StringMap & env,
const StructuredAttrs * parsed,
bool shouldWarn,
const ExperimentalFeatureSettings & mockXpSettings)
{
DerivationOptions defaults = {};
DerivationOptions<SingleDerivedPath> defaults = {};
std::map<std::string, SingleDerivedPath::Built> placeholders;
if (mockXpSettings.isEnabled(Xp::CaDerivations)) {
/* Initialize placeholder map from inputDrvs */
auto initPlaceholders = [&](this const auto & initPlaceholders,
ref<const SingleDerivedPath> basePath,
const DerivedPathMap<StringSet>::ChildNode & node) -> void {
for (const auto & outputName : node.value) {
auto built = SingleDerivedPath::Built{
.drvPath = basePath,
.output = outputName,
};
placeholders.insert_or_assign(
DownstreamPlaceholder::fromSingleDerivedPathBuilt(built, mockXpSettings).render(),
std::move(built));
}
for (const auto & [outputName, childNode] : node.childMap) {
initPlaceholders(
make_ref<const SingleDerivedPath>(SingleDerivedPath::Built{
.drvPath = basePath,
.output = outputName,
}),
childNode);
}
};
for (const auto & [drvPath, outputs] : inputDrvs.map) {
auto basePath = make_ref<const SingleDerivedPath>(SingleDerivedPath::Opaque{drvPath});
initPlaceholders(basePath, outputs);
}
}
auto parseSingleDerivedPath = [&](const std::string & pathS) -> SingleDerivedPath {
if (auto it = placeholders.find(pathS); it != placeholders.end())
return it->second;
else
return SingleDerivedPath::Opaque{store.toStorePath(pathS).first};
};
auto parseRef = [&](const std::string & pathS) -> DrvRef<SingleDerivedPath> {
if (auto it = placeholders.find(pathS); it != placeholders.end())
return it->second;
if (store.isStorePath(pathS))
return SingleDerivedPath::Opaque{store.toStorePath(pathS).first};
else
return pathS;
};
if (shouldWarn && parsed) {
auto & structuredAttrs = parsed->structuredAttrs;
@ -146,14 +226,14 @@ DerivationOptions::fromStructuredAttrs(const StringMap & env, const StructuredAt
}
return {
.outputChecks = [&]() -> OutputChecksVariant {
.outputChecks = [&]() -> OutputChecksVariant<SingleDerivedPath> {
if (parsed) {
auto & structuredAttrs = parsed->structuredAttrs;
std::map<std::string, OutputChecks> res;
std::map<std::string, OutputChecks<SingleDerivedPath>> res;
if (auto * outputChecks = get(structuredAttrs, "outputChecks")) {
for (auto & [outputName, output_] : getObject(*outputChecks)) {
OutputChecks checks;
OutputChecks<SingleDerivedPath> checks;
auto & output = getObject(output_);
@ -163,13 +243,14 @@ DerivationOptions::fromStructuredAttrs(const StringMap & env, const StructuredAt
if (auto maxClosureSize = get(output, "maxClosureSize"))
checks.maxClosureSize = maxClosureSize->get<uint64_t>();
auto get_ = [&output = output](const std::string & name) -> std::optional<StringSet> {
auto get_ =
[&](const std::string & name) -> std::optional<std::set<DrvRef<SingleDerivedPath>>> {
if (auto i = get(output, name)) {
StringSet res;
std::set<DrvRef<SingleDerivedPath>> res;
for (auto j = i->begin(); j != i->end(); ++j) {
if (!j->is_string())
throw Error("attribute '%s' must be a list of strings", name);
res.insert(j->get<std::string>());
res.insert(parseRef(j->get<std::string>()));
}
return res;
}
@ -178,7 +259,7 @@ DerivationOptions::fromStructuredAttrs(const StringMap & env, const StructuredAt
res.insert_or_assign(
outputName,
OutputChecks{
OutputChecks<SingleDerivedPath>{
.maxSize = [&]() -> std::optional<uint64_t> {
if (auto maxSize = get(output, "maxSize"))
return maxSize->get<uint64_t>();
@ -192,21 +273,32 @@ DerivationOptions::fromStructuredAttrs(const StringMap & env, const StructuredAt
return std::nullopt;
}(),
.allowedReferences = get_("allowedReferences"),
.disallowedReferences = get_("disallowedReferences").value_or(StringSet{}),
.disallowedReferences =
get_("disallowedReferences").value_or(std::set<DrvRef<SingleDerivedPath>>{}),
.allowedRequisites = get_("allowedRequisites"),
.disallowedRequisites = get_("disallowedRequisites").value_or(StringSet{}),
.disallowedRequisites =
get_("disallowedRequisites").value_or(std::set<DrvRef<SingleDerivedPath>>{}),
});
}
}
return res;
} else {
return OutputChecks{
auto parseRefSet = [&](const std::optional<StringSet> optionalStringSet)
-> std::optional<std::set<DrvRef<SingleDerivedPath>>> {
if (!optionalStringSet)
return std::nullopt;
auto range = *optionalStringSet | std::views::transform(parseRef);
return std::set<DrvRef<SingleDerivedPath>>(range.begin(), range.end());
};
return OutputChecks<SingleDerivedPath>{
// legacy non-structured-attributes case
.ignoreSelfRefs = true,
.allowedReferences = getStringSetAttr(env, parsed, "allowedReferences"),
.disallowedReferences = getStringSetAttr(env, parsed, "disallowedReferences").value_or(StringSet{}),
.allowedRequisites = getStringSetAttr(env, parsed, "allowedRequisites"),
.disallowedRequisites = getStringSetAttr(env, parsed, "disallowedRequisites").value_or(StringSet{}),
.allowedReferences = parseRefSet(getStringSetAttr(env, parsed, "allowedReferences")),
.disallowedReferences = parseRefSet(getStringSetAttr(env, parsed, "disallowedReferences"))
.value_or(std::set<DrvRef<SingleDerivedPath>>{}),
.allowedRequisites = parseRefSet(getStringSetAttr(env, parsed, "allowedRequisites")),
.disallowedRequisites = parseRefSet(getStringSetAttr(env, parsed, "disallowedRequisites"))
.value_or(std::set<DrvRef<SingleDerivedPath>>{}),
};
}
}(),
@ -245,16 +337,19 @@ DerivationOptions::fromStructuredAttrs(const StringMap & env, const StructuredAt
}(),
.exportReferencesGraph =
[&] {
std::map<std::string, StringSet> ret;
std::map<std::string, std::set<SingleDerivedPath>> ret;
if (parsed) {
auto * e = optionalValueAt(parsed->structuredAttrs, "exportReferencesGraph");
if (!e || !e->is_object())
return ret;
for (auto & [key, value] : getObject(*e)) {
for (auto & [key, storePathsJson] : getObject(*e)) {
StringSet ss;
flatten(value, ss);
ret.insert_or_assign(key, std::move(ss));
flatten(storePathsJson, ss);
std::set<SingleDerivedPath> storePaths;
for (auto & s : ss)
storePaths.insert(parseSingleDerivedPath(s));
ret.insert_or_assign(key, std::move(storePaths));
}
} else {
auto s = getOr(env, "exportReferencesGraph", "");
@ -268,7 +363,7 @@ DerivationOptions::fromStructuredAttrs(const StringMap & env, const StructuredAt
throw Error("invalid file name '%s' in 'exportReferencesGraph'", fileName);
auto & storePathS = *i++;
ret.insert_or_assign(std::move(fileName), StringSet{storePathS});
ret.insert_or_assign(std::move(fileName), std::set{parseSingleDerivedPath(storePathS)});
}
}
return ret;
@ -286,28 +381,8 @@ DerivationOptions::fromStructuredAttrs(const StringMap & env, const StructuredAt
};
}
std::map<std::string, StorePathSet>
DerivationOptions::getParsedExportReferencesGraph(const StoreDirConfig & store) const
{
std::map<std::string, StorePathSet> res;
for (auto & [fileName, ss] : exportReferencesGraph) {
StorePathSet storePaths;
for (auto & storePathS : ss) {
if (!store.isInStore(storePathS))
throw BuildError(
BuildResult::Failure::InputRejected,
"'exportReferencesGraph' contains a non-store path '%1%'",
storePathS);
storePaths.insert(store.toStorePath(storePathS).first);
}
res.insert_or_assign(fileName, storePaths);
}
return res;
}
StringSet DerivationOptions::getRequiredSystemFeatures(const BasicDerivation & drv) const
template<typename Input>
StringSet DerivationOptions<Input>::getRequiredSystemFeatures(const BasicDerivation & drv) const
{
// FIXME: cache this?
StringSet res;
@ -318,7 +393,8 @@ StringSet DerivationOptions::getRequiredSystemFeatures(const BasicDerivation & d
return res;
}
bool DerivationOptions::canBuildLocally(Store & localStore, const BasicDerivation & drv) const
template<typename Input>
bool DerivationOptions<Input>::canBuildLocally(Store & localStore, const BasicDerivation & drv) const
{
if (drv.platform != settings.thisSystem.get() && !settings.extraPlatforms.get().count(drv.platform)
&& !drv.isBuiltin())
@ -334,42 +410,194 @@ bool DerivationOptions::canBuildLocally(Store & localStore, const BasicDerivatio
return true;
}
bool DerivationOptions::willBuildLocally(Store & localStore, const BasicDerivation & drv) const
template<typename Input>
bool DerivationOptions<Input>::willBuildLocally(Store & localStore, const BasicDerivation & drv) const
{
return preferLocalBuild && canBuildLocally(localStore, drv);
}
bool DerivationOptions::substitutesAllowed() const
template<typename Input>
bool DerivationOptions<Input>::substitutesAllowed() const
{
return settings.alwaysAllowSubstitutes ? true : allowSubstitutes;
}
bool DerivationOptions::useUidRange(const BasicDerivation & drv) const
template<typename Input>
bool DerivationOptions<Input>::useUidRange(const BasicDerivation & drv) const
{
return getRequiredSystemFeatures(drv).count("uid-range");
}
std::optional<DerivationOptions<StorePath>> tryResolve(
const DerivationOptions<SingleDerivedPath> & drvOptions,
std::function<std::optional<StorePath>(ref<const SingleDerivedPath> drvPath, const std::string & outputName)>
queryResolutionChain)
{
auto tryResolvePath = [&](const SingleDerivedPath & input) -> std::optional<StorePath> {
return std::visit(
overloaded{
[](const SingleDerivedPath::Opaque & p) -> std::optional<StorePath> { return p.path; },
[&](const SingleDerivedPath::Built & p) -> std::optional<StorePath> {
return queryResolutionChain(p.drvPath, p.output);
}},
input.raw());
};
auto tryResolveRef = [&](const DrvRef<SingleDerivedPath> & ref) -> std::optional<DrvRef<StorePath>> {
return std::visit(
overloaded{
[](const OutputName & outputName) -> std::optional<DrvRef<StorePath>> { return outputName; },
[&](const SingleDerivedPath & input) -> std::optional<DrvRef<StorePath>> {
return tryResolvePath(input);
}},
ref);
};
auto tryResolveRefSet =
[&](const std::set<DrvRef<SingleDerivedPath>> & refSet) -> std::optional<std::set<DrvRef<StorePath>>> {
std::set<DrvRef<StorePath>> resolvedSet;
for (const auto & ref : refSet) {
auto resolvedRef = tryResolveRef(ref);
if (!resolvedRef)
return std::nullopt;
resolvedSet.insert(*resolvedRef);
}
return resolvedSet;
};
// Helper function to try resolving OutputChecks using functional style
auto tryResolveOutputChecks = [&](const DerivationOptions<SingleDerivedPath>::OutputChecks & checks)
-> std::optional<DerivationOptions<StorePath>::OutputChecks> {
std::optional<std::set<DrvRef<StorePath>>> resolvedAllowedReferences;
if (checks.allowedReferences) {
resolvedAllowedReferences = tryResolveRefSet(*checks.allowedReferences);
if (!resolvedAllowedReferences)
return std::nullopt;
}
std::optional<std::set<DrvRef<StorePath>>> resolvedAllowedRequisites;
if (checks.allowedRequisites) {
resolvedAllowedRequisites = tryResolveRefSet(*checks.allowedRequisites);
if (!resolvedAllowedRequisites)
return std::nullopt;
}
auto resolvedDisallowedReferences = tryResolveRefSet(checks.disallowedReferences);
if (!resolvedDisallowedReferences)
return std::nullopt;
auto resolvedDisallowedRequisites = tryResolveRefSet(checks.disallowedRequisites);
if (!resolvedDisallowedRequisites)
return std::nullopt;
return DerivationOptions<StorePath>::OutputChecks{
.ignoreSelfRefs = checks.ignoreSelfRefs,
.maxSize = checks.maxSize,
.maxClosureSize = checks.maxClosureSize,
.allowedReferences = resolvedAllowedReferences,
.disallowedReferences = *resolvedDisallowedReferences,
.allowedRequisites = resolvedAllowedRequisites,
.disallowedRequisites = *resolvedDisallowedRequisites,
};
};
// Helper function to resolve exportReferencesGraph using functional style
auto tryResolveExportReferencesGraph = [&](const std::map<std::string, std::set<SingleDerivedPath>> & exportGraph)
-> std::optional<std::map<std::string, std::set<StorePath>>> {
std::map<std::string, std::set<StorePath>> resolved;
for (const auto & [name, inputPaths] : exportGraph) {
std::set<StorePath> resolvedPaths;
for (const auto & inputPath : inputPaths) {
auto resolvedPath = tryResolvePath(inputPath);
if (!resolvedPath)
return std::nullopt;
resolvedPaths.insert(*resolvedPath);
}
resolved.emplace(name, std::move(resolvedPaths));
}
return resolved;
};
// Resolve outputChecks using functional style with std::visit
auto resolvedOutputChecks = std::visit(
overloaded{
[&](const DerivationOptions<SingleDerivedPath>::OutputChecks & checks)
-> std::optional<std::variant<
DerivationOptions<StorePath>::OutputChecks,
std::map<std::string, DerivationOptions<StorePath>::OutputChecks>>> {
auto resolved = tryResolveOutputChecks(checks);
if (!resolved)
return std::nullopt;
return std::variant<
DerivationOptions<StorePath>::OutputChecks,
std::map<std::string, DerivationOptions<StorePath>::OutputChecks>>(*resolved);
},
[&](const std::map<std::string, DerivationOptions<SingleDerivedPath>::OutputChecks> & checksMap)
-> std::optional<std::variant<
DerivationOptions<StorePath>::OutputChecks,
std::map<std::string, DerivationOptions<StorePath>::OutputChecks>>> {
std::map<std::string, DerivationOptions<StorePath>::OutputChecks> resolvedMap;
for (const auto & [outputName, checks] : checksMap) {
auto resolved = tryResolveOutputChecks(checks);
if (!resolved)
return std::nullopt;
resolvedMap.emplace(outputName, *resolved);
}
return std::variant<
DerivationOptions<StorePath>::OutputChecks,
std::map<std::string, DerivationOptions<StorePath>::OutputChecks>>(resolvedMap);
}},
drvOptions.outputChecks);
if (!resolvedOutputChecks)
return std::nullopt;
// Resolve exportReferencesGraph
auto resolvedExportGraph = tryResolveExportReferencesGraph(drvOptions.exportReferencesGraph);
if (!resolvedExportGraph)
return std::nullopt;
// Return resolved DerivationOptions using designated initializers
return DerivationOptions<StorePath>{
.outputChecks = *resolvedOutputChecks,
.unsafeDiscardReferences = drvOptions.unsafeDiscardReferences,
.passAsFile = drvOptions.passAsFile,
.exportReferencesGraph = *resolvedExportGraph,
.additionalSandboxProfile = drvOptions.additionalSandboxProfile,
.noChroot = drvOptions.noChroot,
.impureHostDeps = drvOptions.impureHostDeps,
.impureEnvVars = drvOptions.impureEnvVars,
.allowLocalNetworking = drvOptions.allowLocalNetworking,
.requiredSystemFeatures = drvOptions.requiredSystemFeatures,
.preferLocalBuild = drvOptions.preferLocalBuild,
.allowSubstitutes = drvOptions.allowSubstitutes,
};
}
template struct DerivationOptions<StorePath>;
template struct DerivationOptions<SingleDerivedPath>;
} // namespace nix
namespace nlohmann {
using namespace nix;
DerivationOptions adl_serializer<DerivationOptions>::from_json(const json & json_)
DerivationOptions<SingleDerivedPath> adl_serializer<DerivationOptions<SingleDerivedPath>>::from_json(const json & json_)
{
auto & json = getObject(json_);
return {
.outputChecks = [&]() -> OutputChecksVariant {
.outputChecks = [&]() -> OutputChecksVariant<SingleDerivedPath> {
auto outputChecks = getObject(valueAt(json, "outputChecks"));
auto forAllOutputsOpt = optionalValueAt(outputChecks, "forAllOutputs");
auto perOutputOpt = optionalValueAt(outputChecks, "perOutput");
if (forAllOutputsOpt && !perOutputOpt) {
return static_cast<OutputChecks>(*forAllOutputsOpt);
return static_cast<OutputChecks<SingleDerivedPath>>(*forAllOutputsOpt);
} else if (perOutputOpt && !forAllOutputsOpt) {
return static_cast<std::map<std::string, OutputChecks>>(*perOutputOpt);
return static_cast<std::map<std::string, OutputChecks<SingleDerivedPath>>>(*perOutputOpt);
} else {
throw Error("Exactly one of 'perOutput' or 'forAllOutputs' is required");
}
@ -377,7 +605,7 @@ DerivationOptions adl_serializer<DerivationOptions>::from_json(const json & json
.unsafeDiscardReferences = valueAt(json, "unsafeDiscardReferences"),
.passAsFile = getStringSet(valueAt(json, "passAsFile")),
.exportReferencesGraph = getMap<StringSet>(getObject(valueAt(json, "exportReferencesGraph")), getStringSet),
.exportReferencesGraph = valueAt(json, "exportReferencesGraph"),
.additionalSandboxProfile = getString(valueAt(json, "additionalSandboxProfile")),
.noChroot = getBoolean(valueAt(json, "noChroot")),
@ -391,16 +619,17 @@ DerivationOptions adl_serializer<DerivationOptions>::from_json(const json & json
};
}
void adl_serializer<DerivationOptions>::to_json(json & json, const DerivationOptions & o)
void adl_serializer<DerivationOptions<SingleDerivedPath>>::to_json(
json & json, const DerivationOptions<SingleDerivedPath> & o)
{
json["outputChecks"] = std::visit(
overloaded{
[&](const OutputChecks & checks) {
[&](const OutputChecks<SingleDerivedPath> & checks) {
nlohmann::json outputChecks;
outputChecks["forAllOutputs"] = checks;
return outputChecks;
},
[&](const std::map<std::string, OutputChecks> & checksPerOutput) {
[&](const std::map<std::string, OutputChecks<SingleDerivedPath>> & checksPerOutput) {
nlohmann::json outputChecks;
outputChecks["perOutput"] = checksPerOutput;
return outputChecks;
@ -423,7 +652,7 @@ void adl_serializer<DerivationOptions>::to_json(json & json, const DerivationOpt
json["allowSubstitutes"] = o.allowSubstitutes;
}
DerivationOptions::OutputChecks adl_serializer<DerivationOptions::OutputChecks>::from_json(const json & json_)
OutputChecks<SingleDerivedPath> adl_serializer<OutputChecks<SingleDerivedPath>>::from_json(const json & json_)
{
auto & json = getObject(json_);
@ -431,14 +660,16 @@ DerivationOptions::OutputChecks adl_serializer<DerivationOptions::OutputChecks>:
.ignoreSelfRefs = getBoolean(valueAt(json, "ignoreSelfRefs")),
.maxSize = ptrToOwned<uint64_t>(getNullable(valueAt(json, "maxSize"))),
.maxClosureSize = ptrToOwned<uint64_t>(getNullable(valueAt(json, "maxClosureSize"))),
.allowedReferences = ptrToOwned<StringSet>(getNullable(valueAt(json, "allowedReferences"))),
.disallowedReferences = getStringSet(valueAt(json, "disallowedReferences")),
.allowedRequisites = ptrToOwned<StringSet>(getNullable(valueAt(json, "allowedRequisites"))),
.disallowedRequisites = getStringSet(valueAt(json, "disallowedRequisites")),
.allowedReferences =
ptrToOwned<std::set<DrvRef<SingleDerivedPath>>>(getNullable(valueAt(json, "allowedReferences"))),
.disallowedReferences = valueAt(json, "disallowedReferences"),
.allowedRequisites =
ptrToOwned<std::set<DrvRef<SingleDerivedPath>>>(getNullable(valueAt(json, "allowedRequisites"))),
.disallowedRequisites = valueAt(json, "disallowedRequisites"),
};
}
void adl_serializer<DerivationOptions::OutputChecks>::to_json(json & json, const DerivationOptions::OutputChecks & c)
void adl_serializer<OutputChecks<SingleDerivedPath>>::to_json(json & json, const OutputChecks<SingleDerivedPath> & c)
{
json["ignoreSelfRefs"] = c.ignoreSelfRefs;
json["maxSize"] = c.maxSize;

View file

@ -1,5 +1,6 @@
#include "nix/store/downstream-placeholder.hh"
#include "nix/store/derivations.hh"
#include "nix/util/json-utils.hh"
namespace nix {
@ -49,3 +50,45 @@ DownstreamPlaceholder DownstreamPlaceholder::fromSingleDerivedPathBuilt(
}
} // namespace nix
namespace nlohmann {
using namespace nix;
template<typename Item>
DrvRef<Item> adl_serializer<DrvRef<Item>>::from_json(const json & json)
{
// OutputName case: { "drvPath": "self", "output": <output> }
if (json.type() == nlohmann::json::value_t::object) {
auto & obj = getObject(json);
if (auto * drvPath_ = get(obj, "drvPath")) {
auto & drvPath = *drvPath_;
if (drvPath.type() == nlohmann::json::value_t::string && getString(drvPath) == "self") {
return getString(valueAt(obj, "output"));
}
}
}
// Input case
return adl_serializer<Item>::from_json(json);
}
template<typename Item>
void adl_serializer<DrvRef<Item>>::to_json(json & json, const DrvRef<Item> & ref)
{
std::visit(
overloaded{
[&](const OutputName & outputName) {
json = nlohmann::json::object();
json["drvPath"] = "self";
json["output"] = outputName;
},
[&](const Item & item) { json = item; },
},
ref);
}
template struct adl_serializer<nix::DrvRef<StorePath>>;
template struct adl_serializer<nix::DrvRef<SingleDerivedPath>>;
} // namespace nlohmann

View file

@ -69,7 +69,7 @@ struct DerivationBuilderParams
*
* @todo this should be part of `Derivation`.
*/
const DerivationOptions & drvOptions;
const DerivationOptions<StorePath> & drvOptions;
// The remainder is state held during the build.

View file

@ -52,8 +52,6 @@ private:
*/
std::unique_ptr<Derivation> drv;
std::unique_ptr<DerivationOptions> drvOptions;
/**
* The remainder is state held during the build.
*/
@ -115,7 +113,8 @@ private:
/**
* Is the build hook willing to perform the build?
*/
HookReply tryBuildHook(const std::map<std::string, InitialOutput> & initialOutputs);
HookReply tryBuildHook(
const std::map<std::string, InitialOutput> & initialOutputs, const DerivationOptions<StorePath> & drvOptions);
/**
* Open a log file and a pipe to it.

View file

@ -8,6 +8,7 @@ namespace nix {
class Store;
struct Derivation;
template<typename Input>
struct DerivationOptions;
/**
@ -77,7 +78,10 @@ struct DesugaredEnv
* just part of `Derivation`.
*/
static DesugaredEnv create(
Store & store, const Derivation & drv, const DerivationOptions & drvOptions, const StorePathSet & inputPaths);
Store & store,
const Derivation & drv,
const DerivationOptions<StorePath> & drvOptions,
const StorePathSet & inputPaths);
};
} // namespace nix

View file

@ -8,7 +8,8 @@
#include "nix/util/types.hh"
#include "nix/util/json-impls.hh"
#include "nix/store/path.hh"
#include "nix/store/store-dir-config.hh"
#include "nix/store/downstream-placeholder.hh"
namespace nix {
@ -17,6 +18,9 @@ struct StoreDirConfig;
struct BasicDerivation;
struct StructuredAttrs;
template<typename V>
struct DerivedPathMap;
/**
* This represents all the special options on a `Derivation`.
*
@ -34,6 +38,7 @@ struct StructuredAttrs;
* separately. That would be nice to separate concerns, and not make any
* environment variable names magical.
*/
template<typename Input>
struct DerivationOptions
{
struct OutputChecks
@ -41,13 +46,15 @@ struct DerivationOptions
bool ignoreSelfRefs = false;
std::optional<uint64_t> maxSize, maxClosureSize;
using DrvRef = nix::DrvRef<Input>;
/**
* env: allowedReferences
*
* A value of `nullopt` indicates that the check is skipped.
* This means that all references are allowed.
*/
std::optional<StringSet> allowedReferences;
std::optional<std::set<DrvRef>> allowedReferences;
/**
* env: disallowedReferences
@ -55,21 +62,21 @@ struct DerivationOptions
* No needed for `std::optional`, because skipping the check is
* the same as disallowing the references.
*/
StringSet disallowedReferences;
std::set<DrvRef> disallowedReferences;
/**
* env: allowedRequisites
*
* See `allowedReferences`
*/
std::optional<StringSet> allowedRequisites;
std::optional<std::set<DrvRef>> allowedRequisites;
/**
* env: disallowedRequisites
*
* See `disallowedReferences`
*/
StringSet disallowedRequisites;
std::set<DrvRef> disallowedRequisites;
bool operator==(const OutputChecks &) const = default;
};
@ -116,23 +123,7 @@ struct DerivationOptions
* attributes give to the builder. The set of paths in the original JSON
* is replaced with a list of `PathInfo` in JSON format.
*/
std::map<std::string, StringSet> exportReferencesGraph;
/**
* Once a derivations is resolved, the strings in in
* `exportReferencesGraph` should all be store paths (with possible
* suffix paths, but those are discarded).
*
* @return The parsed path set for for each key in the map.
*
* @todo Ideally, `exportReferencesGraph` would just store
* `StorePath`s for this, but we can't just do that, because for CA
* derivations they is actually in general `DerivedPath`s (via
* placeholder strings) until the derivation is resolved and exact
* inputs store paths are known. We can use better types for that
* too, but that is a longer project.
*/
std::map<std::string, StorePathSet> getParsedExportReferencesGraph(const StoreDirConfig & store) const;
std::map<std::string, std::set<Input>> exportReferencesGraph;
/**
* env: __sandboxProfile
@ -185,18 +176,6 @@ struct DerivationOptions
bool operator==(const DerivationOptions &) const = default;
/**
* Parse this information from its legacy encoding as part of the
* environment. This should not be used with nice greenfield formats
* (e.g. JSON) but is necessary for supporting old formats (e.g.
* ATerm).
*/
static DerivationOptions
fromStructuredAttrs(const StringMap & env, const StructuredAttrs * parsed, bool shouldWarn = true);
static DerivationOptions
fromStructuredAttrs(const StringMap & env, const std::optional<StructuredAttrs> & parsed, bool shouldWarn = true);
/**
* @param drv Must be the same derivation we parsed this from. In
* the future we'll flip things around so a `BasicDerivation` has
@ -222,7 +201,49 @@ struct DerivationOptions
bool useUidRange(const BasicDerivation & drv) const;
};
extern template struct DerivationOptions<StorePath>;
extern template struct DerivationOptions<SingleDerivedPath>;
struct DerivationOutput;
/**
* Parse this information from its legacy encoding as part of the
* environment. This should not be used with nice greenfield formats
* (e.g. JSON) but is necessary for supporting old formats (e.g.
* ATerm).
*/
DerivationOptions<SingleDerivedPath> derivationOptionsFromStructuredAttrs(
const StoreDirConfig & store,
const DerivedPathMap<StringSet> & inputDrvs,
const StringMap & env,
const StructuredAttrs * parsed,
bool shouldWarn = true,
const ExperimentalFeatureSettings & mockXpSettings = experimentalFeatureSettings);
DerivationOptions<StorePath> derivationOptionsFromStructuredAttrs(
const StoreDirConfig & store,
const StringMap & env,
const StructuredAttrs * parsed,
bool shouldWarn = true,
const ExperimentalFeatureSettings & mockXpSettings = experimentalFeatureSettings);
/**
* This is the counterpart of `Derivation::tryResolve`. In particular,
* it takes the same sort of callback, which is used to reolve
* non-constant deriving paths.
*
* We need this function when resolving a derivation, and we will use
* this as part of that if/when `Derivation` includes
* `DerivationOptions`
*/
std::optional<DerivationOptions<StorePath>> tryResolve(
const DerivationOptions<SingleDerivedPath> & drvOptions,
std::function<std::optional<StorePath>(ref<const SingleDerivedPath> drvPath, const std::string & outputName)>
queryResolutionChain);
}; // namespace nix
JSON_IMPL(DerivationOptions);
JSON_IMPL(DerivationOptions::OutputChecks)
JSON_IMPL(nix::DerivationOptions<nix::StorePath>);
JSON_IMPL(nix::DerivationOptions<nix::SingleDerivedPath>);
JSON_IMPL(nix::DerivationOptions<nix::StorePath>::OutputChecks)
JSON_IMPL(nix::DerivationOptions<nix::SingleDerivedPath>::OutputChecks)

View file

@ -2,11 +2,23 @@
///@file
#include "nix/util/hash.hh"
#include "nix/util/json-impls.hh"
#include "nix/store/path.hh"
#include "nix/store/derived-path.hh"
namespace nix {
/**
* A reference is either to a to-be-registered output (by name),
* or to an already-registered store object (by `Input`).
*
* `Ref<SingleDerivedPath` is a representation of something that can be
* turned into a placeholder. (Regular own-output placeholder in the
* first case, `DownstreamPlaceholder` in the second case.)
*/
template<typename Input>
using DrvRef = std::variant<OutputName, Input>;
/**
* Downstream Placeholders are opaque and almost certainly unique values
* used to allow derivations to refer to store objects which are yet to
@ -92,3 +104,17 @@ public:
};
} // namespace nix
namespace nlohmann {
template<typename Item>
struct adl_serializer<nix::DrvRef<Item>>
{
static nix::DrvRef<Item> from_json(const json & json);
static void to_json(json & json, const nix::DrvRef<Item> & t);
};
extern template struct adl_serializer<nix::DrvRef<nix::StorePath>>;
extern template struct adl_serializer<nix::DrvRef<nix::SingleDerivedPath>>;
} // namespace nlohmann

View file

@ -9,6 +9,7 @@
namespace nix {
class Store;
template<typename Input>
struct DerivationOptions;
struct DerivationOutput;
@ -47,7 +48,7 @@ struct StructuredAttrs
nlohmann::json::object_t prepareStructuredAttrs(
Store & store,
const DerivationOptions & drvOptions,
const DerivationOptions<StorePath> & drvOptions,
const StorePathSet & inputPaths,
const DerivationOutputs & outputs) const;

View file

@ -225,11 +225,12 @@ MissingPaths Store::queryMissing(const std::vector<DerivedPath> & targets)
return;
auto drv = make_ref<Derivation>(derivationFromPath(drvPath));
DerivationOptions drvOptions;
DerivationOptions<SingleDerivedPath> drvOptions;
try {
// FIXME: this is a lot of work just to get the value
// of `allowSubstitutes`.
drvOptions = DerivationOptions::fromStructuredAttrs(drv->env, drv->structuredAttrs);
drvOptions = derivationOptionsFromStructuredAttrs(
*this, drv->inputDrvs, drv->env, get(drv->structuredAttrs));
} catch (Error & e) {
e.addTrace({}, "while parsing derivation '%s'", printStorePath(drvPath));
throw;

View file

@ -100,7 +100,7 @@ static nlohmann::json pathInfoToJSON(Store & store, const StorePathSet & storePa
nlohmann::json::object_t StructuredAttrs::prepareStructuredAttrs(
Store & store,
const DerivationOptions & drvOptions,
const DerivationOptions<StorePath> & drvOptions,
const StorePathSet & inputPaths,
const DerivationOutputs & outputs) const
{
@ -114,8 +114,8 @@ nlohmann::json::object_t StructuredAttrs::prepareStructuredAttrs(
json["outputs"] = std::move(outputsJson);
/* Handle exportReferencesGraph. */
for (auto & [key, storePaths] : drvOptions.getParsedExportReferencesGraph(store)) {
json[key] = pathInfoToJSON(store, store.exportReferences(storePaths, storePaths));
for (auto & [key, storePaths] : drvOptions.exportReferencesGraph) {
json[key] = pathInfoToJSON(store, store.exportReferences(storePaths, inputPaths));
}
return json;