diff --git a/src/libstore-tests/derivation-advanced-attrs.cc b/src/libstore-tests/derivation-advanced-attrs.cc index 9d2c64ef3..107cf13e3 100644 --- a/src/libstore-tests/derivation-advanced-attrs.cc +++ b/src/libstore-tests/derivation-advanced-attrs.cc @@ -3,13 +3,15 @@ #include "experimental-features.hh" #include "derivations.hh" - -#include "tests/libstore.hh" -#include "tests/characterization.hh" +#include "derivations.hh" +#include "derivation-options.hh" #include "parsed-derivations.hh" #include "types.hh" #include "json-utils.hh" +#include "tests/libstore.hh" +#include "tests/characterization.hh" + namespace nix { using nlohmann::json; @@ -80,21 +82,30 @@ TEST_F(DerivationAdvancedAttrsTest, Derivation_advancedAttributes_defaults) auto drvPath = writeDerivation(*store, got, NoRepair, true); ParsedDerivation parsedDrv(drvPath, got); + DerivationOptions options = DerivationOptions::fromParsedDerivation(parsedDrv); - EXPECT_EQ(parsedDrv.getStringAttr("__sandboxProfile").value_or(""), ""); - EXPECT_EQ(parsedDrv.getBoolAttr("__noChroot"), false); - EXPECT_EQ(parsedDrv.getStringsAttr("__impureHostDeps").value_or(Strings()), Strings()); - EXPECT_EQ(parsedDrv.getStringsAttr("impureEnvVars").value_or(Strings()), Strings()); - EXPECT_EQ(parsedDrv.getBoolAttr("__darwinAllowLocalNetworking"), false); - EXPECT_EQ(parsedDrv.getStringsAttr("allowedReferences"), std::nullopt); - EXPECT_EQ(parsedDrv.getStringsAttr("allowedRequisites"), std::nullopt); - EXPECT_EQ(parsedDrv.getStringsAttr("disallowedReferences"), std::nullopt); - EXPECT_EQ(parsedDrv.getStringsAttr("disallowedRequisites"), std::nullopt); - EXPECT_EQ(parsedDrv.getRequiredSystemFeatures(), StringSet()); - EXPECT_EQ(parsedDrv.canBuildLocally(*store), false); - EXPECT_EQ(parsedDrv.willBuildLocally(*store), false); - EXPECT_EQ(parsedDrv.substitutesAllowed(), true); - EXPECT_EQ(parsedDrv.useUidRange(), false); + EXPECT_TRUE(!parsedDrv.hasStructuredAttrs()); + + EXPECT_EQ(options.additionalSandboxProfile, ""); + EXPECT_EQ(options.noChroot, false); + EXPECT_EQ(options.impureHostDeps, StringSet{}); + EXPECT_EQ(options.impureEnvVars, StringSet{}); + EXPECT_EQ(options.allowLocalNetworking, false); + { + auto * checksForAllOutputs_ = std::get_if<0>(&options.outputChecks); + ASSERT_TRUE(checksForAllOutputs_ != nullptr); + auto & checksForAllOutputs = *checksForAllOutputs_; + + EXPECT_EQ(checksForAllOutputs.allowedReferences, std::nullopt); + EXPECT_EQ(checksForAllOutputs.allowedRequisites, std::nullopt); + EXPECT_EQ(checksForAllOutputs.disallowedReferences, StringSet{}); + EXPECT_EQ(checksForAllOutputs.disallowedRequisites, StringSet{}); + } + EXPECT_EQ(options.getRequiredSystemFeatures(got), StringSet()); + EXPECT_EQ(options.canBuildLocally(*store, got), false); + EXPECT_EQ(options.willBuildLocally(*store, got), false); + EXPECT_EQ(options.substitutesAllowed(), true); + EXPECT_EQ(options.useUidRange(got), false); }); }; @@ -106,29 +117,36 @@ TEST_F(DerivationAdvancedAttrsTest, Derivation_advancedAttributes) auto drvPath = writeDerivation(*store, got, NoRepair, true); ParsedDerivation parsedDrv(drvPath, got); + DerivationOptions options = DerivationOptions::fromParsedDerivation(parsedDrv); StringSet systemFeatures{"rainbow", "uid-range"}; - EXPECT_EQ(parsedDrv.getStringAttr("__sandboxProfile").value_or(""), "sandcastle"); - EXPECT_EQ(parsedDrv.getBoolAttr("__noChroot"), true); - EXPECT_EQ(parsedDrv.getStringsAttr("__impureHostDeps").value_or(Strings()), Strings{"/usr/bin/ditto"}); - EXPECT_EQ(parsedDrv.getStringsAttr("impureEnvVars").value_or(Strings()), Strings{"UNICORN"}); - EXPECT_EQ(parsedDrv.getBoolAttr("__darwinAllowLocalNetworking"), true); - EXPECT_EQ( - parsedDrv.getStringsAttr("allowedReferences"), Strings{"/nix/store/3c08bzb71z4wiag719ipjxr277653ynp-foo"}); - EXPECT_EQ( - parsedDrv.getStringsAttr("allowedRequisites"), Strings{"/nix/store/3c08bzb71z4wiag719ipjxr277653ynp-foo"}); - EXPECT_EQ( - parsedDrv.getStringsAttr("disallowedReferences"), - Strings{"/nix/store/7rhsm8i393hm1wcsmph782awg1hi2f7x-bar"}); - EXPECT_EQ( - parsedDrv.getStringsAttr("disallowedRequisites"), - Strings{"/nix/store/7rhsm8i393hm1wcsmph782awg1hi2f7x-bar"}); - EXPECT_EQ(parsedDrv.getRequiredSystemFeatures(), systemFeatures); - EXPECT_EQ(parsedDrv.canBuildLocally(*store), false); - EXPECT_EQ(parsedDrv.willBuildLocally(*store), false); - EXPECT_EQ(parsedDrv.substitutesAllowed(), false); - EXPECT_EQ(parsedDrv.useUidRange(), true); + EXPECT_TRUE(!parsedDrv.hasStructuredAttrs()); + + EXPECT_EQ(options.additionalSandboxProfile, "sandcastle"); + EXPECT_EQ(options.noChroot, true); + EXPECT_EQ(options.impureHostDeps, StringSet{"/usr/bin/ditto"}); + EXPECT_EQ(options.impureEnvVars, StringSet{"UNICORN"}); + EXPECT_EQ(options.allowLocalNetworking, true); + { + auto * checksForAllOutputs_ = std::get_if<0>(&options.outputChecks); + ASSERT_TRUE(checksForAllOutputs_ != nullptr); + auto & checksForAllOutputs = *checksForAllOutputs_; + + EXPECT_EQ( + checksForAllOutputs.allowedReferences, StringSet{"/nix/store/3c08bzb71z4wiag719ipjxr277653ynp-foo"}); + EXPECT_EQ( + checksForAllOutputs.allowedRequisites, StringSet{"/nix/store/3c08bzb71z4wiag719ipjxr277653ynp-foo"}); + EXPECT_EQ( + checksForAllOutputs.disallowedReferences, StringSet{"/nix/store/7rhsm8i393hm1wcsmph782awg1hi2f7x-bar"}); + EXPECT_EQ( + checksForAllOutputs.disallowedRequisites, StringSet{"/nix/store/7rhsm8i393hm1wcsmph782awg1hi2f7x-bar"}); + } + EXPECT_EQ(options.getRequiredSystemFeatures(got), systemFeatures); + EXPECT_EQ(options.canBuildLocally(*store, got), false); + EXPECT_EQ(options.willBuildLocally(*store, got), false); + EXPECT_EQ(options.substitutesAllowed(), false); + EXPECT_EQ(options.useUidRange(got), true); }); }; @@ -140,27 +158,29 @@ TEST_F(DerivationAdvancedAttrsTest, Derivation_advancedAttributes_structuredAttr auto drvPath = writeDerivation(*store, got, NoRepair, true); ParsedDerivation parsedDrv(drvPath, got); + DerivationOptions options = DerivationOptions::fromParsedDerivation(parsedDrv); - EXPECT_EQ(parsedDrv.getStringAttr("__sandboxProfile").value_or(""), ""); - EXPECT_EQ(parsedDrv.getBoolAttr("__noChroot"), false); - EXPECT_EQ(parsedDrv.getStringsAttr("__impureHostDeps").value_or(Strings()), Strings()); - EXPECT_EQ(parsedDrv.getStringsAttr("impureEnvVars").value_or(Strings()), Strings()); - EXPECT_EQ(parsedDrv.getBoolAttr("__darwinAllowLocalNetworking"), false); + EXPECT_TRUE(parsedDrv.hasStructuredAttrs()); + + EXPECT_EQ(options.additionalSandboxProfile, ""); + EXPECT_EQ(options.noChroot, false); + EXPECT_EQ(options.impureHostDeps, StringSet{}); + EXPECT_EQ(options.impureEnvVars, StringSet{}); + EXPECT_EQ(options.allowLocalNetworking, false); { - auto structuredAttrs_ = parsedDrv.getStructuredAttrs(); - ASSERT_TRUE(structuredAttrs_); - auto & structuredAttrs = *structuredAttrs_; + auto * checksPerOutput_ = std::get_if<1>(&options.outputChecks); + ASSERT_TRUE(checksPerOutput_ != nullptr); + auto & checksPerOutput = *checksPerOutput_; - auto outputChecks_ = get(structuredAttrs, "outputChecks"); - ASSERT_FALSE(outputChecks_); + EXPECT_EQ(checksPerOutput.size(), 0); } - EXPECT_EQ(parsedDrv.getRequiredSystemFeatures(), StringSet()); - EXPECT_EQ(parsedDrv.canBuildLocally(*store), false); - EXPECT_EQ(parsedDrv.willBuildLocally(*store), false); - EXPECT_EQ(parsedDrv.substitutesAllowed(), true); - EXPECT_EQ(parsedDrv.useUidRange(), false); + EXPECT_EQ(options.getRequiredSystemFeatures(got), StringSet()); + EXPECT_EQ(options.canBuildLocally(*store, got), false); + EXPECT_EQ(options.willBuildLocally(*store, got), false); + EXPECT_EQ(options.substitutesAllowed(), true); + EXPECT_EQ(options.useUidRange(got), false); }); }; @@ -172,62 +192,52 @@ TEST_F(DerivationAdvancedAttrsTest, Derivation_advancedAttributes_structuredAttr auto drvPath = writeDerivation(*store, got, NoRepair, true); ParsedDerivation parsedDrv(drvPath, got); + DerivationOptions options = DerivationOptions::fromParsedDerivation(parsedDrv); StringSet systemFeatures{"rainbow", "uid-range"}; - EXPECT_EQ(parsedDrv.getStringAttr("__sandboxProfile").value_or(""), "sandcastle"); - EXPECT_EQ(parsedDrv.getBoolAttr("__noChroot"), true); - EXPECT_EQ(parsedDrv.getStringsAttr("__impureHostDeps").value_or(Strings()), Strings{"/usr/bin/ditto"}); - EXPECT_EQ(parsedDrv.getStringsAttr("impureEnvVars").value_or(Strings()), Strings{"UNICORN"}); - EXPECT_EQ(parsedDrv.getBoolAttr("__darwinAllowLocalNetworking"), true); + EXPECT_TRUE(parsedDrv.hasStructuredAttrs()); + + EXPECT_EQ(options.additionalSandboxProfile, "sandcastle"); + EXPECT_EQ(options.noChroot, true); + EXPECT_EQ(options.impureHostDeps, StringSet{"/usr/bin/ditto"}); + EXPECT_EQ(options.impureEnvVars, StringSet{"UNICORN"}); + EXPECT_EQ(options.allowLocalNetworking, true); { - auto structuredAttrs_ = parsedDrv.getStructuredAttrs(); - ASSERT_TRUE(structuredAttrs_); - auto & structuredAttrs = *structuredAttrs_; - - auto outputChecks_ = get(structuredAttrs, "outputChecks"); - ASSERT_TRUE(outputChecks_); - auto & outputChecks = *outputChecks_; - { - auto output_ = get(outputChecks, "out"); + auto output_ = get(std::get<1>(options.outputChecks), "out"); ASSERT_TRUE(output_); auto & output = *output_; - EXPECT_EQ( - get(output, "allowedReferences")->get(), - Strings{"/nix/store/3c08bzb71z4wiag719ipjxr277653ynp-foo"}); - EXPECT_EQ( - get(output, "allowedRequisites")->get(), - Strings{"/nix/store/3c08bzb71z4wiag719ipjxr277653ynp-foo"}); + + EXPECT_EQ(output.allowedReferences, StringSet{"/nix/store/3c08bzb71z4wiag719ipjxr277653ynp-foo"}); + EXPECT_EQ(output.allowedRequisites, StringSet{"/nix/store/3c08bzb71z4wiag719ipjxr277653ynp-foo"}); } { - auto output_ = get(outputChecks, "bin"); + auto output_ = get(std::get<1>(options.outputChecks), "bin"); ASSERT_TRUE(output_); auto & output = *output_; - EXPECT_EQ( - get(output, "disallowedReferences")->get(), - Strings{"/nix/store/7rhsm8i393hm1wcsmph782awg1hi2f7x-bar"}); - EXPECT_EQ( - get(output, "disallowedRequisites")->get(), - Strings{"/nix/store/7rhsm8i393hm1wcsmph782awg1hi2f7x-bar"}); + + EXPECT_EQ(output.disallowedReferences, StringSet{"/nix/store/7rhsm8i393hm1wcsmph782awg1hi2f7x-bar"}); + EXPECT_EQ(output.disallowedRequisites, StringSet{"/nix/store/7rhsm8i393hm1wcsmph782awg1hi2f7x-bar"}); } { - auto output_ = get(outputChecks, "dev"); + auto output_ = get(std::get<1>(options.outputChecks), "dev"); ASSERT_TRUE(output_); auto & output = *output_; - EXPECT_EQ(get(output, "maxSize")->get(), 789); - EXPECT_EQ(get(output, "maxClosureSize")->get(), 5909); + + EXPECT_EQ(output.maxSize, 789); + EXPECT_EQ(output.maxClosureSize, 5909); } } - EXPECT_EQ(parsedDrv.getRequiredSystemFeatures(), systemFeatures); - EXPECT_EQ(parsedDrv.canBuildLocally(*store), false); - EXPECT_EQ(parsedDrv.willBuildLocally(*store), false); - EXPECT_EQ(parsedDrv.substitutesAllowed(), false); - EXPECT_EQ(parsedDrv.useUidRange(), true); + EXPECT_EQ(options.getRequiredSystemFeatures(got), systemFeatures); + EXPECT_EQ(options.canBuildLocally(*store, got), false); + EXPECT_EQ(options.willBuildLocally(*store, got), false); + EXPECT_EQ(options.substitutesAllowed(), false); + EXPECT_EQ(options.useUidRange(got), true); }); }; diff --git a/src/libstore/build/derivation-goal.cc b/src/libstore/build/derivation-goal.cc index 3f652c0ca..41762cde1 100644 --- a/src/libstore/build/derivation-goal.cc +++ b/src/libstore/build/derivation-goal.cc @@ -168,6 +168,7 @@ Goal::Co DerivationGoal::haveDerivation() trace("have derivation"); parsedDrv = std::make_unique(drvPath, *drv); + drvOptions = std::make_unique(DerivationOptions::fromParsedDerivation(*parsedDrv)); if (!drv->type().hasKnownOutputPaths()) experimentalFeatureSettings.require(Xp::CaDerivations); @@ -224,7 +225,7 @@ Goal::Co DerivationGoal::haveDerivation() /* We are first going to try to create the invalid output paths through substitutes. If that doesn't work, we'll build them. */ - if (settings.useSubstitutes && parsedDrv->substitutesAllowed()) + if (settings.useSubstitutes && drvOptions->substitutesAllowed()) for (auto & [outputName, status] : initialOutputs) { if (!status.wanted) continue; if (!status.known) @@ -614,7 +615,7 @@ Goal::Co DerivationGoal::tryToBuild() `preferLocalBuild' set. Also, check and repair modes are only supported for local builds. */ bool buildLocally = - (buildMode != bmNormal || parsedDrv->willBuildLocally(worker.store)) + (buildMode != bmNormal || drvOptions->willBuildLocally(worker.store, *drv)) && settings.maxBuildJobs.get() != 0; if (!buildLocally) { @@ -1110,7 +1111,7 @@ HookReply DerivationGoal::tryBuildHook() << (worker.getNrLocalBuilds() < settings.maxBuildJobs ? 1 : 0) << drv->platform << worker.store.printStorePath(drvPath) - << parsedDrv->getRequiredSystemFeatures(); + << drvOptions->getRequiredSystemFeatures(*drv); worker.hook->sink.flush(); /* Read the first line of input, which should be a word indicating diff --git a/src/libstore/build/derivation-goal.hh b/src/libstore/build/derivation-goal.hh index 538840813..3ff34509a 100644 --- a/src/libstore/build/derivation-goal.hh +++ b/src/libstore/build/derivation-goal.hh @@ -2,6 +2,7 @@ ///@file #include "parsed-derivations.hh" +#include "derivation-options.hh" #ifndef _WIN32 # include "user-lock.hh" #endif @@ -147,6 +148,7 @@ struct DerivationGoal : public Goal std::unique_ptr drv; std::unique_ptr parsedDrv; + std::unique_ptr drvOptions; /** * The remainder is state held during the build. diff --git a/src/libstore/derivation-options.cc b/src/libstore/derivation-options.cc new file mode 100644 index 000000000..1fc1718f7 --- /dev/null +++ b/src/libstore/derivation-options.cc @@ -0,0 +1,274 @@ +#include "derivation-options.hh" +#include "json-utils.hh" +#include "parsed-derivations.hh" +#include "types.hh" +#include "util.hh" +#include +#include +#include + +namespace nix { + +using OutputChecks = DerivationOptions::OutputChecks; + +using OutputChecksVariant = std::variant>; + +DerivationOptions DerivationOptions::fromParsedDerivation(const ParsedDerivation & parsed, bool shouldWarn) +{ + DerivationOptions defaults = {}; + + auto structuredAttrs = parsed.structuredAttrs.get(); + + if (shouldWarn && structuredAttrs) { + if (get(*structuredAttrs, "allowedReferences")) { + warn( + "'structuredAttrs' disables the effect of the top-level attribute 'allowedReferences'; use 'outputChecks' instead"); + } + if (get(*structuredAttrs, "allowedRequisites")) { + warn( + "'structuredAttrs' disables the effect of the top-level attribute 'allowedRequisites'; use 'outputChecks' instead"); + } + if (get(*structuredAttrs, "disallowedRequisites")) { + warn( + "'structuredAttrs' disables the effect of the top-level attribute 'disallowedRequisites'; use 'outputChecks' instead"); + } + if (get(*structuredAttrs, "disallowedReferences")) { + warn( + "'structuredAttrs' disables the effect of the top-level attribute 'disallowedReferences'; use 'outputChecks' instead"); + } + if (get(*structuredAttrs, "maxSize")) { + warn( + "'structuredAttrs' disables the effect of the top-level attribute 'maxSize'; use 'outputChecks' instead"); + } + if (get(*structuredAttrs, "maxClosureSize")) { + warn( + "'structuredAttrs' disables the effect of the top-level attribute 'maxClosureSize'; use 'outputChecks' instead"); + } + } + + return { + .outputChecks = [&]() -> OutputChecksVariant { + if (auto structuredAttrs = parsed.structuredAttrs.get()) { + std::map res; + if (auto outputChecks = get(*structuredAttrs, "outputChecks")) { + for (auto & [outputName, output] : getObject(*outputChecks)) { + OutputChecks checks; + + if (auto maxSize = get(output, "maxSize")) + checks.maxSize = maxSize->get(); + + if (auto maxClosureSize = get(output, "maxClosureSize")) + checks.maxClosureSize = maxClosureSize->get(); + + auto get_ = [&](const std::string & name) -> std::optional { + if (auto i = get(output, name)) { + StringSet 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()); + } + checks.disallowedRequisites = res; + return res; + } + return {}; + }; + + checks.allowedReferences = get_("allowedReferences"); + checks.allowedRequisites = get_("allowedRequisites"); + checks.disallowedReferences = get_("disallowedReferences").value_or(StringSet{}); + checks.disallowedRequisites = get_("disallowedRequisites").value_or(StringSet{}); + ; + + res.insert_or_assign(outputName, std::move(checks)); + } + } + return res; + } else { + return OutputChecks{ + // legacy non-structured-attributes case + .ignoreSelfRefs = true, + .allowedReferences = parsed.getStringSetAttr("allowedReferences"), + .disallowedReferences = parsed.getStringSetAttr("disallowedReferences").value_or(StringSet{}), + .allowedRequisites = parsed.getStringSetAttr("allowedRequisites"), + .disallowedRequisites = parsed.getStringSetAttr("disallowedRequisites").value_or(StringSet{}), + }; + } + }(), + .unsafeDiscardReferences = + [&] { + std::map res; + + if (auto structuredAttrs = parsed.structuredAttrs.get()) { + if (auto udr = get(*structuredAttrs, "unsafeDiscardReferences")) { + for (auto & [outputName, output] : getObject(*udr)) { + if (!output.is_boolean()) + throw Error("attribute 'unsafeDiscardReferences.\"%s\"' must be a Boolean", outputName); + res.insert_or_assign(outputName, output.get()); + } + } + } + + return res; + }(), + .passAsFile = + [&] { + StringSet res; + if (auto * passAsFileString = get(parsed.drv.env, "passAsFile")) { + if (parsed.hasStructuredAttrs()) { + if (shouldWarn) { + warn( + "'structuredAttrs' disables the effect of the top-level attribute 'passAsFile'; because all JSON is always passed via file"); + } + } else { + res = tokenizeString(*passAsFileString); + } + } + return res; + }(), + .additionalSandboxProfile = + parsed.getStringAttr("__sandboxProfile").value_or(defaults.additionalSandboxProfile), + .noChroot = parsed.getBoolAttr("__noChroot", defaults.noChroot), + .impureHostDeps = parsed.getStringSetAttr("__impureHostDeps").value_or(defaults.impureHostDeps), + .impureEnvVars = parsed.getStringSetAttr("impureEnvVars").value_or(defaults.impureEnvVars), + .allowLocalNetworking = parsed.getBoolAttr("__darwinAllowLocalNetworking", defaults.allowLocalNetworking), + .requiredSystemFeatures = + parsed.getStringSetAttr("requiredSystemFeatures").value_or(defaults.requiredSystemFeatures), + .preferLocalBuild = parsed.getBoolAttr("preferLocalBuild", defaults.preferLocalBuild), + .allowSubstitutes = parsed.getBoolAttr("allowSubstitutes", defaults.allowSubstitutes), + }; +} + +StringSet DerivationOptions::getRequiredSystemFeatures(const BasicDerivation & drv) const +{ + // FIXME: cache this? + StringSet res; + for (auto & i : requiredSystemFeatures) + res.insert(i); + if (!drv.type().hasKnownOutputPaths()) + res.insert("ca-derivations"); + return res; +} + +bool DerivationOptions::canBuildLocally(Store & localStore, const BasicDerivation & drv) const +{ + if (drv.platform != settings.thisSystem.get() && !settings.extraPlatforms.get().count(drv.platform) + && !drv.isBuiltin()) + return false; + + if (settings.maxBuildJobs.get() == 0 && !drv.isBuiltin()) + return false; + + for (auto & feature : getRequiredSystemFeatures(drv)) + if (!localStore.systemFeatures.get().count(feature)) + return false; + + return true; +} + +bool DerivationOptions::willBuildLocally(Store & localStore, const BasicDerivation & drv) const +{ + return preferLocalBuild && canBuildLocally(localStore, drv); +} + +bool DerivationOptions::substitutesAllowed() const +{ + return settings.alwaysAllowSubstitutes ? true : allowSubstitutes; +} + +bool DerivationOptions::useUidRange(const BasicDerivation & drv) const +{ + return getRequiredSystemFeatures(drv).count("uid-range"); +} + +} + +namespace nlohmann { + +using namespace nix; + +DerivationOptions adl_serializer::from_json(const json & json) +{ + return { + .outputChecks = [&]() -> OutputChecksVariant { + auto outputChecks = getObject(valueAt(json, "outputChecks")); + + auto forAllOutputsOpt = optionalValueAt(outputChecks, "forAllOutputs"); + auto perOutputOpt = optionalValueAt(outputChecks, "perOutput"); + + if (forAllOutputsOpt && !perOutputOpt) { + return static_cast(*forAllOutputsOpt); + } else if (perOutputOpt && !forAllOutputsOpt) { + return static_cast>(*perOutputOpt); + } else { + throw Error("Exactly one of 'perOutput' or 'forAllOutputs' is required"); + } + }(), + + .unsafeDiscardReferences = valueAt(json, "unsafeDiscardReferences"), + .passAsFile = getStringSet(valueAt(json, "passAsFile")), + + .additionalSandboxProfile = getString(valueAt(json, "additionalSandboxProfile")), + .noChroot = getBoolean(valueAt(json, "noChroot")), + .impureHostDeps = getStringSet(valueAt(json, "impureHostDeps")), + .impureEnvVars = getStringSet(valueAt(json, "impureEnvVars")), + .allowLocalNetworking = getBoolean(valueAt(json, "allowLocalNetworking")), + + .requiredSystemFeatures = getStringSet(valueAt(json, "requiredSystemFeatures")), + .preferLocalBuild = getBoolean(valueAt(json, "preferLocalBuild")), + .allowSubstitutes = getBoolean(valueAt(json, "allowSubstitutes")), + }; +} + +void adl_serializer::to_json(json & json, DerivationOptions o) +{ + json["outputChecks"] = std::visit( + overloaded{ + [&](const OutputChecks & checks) { + nlohmann::json outputChecks; + outputChecks["forAllOutputs"] = checks; + return outputChecks; + }, + [&](const std::map & checksPerOutput) { + nlohmann::json outputChecks; + outputChecks["perOutput"] = checksPerOutput; + return outputChecks; + }, + }, + o.outputChecks); + + json["unsafeDiscardReferences"] = o.unsafeDiscardReferences; + json["passAsFile"] = o.passAsFile; + + json["additionalSandboxProfile"] = o.additionalSandboxProfile; + json["noChroot"] = o.noChroot; + json["impureHostDeps"] = o.impureHostDeps; + json["impureEnvVars"] = o.impureEnvVars; + json["allowLocalNetworking"] = o.allowLocalNetworking; + + json["requiredSystemFeatures"] = o.requiredSystemFeatures; + json["preferLocalBuild"] = o.preferLocalBuild; + json["allowSubstitutes"] = o.allowSubstitutes; +} + +DerivationOptions::OutputChecks adl_serializer::from_json(const json & json) +{ + return { + .ignoreSelfRefs = getBoolean(valueAt(json, "ignoreSelfRefs")), + .allowedReferences = nullableValueAt(json, "allowedReferences"), + .disallowedReferences = getStringSet(valueAt(json, "disallowedReferences")), + .allowedRequisites = nullableValueAt(json, "allowedRequisites"), + .disallowedRequisites = getStringSet(valueAt(json, "disallowedRequisites")), + }; +} + +void adl_serializer::to_json(json & json, DerivationOptions::OutputChecks c) +{ + json["ignoreSelfRefs"] = c.ignoreSelfRefs; + json["allowedReferences"] = c.allowedReferences; + json["disallowedReferences"] = c.disallowedReferences; + json["allowedRequisites"] = c.allowedRequisites; + json["disallowedRequisites"] = c.disallowedRequisites; +} + +} diff --git a/src/libstore/derivation-options.hh b/src/libstore/derivation-options.hh new file mode 100644 index 000000000..6e4ea5cd9 --- /dev/null +++ b/src/libstore/derivation-options.hh @@ -0,0 +1,185 @@ +#pragma once +///@file + +#include +#include +#include +#include + +#include "types.hh" +#include "json-impls.hh" + +namespace nix { + +class Store; +struct BasicDerivation; +class ParsedDerivation; + +/** + * This represents all the special options on a `Derivation`. + * + * Currently, these options are parsed from the environment variables + * with the aid of `ParsedDerivation`. + * + * The first goal of this data type is to make sure that no other code + * uses `ParsedDerivation` to ad-hoc parse some additional options. That + * ensures this data type is up to date and fully correct. + * + * The second goal of this data type is to allow an alternative to + * hackily parsing the options from the environment variables. The ATerm + * format cannot change, but in alternatives to it (like the JSON + * format), we have the option of instead storing the options + * separately. That would be nice to separate concerns, and not make any + * environment variable names magical. + */ +struct DerivationOptions +{ + struct OutputChecks + { + bool ignoreSelfRefs = false; + std::optional maxSize, maxClosureSize; + + /** + * env: allowedReferences + * + * A value of `nullopt` indicates that the check is skipped. + * This means that all references are allowed. + */ + std::optional allowedReferences; + + /** + * env: disallowedReferences + * + * No needed for `std::optional`, because skipping the check is + * the same as disallowing the references. + */ + StringSet disallowedReferences; + + /** + * env: allowedRequisites + * + * See `allowedReferences` + */ + std::optional allowedRequisites; + + /** + * env: disallowedRequisites + * + * See `disallowedReferences` + */ + StringSet disallowedRequisites; + + bool operator==(const OutputChecks &) const = default; + }; + + /** + * Either one set of checks for all outputs, or separate checks + * per-output. + */ + std::variant> outputChecks = OutputChecks{}; + + /** + * Whether to avoid scanning for references for a given output. + */ + std::map unsafeDiscardReferences; + + /** + * In non-structured mode, all bindings specified in the derivation + * go directly via the environment, except those listed in the + * passAsFile attribute. Those are instead passed as file names + * pointing to temporary files containing the contents. + * + * Note that passAsFile is ignored in structure mode because it's + * not needed (attributes are not passed through the environment, so + * there is no size constraint). + */ + StringSet passAsFile; + + /** + * env: __sandboxProfile + * + * Just for Darwin + */ + std::string additionalSandboxProfile = ""; + + /** + * env: __noChroot + * + * Derivation would like to opt out of the sandbox. + * + * Builder is free to not respect this wish (because it is + * insecure) and fail the build instead. + */ + bool noChroot = false; + + /** + * env: __impureHostDeps + */ + StringSet impureHostDeps = {}; + + /** + * env: impureEnvVars + */ + StringSet impureEnvVars = {}; + + /** + * env: __darwinAllowLocalNetworking + * + * Just for Darwin + */ + bool allowLocalNetworking = false; + + /** + * env: requiredSystemFeatures + */ + StringSet requiredSystemFeatures = {}; + + /** + * env: preferLocalBuild + */ + bool preferLocalBuild = false; + + /** + * env: allowSubstitutes + */ + bool allowSubstitutes = true; + + 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 supporing old formats (e.g. + * ATerm). + */ + static DerivationOptions fromParsedDerivation(const ParsedDerivation & 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 + * `DerivationOptions` instead. + */ + StringSet getRequiredSystemFeatures(const BasicDerivation & drv) const; + + /** + * @param drv See note on `getRequiredSystemFeatures` + */ + bool canBuildLocally(Store & localStore, const BasicDerivation & drv) const; + + /** + * @param drv See note on `getRequiredSystemFeatures` + */ + bool willBuildLocally(Store & localStore, const BasicDerivation & drv) const; + + bool substitutesAllowed() const; + + /** + * @param drv See note on `getRequiredSystemFeatures` + */ + bool useUidRange(const BasicDerivation & drv) const; +}; + +}; + +JSON_IMPL(DerivationOptions); +JSON_IMPL(DerivationOptions::OutputChecks) diff --git a/src/libstore/meson.build b/src/libstore/meson.build index d42b01061..899ba33fe 100644 --- a/src/libstore/meson.build +++ b/src/libstore/meson.build @@ -197,6 +197,7 @@ sources = files( 'content-address.cc', 'daemon.cc', 'derivations.cc', + 'derivation-options.cc', 'derived-path-map.cc', 'derived-path.cc', 'downstream-placeholder.cc', @@ -269,6 +270,7 @@ headers = [config_h] + files( 'content-address.hh', 'daemon.hh', 'derivations.hh', + 'derivation-options.hh', 'derived-path-map.hh', 'derived-path.hh', 'downstream-placeholder.hh', diff --git a/src/libstore/misc.cc b/src/libstore/misc.cc index bcc02206b..9d3b24326 100644 --- a/src/libstore/misc.cc +++ b/src/libstore/misc.cc @@ -2,6 +2,7 @@ #include "derivations.hh" #include "parsed-derivations.hh" +#include "derivation-options.hh" #include "globals.hh" #include "store-api.hh" #include "thread-pool.hh" @@ -222,8 +223,9 @@ void Store::queryMissing(const std::vector & targets, auto drv = make_ref(derivationFromPath(drvPath)); ParsedDerivation parsedDrv(StorePath(drvPath), *drv); + DerivationOptions drvOptions = DerivationOptions::fromParsedDerivation(parsedDrv); - if (!knownOutputPaths && settings.useSubstitutes && parsedDrv.substitutesAllowed()) { + if (!knownOutputPaths && settings.useSubstitutes && drvOptions.substitutesAllowed()) { experimentalFeatureSettings.require(Xp::CaDerivations); // If there are unknown output paths, attempt to find if the @@ -253,7 +255,7 @@ void Store::queryMissing(const std::vector & targets, } } - if (knownOutputPaths && settings.useSubstitutes && parsedDrv.substitutesAllowed()) { + if (knownOutputPaths && settings.useSubstitutes && drvOptions.substitutesAllowed()) { auto drvState = make_ref>(DrvState(invalid.size())); for (auto & output : invalid) pool.enqueue(std::bind(checkOutput, drvPath, drv, output, drvState)); diff --git a/src/libstore/parsed-derivations.cc b/src/libstore/parsed-derivations.cc index d8459d4d7..b26c36efe 100644 --- a/src/libstore/parsed-derivations.cc +++ b/src/libstore/parsed-derivations.cc @@ -87,47 +87,12 @@ std::optional ParsedDerivation::getStringsAttr(const std::string & name } } -StringSet ParsedDerivation::getRequiredSystemFeatures() const +std::optional ParsedDerivation::getStringSetAttr(const std::string & name) const { - // FIXME: cache this? - StringSet res; - for (auto & i : getStringsAttr("requiredSystemFeatures").value_or(Strings())) - res.insert(i); - if (!drv.type().hasKnownOutputPaths()) - res.insert("ca-derivations"); - return res; -} - -bool ParsedDerivation::canBuildLocally(Store & localStore) const -{ - if (drv.platform != settings.thisSystem.get() - && !settings.extraPlatforms.get().count(drv.platform) - && !drv.isBuiltin()) - return false; - - if (settings.maxBuildJobs.get() == 0 - && !drv.isBuiltin()) - return false; - - for (auto & feature : getRequiredSystemFeatures()) - if (!localStore.systemFeatures.get().count(feature)) return false; - - return true; -} - -bool ParsedDerivation::willBuildLocally(Store & localStore) const -{ - return getBoolAttr("preferLocalBuild") && canBuildLocally(localStore); -} - -bool ParsedDerivation::substitutesAllowed() const -{ - return settings.alwaysAllowSubstitutes ? true : getBoolAttr("allowSubstitutes", true); -} - -bool ParsedDerivation::useUidRange() const -{ - return getRequiredSystemFeatures().count("uid-range"); + auto ss = getStringsAttr(name); + return ss + ? (std::optional{StringSet{ss->begin(), ss->end()}}) + : (std::optional{}); } static std::regex shVarName("[A-Za-z_][A-Za-z0-9_]*"); @@ -188,7 +153,6 @@ static nlohmann::json pathInfoToJSON( std::optional ParsedDerivation::prepareStructuredAttrs(Store & store, const StorePathSet & inputPaths) { - auto structuredAttrs = getStructuredAttrs(); if (!structuredAttrs) return std::nullopt; auto json = *structuredAttrs; diff --git a/src/libstore/parsed-derivations.hh b/src/libstore/parsed-derivations.hh index 71085a604..51992fa84 100644 --- a/src/libstore/parsed-derivations.hh +++ b/src/libstore/parsed-derivations.hh @@ -8,38 +8,40 @@ namespace nix { +struct DerivationOptions; + class ParsedDerivation { StorePath drvPath; BasicDerivation & drv; std::unique_ptr structuredAttrs; -public: - - ParsedDerivation(const StorePath & drvPath, BasicDerivation & drv); - - ~ParsedDerivation(); - - const nlohmann::json * getStructuredAttrs() const - { - return structuredAttrs.get(); - } - std::optional getStringAttr(const std::string & name) const; bool getBoolAttr(const std::string & name, bool def = false) const; std::optional getStringsAttr(const std::string & name) const; - StringSet getRequiredSystemFeatures() const; + std::optional getStringSetAttr(const std::string & name) const; - bool canBuildLocally(Store & localStore) const; + /** + * Only `DerivationOptions` is allowed to parse individual fields + * from `ParsedDerivation`. This ensure that it includes all + * derivation options, and, the likes of `LocalDerivationGoal` are + * incapable of more ad-hoc options. + */ + friend struct DerivationOptions; - bool willBuildLocally(Store & localStore) const; +public: - bool substitutesAllowed() const; + ParsedDerivation(const StorePath & drvPath, BasicDerivation & drv); - bool useUidRange() const; + ~ParsedDerivation(); + + bool hasStructuredAttrs() const + { + return static_cast(structuredAttrs); + } std::optional prepareStructuredAttrs(Store & store, const StorePathSet & inputPaths); }; diff --git a/src/libstore/unix/build/local-derivation-goal.cc b/src/libstore/unix/build/local-derivation-goal.cc index 5006b53a5..29b786941 100644 --- a/src/libstore/unix/build/local-derivation-goal.cc +++ b/src/libstore/unix/build/local-derivation-goal.cc @@ -184,10 +184,6 @@ void LocalDerivationGoal::killSandbox(bool getStats) Goal::Co LocalDerivationGoal::tryLocalBuild() { -#if __APPLE__ - additionalSandboxProfile = parsedDrv->getStringAttr("__sandboxProfile").value_or(""); -#endif - unsigned int curBuilds = worker.getNrLocalBuilds(); if (curBuilds >= settings.maxBuildJobs) { worker.waitForBuildSlot(shared_from_this()); @@ -200,13 +196,12 @@ Goal::Co LocalDerivationGoal::tryLocalBuild() /* Are we doing a chroot build? */ { - auto noChroot = parsedDrv->getBoolAttr("__noChroot"); if (settings.sandboxMode == smEnabled) { - if (noChroot) + if (drvOptions->noChroot) throw Error("derivation '%s' has '__noChroot' set, " "but that's not allowed when 'sandbox' is 'true'", worker.store.printStorePath(drvPath)); #if __APPLE__ - if (additionalSandboxProfile != "") + if (drvOptions->additionalSandboxProfile != "") throw Error("derivation '%s' specifies a sandbox profile, " "but this is only allowed when 'sandbox' is 'relaxed'", worker.store.printStorePath(drvPath)); #endif @@ -215,7 +210,7 @@ Goal::Co LocalDerivationGoal::tryLocalBuild() else if (settings.sandboxMode == smDisabled) useChroot = false; else if (settings.sandboxMode == smRelaxed) - useChroot = derivationType->isSandboxed() && !noChroot; + useChroot = derivationType->isSandboxed() && !drvOptions->noChroot; } auto & localStore = getLocalStore(); @@ -240,7 +235,7 @@ Goal::Co LocalDerivationGoal::tryLocalBuild() if (useBuildUsers()) { if (!buildUser) - buildUser = acquireUserLock(parsedDrv->useUidRange() ? 65536 : 1, useChroot); + buildUser = acquireUserLock(drvOptions->useUidRange(*drv) ? 65536 : 1, useChroot); if (!buildUser) { if (!actLock) @@ -531,14 +526,14 @@ void LocalDerivationGoal::startBuilder() killSandbox(false); /* Right platform? */ - if (!parsedDrv->canBuildLocally(worker.store)) { + if (!drvOptions->canBuildLocally(worker.store, *drv)) { // since aarch64-darwin has Rosetta 2, this user can actually run x86_64-darwin on their hardware - we should tell them to run the command to install Darwin 2 if (drv->platform == "x86_64-darwin" && settings.thisSystem == "aarch64-darwin") { throw Error("run `/usr/sbin/softwareupdate --install-rosetta` to enable your %s to run programs for %s", settings.thisSystem, drv->platform); } else { throw Error("a '%s' with features {%s} is required to build '%s', but I am a '%s' with features {%s}", drv->platform, - concatStringsSep(", ", parsedDrv->getRequiredSystemFeatures()), + concatStringsSep(", ", drvOptions->getRequiredSystemFeatures(*drv)), worker.store.printStorePath(drvPath), settings.thisSystem, concatStringsSep(", ", worker.store.systemFeatures)); @@ -628,7 +623,7 @@ void LocalDerivationGoal::startBuilder() writeStructuredAttrs(); /* Handle exportReferencesGraph(), if set. */ - if (!parsedDrv->getStructuredAttrs()) { + if (!parsedDrv->hasStructuredAttrs()) { /* The `exportReferencesGraph' feature allows the references graph to be passed to a builder. This attribute should be a list of pairs [name1 path1 name2 path2 ...]. The references graph of @@ -702,7 +697,7 @@ void LocalDerivationGoal::startBuilder() PathSet allowedPaths = settings.allowedImpureHostPrefixes; /* This works like the above, except on a per-derivation level */ - auto impurePaths = parsedDrv->getStringsAttr("__impureHostDeps").value_or(Strings()); + auto impurePaths = drvOptions->impureHostDeps; for (auto & i : impurePaths) { bool found = false; @@ -722,7 +717,7 @@ void LocalDerivationGoal::startBuilder() throw Error("derivation '%s' requested impure path '%s', but it was not in allowed-impure-host-deps", worker.store.printStorePath(drvPath), i); - /* Allow files in __impureHostDeps to be missing; e.g. + /* Allow files in drvOptions->impureHostDeps to be missing; e.g. macOS 11+ has no /usr/lib/libSystem*.dylib */ pathsInChroot[i] = {i, true}; } @@ -762,10 +757,10 @@ void LocalDerivationGoal::startBuilder() nobody account. The latter is kind of a hack to support Samba-in-QEMU. */ createDirs(chrootRootDir + "/etc"); - if (parsedDrv->useUidRange()) + if (drvOptions->useUidRange(*drv)) chownToBuilder(chrootRootDir + "/etc"); - if (parsedDrv->useUidRange() && (!buildUser || buildUser->getUIDCount() < 65536)) + if (drvOptions->useUidRange(*drv) && (!buildUser || buildUser->getUIDCount() < 65536)) throw Error("feature 'uid-range' requires the setting '%s' to be enabled", settings.autoAllocateUids.name); /* Declare the build user's group so that programs get a consistent @@ -824,7 +819,7 @@ void LocalDerivationGoal::startBuilder() } #else - if (parsedDrv->useUidRange()) + if (drvOptions->useUidRange(*drv)) throw Error("feature 'uid-range' is not supported on this platform"); #if __APPLE__ /* We don't really have any parent prep work to do (yet?) @@ -834,7 +829,7 @@ void LocalDerivationGoal::startBuilder() #endif #endif } else { - if (parsedDrv->useUidRange()) + if (drvOptions->useUidRange(*drv)) throw Error("feature 'uid-range' is only supported in sandboxed builds"); } @@ -879,7 +874,7 @@ void LocalDerivationGoal::startBuilder() /* Fire up a Nix daemon to process recursive Nix calls from the builder. */ - if (parsedDrv->getRequiredSystemFeatures().count("recursive-nix")) + if (drvOptions->getRequiredSystemFeatures(*drv).count("recursive-nix")) startDaemon(); /* Run the builder. */ @@ -1147,18 +1142,12 @@ void LocalDerivationGoal::initTmpDir() tmpDirInSandbox = tmpDir; #endif - /* In non-structured mode, add all bindings specified in the - derivation via the environment, except those listed in the - passAsFile attribute. Those are passed as file names pointing - to temporary files containing the contents. Note that - passAsFile is ignored in structure mode because it's not - needed (attributes are not passed through the environment, so - there is no size constraint). */ - if (!parsedDrv->getStructuredAttrs()) { - - StringSet passAsFile = tokenizeString(getOr(drv->env, "passAsFile", "")); + /* In non-structured mode, set all bindings either directory in the + environment or via a file, as specified by + `DerivationOptions::passAsFile`. */ + if (!parsedDrv->hasStructuredAttrs()) { for (auto & i : drv->env) { - if (passAsFile.find(i.first) == passAsFile.end()) { + if (drvOptions->passAsFile.find(i.first) == drvOptions->passAsFile.end()) { env[i.first] = i.second; } else { auto hash = hashString(HashAlgorithm::SHA256, i.first); @@ -1235,7 +1224,7 @@ void LocalDerivationGoal::initEnv() if (!impureEnv.empty()) experimentalFeatureSettings.require(Xp::ConfigurableImpureEnv); - for (auto & i : parsedDrv->getStringsAttr("impureEnvVars").value_or(Strings())) { + for (auto & i : drvOptions->impureEnvVars){ auto envVar = impureEnv.find(i); if (envVar != impureEnv.end()) { env[i] = envVar->second; @@ -1995,7 +1984,7 @@ void LocalDerivationGoal::runChild() } /* Make /etc unwritable */ - if (!parsedDrv->useUidRange()) + if (!drvOptions->useUidRange(*drv)) chmod_(chrootRootDir + "/etc", 0555); /* Unshare this mount namespace. This is necessary because @@ -2182,7 +2171,7 @@ void LocalDerivationGoal::runChild() } sandboxProfile += ")\n"; - sandboxProfile += additionalSandboxProfile; + sandboxProfile += drvOptions->additionalSandboxProfile; } else sandboxProfile += #include "sandbox-minimal.sb" @@ -2191,8 +2180,6 @@ void LocalDerivationGoal::runChild() debug("Generated sandbox profile:"); debug(sandboxProfile); - bool allowLocalNetworking = parsedDrv->getBoolAttr("__darwinAllowLocalNetworking"); - /* The tmpDir in scope points at the temporary build directory for our derivation. Some packages try different mechanisms to find temporary directories, so we want to open up a broader place for them to put their files, if needed. */ Path globalTmpDir = canonPath(defaultTempDir(), true); @@ -2205,7 +2192,7 @@ void LocalDerivationGoal::runChild() Strings sandboxArgs; sandboxArgs.push_back("_GLOBAL_TMP_DIR"); sandboxArgs.push_back(globalTmpDir); - if (allowLocalNetworking) { + if (drvOptions->allowLocalNetworking) { sandboxArgs.push_back("_ALLOW_LOCAL_NETWORKING"); sandboxArgs.push_back("1"); } @@ -2395,14 +2382,8 @@ SingleDrvOutputs LocalDerivationGoal::registerOutputs() inodesSeen); bool discardReferences = false; - if (auto structuredAttrs = parsedDrv->getStructuredAttrs()) { - if (auto udr = get(*structuredAttrs, "unsafeDiscardReferences")) { - if (auto output = get(*udr, outputName)) { - if (!output->is_boolean()) - throw Error("attribute 'unsafeDiscardReferences.\"%s\"' of derivation '%s' must be a Boolean", outputName, drvPath.to_string()); - discardReferences = output->get(); - } - } + if (auto udr = get(drvOptions->unsafeDiscardReferences, outputName)) { + discardReferences = *udr; } StorePathSet references; @@ -2873,13 +2854,6 @@ void LocalDerivationGoal::checkOutputs(const std::map maxSize, maxClosureSize; - std::optional allowedReferences, allowedRequisites, disallowedReferences, disallowedRequisites; - }; - /* Compute the closure and closure size of some output. This is slightly tricky because some of its references (namely other outputs) may not be valid yet. */ @@ -2911,7 +2885,7 @@ void LocalDerivationGoal::checkOutputs(const std::map *checks.maxSize) throw BuildError("path '%s' is too large at %d bytes; limit is %d bytes", @@ -2924,15 +2898,13 @@ void LocalDerivationGoal::checkOutputs(const std::map & value, bool allowed, bool recursive) + auto checkRefs = [&](const StringSet & value, bool allowed, bool recursive) { - if (!value) return; - /* 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) { + for (auto & i : value) { if (worker.store.isStorePath(i)) spec.insert(worker.store.parseStorePath(i)); else if (auto output = get(outputs, i)) @@ -2970,73 +2942,35 @@ void LocalDerivationGoal::checkOutputs(const std::mapgetStructuredAttrs()) { - if (get(*structuredAttrs, "allowedReferences")){ - warn("'structuredAttrs' disables the effect of the top-level attribute 'allowedReferences'; use 'outputChecks' instead"); - } - if (get(*structuredAttrs, "allowedRequisites")){ - warn("'structuredAttrs' disables the effect of the top-level attribute 'allowedRequisites'; use 'outputChecks' instead"); - } - if (get(*structuredAttrs, "disallowedRequisites")){ - warn("'structuredAttrs' disables the effect of the top-level attribute 'disallowedRequisites'; use 'outputChecks' instead"); - } - if (get(*structuredAttrs, "disallowedReferences")){ - warn("'structuredAttrs' disables the effect of the top-level attribute 'disallowedReferences'; use 'outputChecks' instead"); - } - if (get(*structuredAttrs, "maxSize")){ - warn("'structuredAttrs' disables the effect of the top-level attribute 'maxSize'; use 'outputChecks' instead"); - } - if (get(*structuredAttrs, "maxClosureSize")){ - warn("'structuredAttrs' disables the effect of the top-level attribute 'maxClosureSize'; use 'outputChecks' instead"); - } - if (auto outputChecks = get(*structuredAttrs, "outputChecks")) { - if (auto output = get(*outputChecks, outputName)) { - Checks checks; + std::visit(overloaded{ + [&](const DerivationOptions::OutputChecks & checks) { + applyChecks(checks); + }, + [&](const std::map & checksPerOutput) { + if (auto outputChecks = get(checksPerOutput, outputName)) - if (auto maxSize = get(*output, "maxSize")) - checks.maxSize = maxSize->get(); - - if (auto maxClosureSize = get(*output, "maxClosureSize")) - checks.maxClosureSize = maxClosureSize->get(); - - auto get_ = [&](const std::string & name) -> std::optional { - if (auto i = get(*output, name)) { - Strings res; - for (auto j = i->begin(); j != i->end(); ++j) { - if (!j->is_string()) - throw Error("attribute '%s' of derivation '%s' must be a list of strings", name, worker.store.printStorePath(drvPath)); - res.push_back(j->get()); - } - checks.disallowedRequisites = res; - return res; - } - return {}; - }; - - checks.allowedReferences = get_("allowedReferences"); - checks.allowedRequisites = get_("allowedRequisites"); - checks.disallowedReferences = get_("disallowedReferences"); - checks.disallowedRequisites = get_("disallowedRequisites"); - - applyChecks(checks); - } - } - } else { - // legacy non-structured-attributes case - Checks checks; - checks.ignoreSelfRefs = true; - checks.allowedReferences = parsedDrv->getStringsAttr("allowedReferences"); - checks.allowedRequisites = parsedDrv->getStringsAttr("allowedRequisites"); - checks.disallowedReferences = parsedDrv->getStringsAttr("disallowedReferences"); - checks.disallowedRequisites = parsedDrv->getStringsAttr("disallowedRequisites"); - applyChecks(checks); - } + applyChecks(*outputChecks); + }, + }, drvOptions->outputChecks); } } diff --git a/src/libstore/unix/build/local-derivation-goal.hh b/src/libstore/unix/build/local-derivation-goal.hh index 917028880..c7a129f90 100644 --- a/src/libstore/unix/build/local-derivation-goal.hh +++ b/src/libstore/unix/build/local-derivation-goal.hh @@ -109,11 +109,6 @@ struct LocalDerivationGoal : public DerivationGoal typedef map Environment; Environment env; -#if __APPLE__ - typedef std::string SandboxProfile; - SandboxProfile additionalSandboxProfile; -#endif - /** * Hash rewriting. */ diff --git a/src/libutil/json-utils.cc b/src/libutil/json-utils.cc index dff068e07..f67811e21 100644 --- a/src/libutil/json-utils.cc +++ b/src/libutil/json-utils.cc @@ -3,6 +3,7 @@ #include "types.hh" #include #include +#include namespace nix { @@ -38,6 +39,15 @@ std::optional optionalValueAt(const nlohmann::json::object_t & m return std::optional { map.at(key) }; } +std::optional nullableValueAt(const nlohmann::json::object_t & map, const std::string & key) +{ + auto value = valueAt(map, key); + + if (value.is_null()) + return std::nullopt; + + return std::optional { std::move(value) }; +} const nlohmann::json * getNullable(const nlohmann::json & value) { diff --git a/src/libutil/json-utils.hh b/src/libutil/json-utils.hh index 546334e1e..1afc5d796 100644 --- a/src/libutil/json-utils.hh +++ b/src/libutil/json-utils.hh @@ -25,6 +25,7 @@ const nlohmann::json & valueAt( const std::string & key); std::optional optionalValueAt(const nlohmann::json::object_t & value, const std::string & key); +std::optional nullableValueAt(const nlohmann::json::object_t & value, const std::string & key); /** * Downcast the json object, failing with a nice error if the conversion fails. @@ -69,6 +70,9 @@ struct json_avoids_null> : std::true_type {}; template struct json_avoids_null> : std::true_type {}; +template +struct json_avoids_null> : std::true_type {}; + template struct json_avoids_null> : std::true_type {}; diff --git a/src/nix-build/nix-build.cc b/src/nix-build/nix-build.cc index de01e1afc..d3f0ddb72 100644 --- a/src/nix-build/nix-build.cc +++ b/src/nix-build/nix-build.cc @@ -11,6 +11,7 @@ #include "current-process.hh" #include "parsed-derivations.hh" +#include "derivation-options.hh" #include "store-api.hh" #include "local-fs-store.hh" #include "globals.hh" @@ -542,12 +543,13 @@ static void main_nix_build(int argc, char * * argv) env["NIX_STORE"] = store->storeDir; env["NIX_BUILD_CORES"] = std::to_string(settings.buildCores); - auto passAsFile = tokenizeString(getOr(drv.env, "passAsFile", "")); + ParsedDerivation parsedDrv(packageInfo.requireDrvPath(), drv); + DerivationOptions drvOptions = DerivationOptions::fromParsedDerivation(parsedDrv); int fileNr = 0; for (auto & var : drv.env) - if (passAsFile.count(var.first)) { + if (drvOptions.passAsFile.count(var.first)) { auto fn = ".attr-" + std::to_string(fileNr++); Path p = (tmpDir.path() / fn).string(); writeFile(p, var.second); @@ -557,7 +559,7 @@ static void main_nix_build(int argc, char * * argv) std::string structuredAttrsRC; - if (env.count("__json")) { + if (parsedDrv.hasStructuredAttrs()) { StorePathSet inputs; std::function::ChildNode &)> accumInputClosure; @@ -575,8 +577,6 @@ static void main_nix_build(int argc, char * * argv) for (const auto & [inputDrv, inputNode] : drv.inputDrvs.map) accumInputClosure(inputDrv, inputNode); - ParsedDerivation parsedDrv(packageInfo.requireDrvPath(), drv); - if (auto structAttrs = parsedDrv.prepareStructuredAttrs(*store, inputs)) { auto json = structAttrs.value(); structuredAttrsRC = writeStructuredAttrsShell(json);