diff --git a/src/libexpr/include/nix/expr/eval.hh b/src/libexpr/include/nix/expr/eval.hh index aade0243268..852ad0f7bcc 100644 --- a/src/libexpr/include/nix/expr/eval.hh +++ b/src/libexpr/include/nix/expr/eval.hh @@ -228,7 +228,7 @@ struct StaticEvalSymbols line, column, functor, toString, right, wrong, structuredAttrs, json, allowedReferences, allowedRequisites, disallowedReferences, disallowedRequisites, maxSize, maxClosureSize, builder, args, contentAddressed, impure, outputHash, outputHashAlgo, outputHashMode, recurseForDerivations, description, self, epsilon, startSet, - operator_, key, path, prefix, outputSpecified, __meta, identifiers, license, licenses; + operator_, key, path, prefix, outputSpecified, __meta; Expr::AstSymbols exprSymbols; @@ -282,9 +282,6 @@ struct StaticEvalSymbols .prefix = alloc.create("prefix"), .outputSpecified = alloc.create("outputSpecified"), .__meta = alloc.create("__meta"), - .identifiers = alloc.create("identifier"), - .license = alloc.create("license"), - .licenses = alloc.create("licenses"), .exprSymbols = { .sub = alloc.create("__sub"), .lessThan = alloc.create("__lessThan"), diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 2f99adefd88..e1df04f2624 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -1380,16 +1380,15 @@ static void prim_second(EvalState & state, const PosIdx pos, Value ** args, Valu * Derivations *************************************************************/ -static void derivationStrictInternal(EvalState & state, std::string_view name, const Bindings * attrs, Value & v); +static void derivationStrictInternal( + EvalState & state, + std::string_view name, + const Bindings * attrs, + Value & v, + std::shared_ptr provenance, + bool acceptMeta); -/* Construct (as a unobservable side effect) a Nix derivation - expression that performs the derivation described by the argument - set. Returns the original set extended with the following - attributes: `outPath' containing the primary output path of the - derivation; `drvPath' containing the path of the Nix expression; - and `type' set to `derivation' to indicate that this is a - derivation. */ -static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value ** args, Value & v) +static void prim_derivationStrictGeneric(EvalState & state, const PosIdx pos, Value ** args, Value & v, bool acceptMeta) { state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.derivationStrict"); @@ -1409,7 +1408,7 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value ** } try { - derivationStrictInternal(state, drvName, attrs, v); + derivationStrictInternal(state, drvName, attrs, v, state.evalContext.provenance, acceptMeta); } catch (Error & e) { Pos pos = state.positions[nameAttr->pos]; /* @@ -1440,6 +1439,18 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value ** } } +/* Construct a Nix derivation with metadata provenance */ +static RegisterPrimOp primop_derivationStrictWithMeta( + PrimOp{ + .name = "derivationStrictWithMeta", + .arity = 1, + .fun = + [](EvalState & state, const PosIdx pos, Value ** args, Value & v) { + prim_derivationStrictGeneric(state, pos, args, v, /*acceptMeta=*/true); + }, + .internal = true, + }); + /** * Early validation for the derivation name, for better error message. * It is checked again when constructing store paths. @@ -1463,7 +1474,13 @@ static void checkDerivationName(EvalState & state, std::string_view drvName) } } -static void derivationStrictInternal(EvalState & state, std::string_view drvName, const Bindings * attrs, Value & v) +static void derivationStrictInternal( + EvalState & state, + std::string_view drvName, + const Bindings * attrs, + Value & v, + std::shared_ptr provenance, + bool acceptMeta) { checkDerivationName(state, drvName); @@ -1505,8 +1522,6 @@ static void derivationStrictInternal(EvalState & state, std::string_view drvName StringSet outputs; outputs.insert("out"); - auto provenance = state.evalContext.provenance; - for (auto & i : attrs->lexicographicOrder(state.symbols)) { if (i->name == state.s.ignoreNulls) continue; @@ -1574,29 +1589,6 @@ static void derivationStrictInternal(EvalState & state, std::string_view drvName experimentalFeatureSettings.require(Xp::ImpureDerivations); } break; - case EvalState::s.__meta.getId(): - if (experimentalFeatureSettings.isEnabled(Xp::Provenance)) { - state.forceAttrs(*i->value, pos, ""); - auto meta = i->value->attrs(); - auto obj = nlohmann::json(); - - for (auto & i : meta->lexicographicOrder(state.symbols)) { - auto key = state.symbols[i->name]; - switch (i->name.getId()) { - case EvalState::s.identifiers.getId(): - case EvalState::s.license.getId(): - case EvalState::s.licenses.getId(): - obj.emplace(key, printValueAsJSON(state, true, *i->value, pos, context)); - break; - default: - continue; - } - } - - provenance = - std::make_shared(provenance, make_ref(obj)); - } - break; /* The `args' attribute is special: it supplies the command-line arguments to the builder. */ case EvalState::s.args.getId(): @@ -1613,7 +1605,19 @@ static void derivationStrictInternal(EvalState & state, std::string_view drvName the environment. */ default: - if (jsonObject) { + if (acceptMeta && i->name == EvalState::s.__meta) { + if (experimentalFeatureSettings.isEnabled(Xp::Provenance)) { + state.forceAttrs(*i->value, pos, "while evaluating __meta"); + NixStringContext ctx; + auto obj = printValueAsJSON(state, true, *i->value, pos, ctx); + + if (!ctx.empty()) + throw Error("Derivation __meta provenance can't contain string context like store paths."); + + provenance = + std::make_shared(provenance, make_ref(obj)); + } + } else if (jsonObject) { if (i->name == state.s.structuredAttrs) continue; @@ -1900,11 +1904,21 @@ static void derivationStrictInternal(EvalState & state, std::string_view drvName v.mkAttrs(result); } +/* Construct (as a unobservable side effect) a Nix derivation + expression that performs the derivation described by the argument + set. Returns the original set extended with the following + attributes: `outPath' containing the primary output path of the + derivation; `drvPath' containing the path of the Nix expression; + and `type' set to `derivation' to indicate that this is a + derivation. */ static RegisterPrimOp primop_derivationStrict( PrimOp{ .name = "derivationStrict", .arity = 1, - .fun = prim_derivationStrict, + .fun = + [](EvalState & state, const PosIdx pos, Value ** args, Value & v) { + prim_derivationStrictGeneric(state, pos, args, v, /*acceptMeta=*/false); + }, }); /* Return a placeholder string for the specified output that will be @@ -5445,14 +5459,7 @@ void EvalState::createBaseEnv(const EvalSettings & evalSettings) language feature gets added. It's not necessary to increase it when primops get added, because you can just use `builtins ? primOp' to check. */ - if (experimentalFeatureSettings.isEnabled(Xp::Provenance)) { - /* Provenance allows for meta to be inside of derivations. - We increment the version to 7 so Nixpkgs will know when - provenance is available. */ - v.mkInt(7); - } else { - v.mkInt(6); - } + v.mkInt(6); addConstant( "__langVersion", v, @@ -5559,6 +5566,16 @@ void EvalState::createBaseEnv(const EvalSettings & evalSettings) .type = nFunction, }); + auto vDerivationWithMeta = allocValue(); + if (experimentalFeatureSettings.isEnabled(Xp::Provenance)) { + addConstant( + "derivationWithMeta", + vDerivationWithMeta, + { + .type = nFunction, + }); + } + /* Now that we've added all primops, sort the `builtins' set, because attribute lookups expect it to be sorted. */ const_cast(getBuiltins().attrs())->sort(); @@ -5567,7 +5584,14 @@ void EvalState::createBaseEnv(const EvalSettings & evalSettings) /* Note: we have to initialize the 'derivation' constant *after* building baseEnv/staticBaseEnv because it uses 'builtins'. */ - evalFile(derivationInternal, *vDerivation); + auto vDerivationValue = allocValue(); + evalFile(derivationInternal, *vDerivationValue); + + callFunction(*vDerivationValue, getBuiltin("derivationStrict"), *vDerivation, PosIdx()); + + if (experimentalFeatureSettings.isEnabled(Xp::Provenance)) + callFunction( + *vDerivationValue, **get(internalPrimOps, "derivationStrictWithMeta"), *vDerivationWithMeta, PosIdx()); } } // namespace nix diff --git a/src/libexpr/primops/derivation.nix b/src/libexpr/primops/derivation.nix index dbb8c218688..d3b341a2371 100644 --- a/src/libexpr/primops/derivation.nix +++ b/src/libexpr/primops/derivation.nix @@ -26,6 +26,7 @@ Note that `derivation` is very bare-bones, and provides almost no commands during the build. Most likely, you'll want to use functions like `stdenv.mkDerivation` in Nixpkgs to set up a basic environment. */ +drvFunc: drvAttrs@{ outputs ? [ "out" ], ... @@ -33,7 +34,7 @@ drvAttrs@{ let - strict = derivationStrict drvAttrs; + strict = drvFunc drvAttrs; commonAttrs = drvAttrs diff --git a/src/nix/provenance.cc b/src/nix/provenance.cc index 65e7436ff29..f6fa03f356b 100644 --- a/src/nix/provenance.cc +++ b/src/nix/provenance.cc @@ -8,8 +8,11 @@ #include "nix/util/json-utils.hh" #include +#include #include +#define TAB " " + using namespace nix; struct CmdProvenance : NixMultiCommand @@ -101,57 +104,10 @@ struct CmdProvenanceShow : StorePathsCommand provenance = subpath->next; } else if (auto drv = std::dynamic_pointer_cast(provenance)) { logger->cout("← with derivation metadata"); -#define TAB " " - auto json = getObject(*(drv->meta)); - if (auto identifiers = optionalValueAt(json, "identifiers")) { - auto ident = getObject(*identifiers); - if (auto cpeParts = optionalValueAt(ident, "cpeParts")) { - auto parts = getObject(*cpeParts); - - auto vendor = parts["vendor"]; - auto product = parts["product"]; - auto version = parts["version"]; - auto update = parts["update"]; - - logger->cout( - TAB "" ANSI_BOLD "CPE:" ANSI_NORMAL " cpe:2.3:a:%s:%s:%s:%s:*:*:*:*:*:*", - vendor.is_null() ? "*" : vendor.get(), - product.is_null() ? "*" : product.get(), - version.is_null() ? "*" : version.get(), - update.is_null() ? "*" : update.get()); - } - } - if (auto license = optionalValueAt(json, "license")) { - if (license->is_array()) { - logger->cout(TAB "" ANSI_BOLD "Licenses:" ANSI_NORMAL); - auto licenses = getArray(*license); - for (auto it = licenses.begin(); it != licenses.end(); it++) { - auto license = getObject(*it); - auto shortName = license["shortName"]; - logger->cout(TAB "" TAB "- %s", shortName.get()); - } - } else { - auto obj = getObject(*license); - auto shortName = obj["shortName"]; - logger->cout(TAB "" ANSI_BOLD "License:" ANSI_NORMAL " %s", shortName.get()); - } - } - if (auto licenses = optionalValueAt(json, "licenses")) { - if (licenses->is_array()) { - logger->cout(TAB "" ANSI_BOLD "Licenses:" ANSI_NORMAL); - auto licensesArray = getArray(*licenses); - for (auto it = licensesArray.begin(); it != licensesArray.end(); it++) { - auto license = getObject(*it); - auto shortName = license["shortName"]; - logger->cout(TAB "" TAB "- %s", shortName.get()); - } - } else { - auto license = getObject(*licenses); - auto shortName = license["shortName"]; - logger->cout(TAB "" ANSI_BOLD "License:" ANSI_NORMAL " %s", shortName.get()); - } + std::istringstream stream((*drv->meta).dump(2)); + for (std::string line; std::getline(stream, line);) { + logger->cout(" %s", line); } -#undef TAB provenance = drv->next; } else { // Unknown or unhandled provenance type diff --git a/tests/functional/config.nix.in b/tests/functional/config.nix.in index 9f50a8b0454..3ccc3d476bd 100644 --- a/tests/functional/config.nix.in +++ b/tests/functional/config.nix.in @@ -20,7 +20,7 @@ rec { shared = builtins.getEnv "_NIX_TEST_SHARED"; mkDerivation = args: - derivation ({ + (if builtins ? derivationWithMeta then builtins.derivationWithMeta else builtins.derivation) ({ inherit system; builder = shell; args = ["-e" args.builder or (builtins.toFile "builder-${args.name}.sh" '' @@ -28,8 +28,8 @@ rec { eval "$buildCommand" '')]; PATH = path; - } // caArgs // optionalAttrs (builtins.langVersion >= 7) { - __meta = args.meta or {}; + } // caArgs // optionalAttrs (builtins ? derivationWithMeta) { + __meta = removeAttrs (args.meta or {}) ["position"]; } // removeAttrs args ["builder" "meta"]) // { meta = args.meta or {}; }; } diff --git a/tests/functional/flakes/provenance.sh b/tests/functional/flakes/provenance.sh index 7adefd96965..53d62a45835 100644 --- a/tests/functional/flakes/provenance.sh +++ b/tests/functional/flakes/provenance.sh @@ -174,16 +174,27 @@ EOF unset _NIX_FORCE_HTTP # Test `nix provenance show`. -[[ "$(nix provenance show "$outPath")" = $(cat <:: | - | strict = derivationStrict drvAttrs; + | strict = drvFunc drvAttrs; | ^ | diff --git a/tests/functional/lang/eval-fail-derivation-structuredAttrs-stack-overflow.err.exp b/tests/functional/lang/eval-fail-derivation-structuredAttrs-stack-overflow.err.exp index c61eab0aa42..1a8dfa681f2 100644 --- a/tests/functional/lang/eval-fail-derivation-structuredAttrs-stack-overflow.err.exp +++ b/tests/functional/lang/eval-fail-derivation-structuredAttrs-stack-overflow.err.exp @@ -1,24 +1,24 @@ error: … while evaluating the attribute 'outPath' - at «nix-internal»/derivation-internal.nix:50:7: - 49| value = commonAttrs // { - 50| outPath = builtins.getAttr outputName strict; + at «nix-internal»/derivation-internal.nix:51:7: + 50| value = commonAttrs // { + 51| outPath = builtins.getAttr outputName strict; | ^ - 51| drvPath = strict.drvPath; + 52| drvPath = strict.drvPath; … while calling the 'getAttr' builtin - at «nix-internal»/derivation-internal.nix:50:17: - 49| value = commonAttrs // { - 50| outPath = builtins.getAttr outputName strict; + at «nix-internal»/derivation-internal.nix:51:17: + 50| value = commonAttrs // { + 51| outPath = builtins.getAttr outputName strict; | ^ - 51| drvPath = strict.drvPath; + 52| drvPath = strict.drvPath; … while calling the 'derivationStrict' builtin - at «nix-internal»/derivation-internal.nix:37:12: - 36| - 37| strict = derivationStrict drvAttrs; + at «nix-internal»/derivation-internal.nix:38:12: + 37| + 38| strict = drvFunc drvAttrs; | ^ - 38| + 39| … while evaluating derivation 'test' whose name attribute is located at /pwd/lang/eval-fail-derivation-structuredAttrs-stack-overflow.nix:5:3