#include "nix/store/derivation-options.hh" #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 #include #include #include #include namespace nix { static std::optional getStringAttr(const StringMap & env, const StructuredAttrs * parsed, const std::string & name) { if (parsed) { auto i = parsed->structuredAttrs.find(name); if (i == parsed->structuredAttrs.end()) return {}; else { if (!i->second.is_string()) throw Error("attribute '%s' of must be a string", name); return i->second.get(); } } else { auto i = env.find(name); if (i == env.end()) return {}; else return i->second; } } static bool getBoolAttr(const StringMap & env, const StructuredAttrs * parsed, const std::string & name, bool def) { if (parsed) { auto i = parsed->structuredAttrs.find(name); if (i == parsed->structuredAttrs.end()) return def; else { if (!i->second.is_boolean()) throw Error("attribute '%s' must be a Boolean", name); return i->second.get(); } } else { auto i = env.find(name); if (i == env.end()) return def; else return i->second == "1"; } } static std::optional getStringsAttr(const StringMap & env, const StructuredAttrs * parsed, const std::string & name) { if (parsed) { auto i = parsed->structuredAttrs.find(name); if (i == parsed->structuredAttrs.end()) return {}; else { if (!i->second.is_array()) throw Error("attribute '%s' must be a list of strings", name); auto & a = getArray(i->second); Strings res; for (auto j = a.begin(); j != a.end(); ++j) { if (!j->is_string()) throw Error("attribute '%s' must be a list of strings", name); res.push_back(j->get()); } return res; } } else { auto i = env.find(name); if (i == env.end()) return {}; else return tokenizeString(i->second); } } static std::optional getStringSetAttr(const StringMap & env, const StructuredAttrs * parsed, const std::string & name) { auto ss = getStringsAttr(env, parsed, name); return ss ? (std::optional{StringSet{ss->begin(), ss->end()}}) : (std::optional{}); } template using OutputChecks = DerivationOptions::OutputChecks; template using OutputChecksVariant = std::variant, std::map>>; DerivationOptions derivationOptionsFromStructuredAttrs( const StoreDirConfig & store, const StringMap & env, const StructuredAttrs * parsed, bool shouldWarn, const ExperimentalFeatureSettings & mockXpSettings) { /* Use the SingleDerivedPath version with empty inputDrvs, then resolve. */ DerivedPathMap emptyInputDrvs{}; auto singleDerivedPathOptions = derivationOptionsFromStructuredAttrs(store, emptyInputDrvs, env, parsed, shouldWarn, mockXpSettings); /* "Resolve" all SingleDerivedPath inputs to StorePath. */ auto resolved = tryResolve( singleDerivedPathOptions, [&](ref drvPath, const std::string & outputName) -> std::optional { // 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) { if (value.is_array()) for (auto & v : value) flatten(v, res); else if (value.is_string()) res.insert(value); else throw Error("'exportReferencesGraph' value is not an array or a string"); } DerivationOptions derivationOptionsFromStructuredAttrs( const StoreDirConfig & store, const DerivedPathMap & inputDrvs, const StringMap & env, const StructuredAttrs * parsed, bool shouldWarn, const ExperimentalFeatureSettings & mockXpSettings) { DerivationOptions defaults = {}; std::map placeholders; if (mockXpSettings.isEnabled(Xp::CaDerivations)) { /* Initialize placeholder map from inputDrvs */ auto initPlaceholders = [&](this const auto & initPlaceholders, ref basePath, const DerivedPathMap::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(SingleDerivedPath::Built{ .drvPath = basePath, .output = outputName, }), childNode); } }; for (const auto & [drvPath, outputs] : inputDrvs.map) { auto basePath = make_ref(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 { 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; 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 (parsed) { auto & structuredAttrs = parsed->structuredAttrs; std::map> res; if (auto * outputChecks = get(structuredAttrs, "outputChecks")) { for (auto & [outputName, output_] : getObject(*outputChecks)) { OutputChecks checks; auto & output = getObject(output_); 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)) { std::set> 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(parseRef(j->get())); } return res; } return {}; }; res.insert_or_assign( outputName, OutputChecks{ .maxSize = [&]() -> std::optional { if (auto maxSize = get(output, "maxSize")) return maxSize->get(); else return std::nullopt; }(), .maxClosureSize = [&]() -> std::optional { if (auto maxClosureSize = get(output, "maxClosureSize")) return maxClosureSize->get(); else return std::nullopt; }(), .allowedReferences = get_("allowedReferences"), .disallowedReferences = get_("disallowedReferences").value_or(std::set>{}), .allowedRequisites = get_("allowedRequisites"), .disallowedRequisites = get_("disallowedRequisites").value_or(std::set>{}), }); } } return res; } else { auto parseRefSet = [&](const std::optional optionalStringSet) -> std::optional>> { if (!optionalStringSet) return std::nullopt; auto range = *optionalStringSet | std::views::transform(parseRef); return std::set>(range.begin(), range.end()); }; return OutputChecks{ // legacy non-structured-attributes case .ignoreSelfRefs = true, .allowedReferences = parseRefSet(getStringSetAttr(env, parsed, "allowedReferences")), .disallowedReferences = parseRefSet(getStringSetAttr(env, parsed, "disallowedReferences")) .value_or(std::set>{}), .allowedRequisites = parseRefSet(getStringSetAttr(env, parsed, "allowedRequisites")), .disallowedRequisites = parseRefSet(getStringSetAttr(env, parsed, "disallowedRequisites")) .value_or(std::set>{}), }; } }(), .unsafeDiscardReferences = [&] { std::map res; if (parsed) { auto & structuredAttrs = parsed->structuredAttrs; 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(env, "passAsFile")) { if (parsed) { 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; }(), .exportReferencesGraph = [&] { std::map> ret; if (parsed) { auto * e = optionalValueAt(parsed->structuredAttrs, "exportReferencesGraph"); if (!e || !e->is_object()) return ret; for (auto & [key, storePathsJson] : getObject(*e)) { StringSet ss; flatten(storePathsJson, ss); std::set storePaths; for (auto & s : ss) storePaths.insert(parseSingleDerivedPath(s)); ret.insert_or_assign(key, std::move(storePaths)); } } else { auto s = getOr(env, "exportReferencesGraph", ""); Strings ss = tokenizeString(s); if (ss.size() % 2 != 0) throw Error("odd number of tokens in 'exportReferencesGraph': '%1%'", s); for (Strings::iterator i = ss.begin(); i != ss.end();) { auto fileName = std::move(*i++); static std::regex regex("[A-Za-z_][A-Za-z0-9_.-]*"); if (!std::regex_match(fileName, regex)) throw Error("invalid file name '%s' in 'exportReferencesGraph'", fileName); auto & storePathS = *i++; ret.insert_or_assign(std::move(fileName), std::set{parseSingleDerivedPath(storePathS)}); } } return ret; }(), .additionalSandboxProfile = getStringAttr(env, parsed, "__sandboxProfile").value_or(defaults.additionalSandboxProfile), .noChroot = getBoolAttr(env, parsed, "__noChroot", defaults.noChroot), .impureHostDeps = getStringSetAttr(env, parsed, "__impureHostDeps").value_or(defaults.impureHostDeps), .impureEnvVars = getStringSetAttr(env, parsed, "impureEnvVars").value_or(defaults.impureEnvVars), .allowLocalNetworking = getBoolAttr(env, parsed, "__darwinAllowLocalNetworking", defaults.allowLocalNetworking), .requiredSystemFeatures = getStringSetAttr(env, parsed, "requiredSystemFeatures").value_or(defaults.requiredSystemFeatures), .preferLocalBuild = getBoolAttr(env, parsed, "preferLocalBuild", defaults.preferLocalBuild), .allowSubstitutes = getBoolAttr(env, parsed, "allowSubstitutes", defaults.allowSubstitutes), }; } template 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; } template 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.config.systemFeatures.get().count(feature)) return false; return true; } template bool DerivationOptions::willBuildLocally(Store & localStore, const BasicDerivation & drv) const { return preferLocalBuild && canBuildLocally(localStore, drv); } template bool DerivationOptions::substitutesAllowed() const { return settings.alwaysAllowSubstitutes ? true : allowSubstitutes; } template bool DerivationOptions::useUidRange(const BasicDerivation & drv) const { return getRequiredSystemFeatures(drv).count("uid-range"); } std::optional> tryResolve( const DerivationOptions & drvOptions, std::function(ref drvPath, const std::string & outputName)> queryResolutionChain) { auto tryResolvePath = [&](const SingleDerivedPath & input) -> std::optional { return std::visit( overloaded{ [](const SingleDerivedPath::Opaque & p) -> std::optional { return p.path; }, [&](const SingleDerivedPath::Built & p) -> std::optional { return queryResolutionChain(p.drvPath, p.output); }}, input.raw()); }; auto tryResolveRef = [&](const DrvRef & ref) -> std::optional> { return std::visit( overloaded{ [](const OutputName & outputName) -> std::optional> { return outputName; }, [&](const SingleDerivedPath & input) -> std::optional> { return tryResolvePath(input); }}, ref); }; auto tryResolveRefSet = [&](const std::set> & refSet) -> std::optional>> { std::set> 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::OutputChecks & checks) -> std::optional::OutputChecks> { std::optional>> resolvedAllowedReferences; if (checks.allowedReferences) { resolvedAllowedReferences = tryResolveRefSet(*checks.allowedReferences); if (!resolvedAllowedReferences) return std::nullopt; } std::optional>> 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::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> & exportGraph) -> std::optional>> { std::map> resolved; for (const auto & [name, inputPaths] : exportGraph) { std::set 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::OutputChecks & checks) -> std::optional::OutputChecks, std::map::OutputChecks>>> { auto resolved = tryResolveOutputChecks(checks); if (!resolved) return std::nullopt; return std::variant< DerivationOptions::OutputChecks, std::map::OutputChecks>>(*resolved); }, [&](const std::map::OutputChecks> & checksMap) -> std::optional::OutputChecks, std::map::OutputChecks>>> { std::map::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::OutputChecks, std::map::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{ .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; template struct DerivationOptions; } // namespace nix namespace nlohmann { using namespace nix; DerivationOptions adl_serializer>::from_json(const json & json_) { auto & json = getObject(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")), .exportReferencesGraph = valueAt(json, "exportReferencesGraph"), .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, const 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["exportReferencesGraph"] = o.exportReferencesGraph; 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; } OutputChecks adl_serializer>::from_json(const json & json_) { auto & json = getObject(json_); return { .ignoreSelfRefs = getBoolean(valueAt(json, "ignoreSelfRefs")), .maxSize = ptrToOwned(getNullable(valueAt(json, "maxSize"))), .maxClosureSize = ptrToOwned(getNullable(valueAt(json, "maxClosureSize"))), .allowedReferences = ptrToOwned>>(getNullable(valueAt(json, "allowedReferences"))), .disallowedReferences = valueAt(json, "disallowedReferences"), .allowedRequisites = ptrToOwned>>(getNullable(valueAt(json, "allowedRequisites"))), .disallowedRequisites = valueAt(json, "disallowedRequisites"), }; } void adl_serializer>::to_json(json & json, const OutputChecks & c) { json["ignoreSelfRefs"] = c.ignoreSelfRefs; json["maxSize"] = c.maxSize; json["maxClosureSize"] = c.maxClosureSize; json["allowedReferences"] = c.allowedReferences; json["disallowedReferences"] = c.disallowedReferences; json["allowedRequisites"] = c.allowedRequisites; json["disallowedRequisites"] = c.disallowedRequisites; } } // namespace nlohmann