From 1a69c8ea34a8ddbda9e290db4868d5dafcff1489 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 16 Feb 2026 17:06:29 +0100 Subject: [PATCH] Record provenance for unlocked inputs and impure evaluations --- src/libcmd/installable-flake.cc | 4 +- src/libfetchers/fetchers.cc | 6 +-- src/libflake/include/nix/flake/provenance.hh | 6 ++- src/libflake/provenance.cc | 7 ++- src/nix/provenance.cc | 9 +++- tests/functional/flakes/provenance.sh | 50 ++++++++++++++++++-- 6 files changed, 66 insertions(+), 16 deletions(-) diff --git a/src/libcmd/installable-flake.cc b/src/libcmd/installable-flake.cc index f20ffa0f6ef6..f132536356ea 100644 --- a/src/libcmd/installable-flake.cc +++ b/src/libcmd/installable-flake.cc @@ -219,12 +219,10 @@ FlakeRef InstallableFlake::nixpkgsFlakeRef() const std::shared_ptr InstallableFlake::makeProvenance(std::string_view attrPath) const { - if (!evalSettings.pureEval) - return nullptr; auto provenance = getLockedFlake()->flake.provenance; if (!provenance) return nullptr; - return std::make_shared(provenance, std::string(attrPath)); + return std::make_shared(provenance, std::string(attrPath), evalSettings.pureEval); } } // namespace nix diff --git a/src/libfetchers/fetchers.cc b/src/libfetchers/fetchers.cc index ece812635ed0..35223ee04b79 100644 --- a/src/libfetchers/fetchers.cc +++ b/src/libfetchers/fetchers.cc @@ -338,8 +338,7 @@ std::pair, Input> Input::getAccessorUnchecked(const Settings {{"hash", store.queryPathInfo(*storePath)->narHash.to_string(HashFormat::SRI, true)}}); } - if (isLocked(settings)) - accessor->provenance = std::make_shared(*this); + accessor->provenance = std::make_shared(*this); // FIXME: ideally we would use the `showPath()` of the // "real" accessor for this fetcher type. @@ -365,8 +364,7 @@ std::pair, Input> Input::getAccessorUnchecked(const Settings else accessor->fingerprint = result.getFingerprint(store); - if (result.isLocked(settings)) - accessor->provenance = std::make_shared(result); + accessor->provenance = std::make_shared(result); return {accessor, std::move(result)}; } catch (Error & e) { diff --git a/src/libflake/include/nix/flake/provenance.hh b/src/libflake/include/nix/flake/provenance.hh index a4debc6b0c6d..011744f5e65d 100644 --- a/src/libflake/include/nix/flake/provenance.hh +++ b/src/libflake/include/nix/flake/provenance.hh @@ -8,10 +8,12 @@ struct FlakeProvenance : Provenance { std::shared_ptr next; std::string flakeOutput; + bool pure = true; - FlakeProvenance(std::shared_ptr next, std::string flakeOutput) + FlakeProvenance(std::shared_ptr next, std::string flakeOutput, bool pure) : next(std::move(next)) - , flakeOutput(std::move(flakeOutput)) {}; + , flakeOutput(std::move(flakeOutput)) + , pure(pure) {}; nlohmann::json to_json() const override; }; diff --git a/src/libflake/provenance.cc b/src/libflake/provenance.cc index 865eddea7695..c80c4154561a 100644 --- a/src/libflake/provenance.cc +++ b/src/libflake/provenance.cc @@ -11,7 +11,7 @@ nlohmann::json FlakeProvenance::to_json() const {"type", "flake"}, {"next", next ? next->to_json() : nlohmann::json(nullptr)}, {"flakeOutput", flakeOutput}, - }; + {"pure", pure}}; } Provenance::Register registerFlakeProvenance("flake", [](nlohmann::json json) { @@ -19,7 +19,10 @@ Provenance::Register registerFlakeProvenance("flake", [](nlohmann::json json) { std::shared_ptr next; if (auto p = optionalValueAt(obj, "next"); p && !p->is_null()) next = Provenance::from_json(*p); - return make_ref(next, getString(valueAt(obj, "flakeOutput"))); + bool pure = true; + if (auto p = optionalValueAt(obj, "pure")) + pure = getBoolean(*p); + return make_ref(next, getString(valueAt(obj, "flakeOutput")), pure); }); } // namespace nix diff --git a/src/nix/provenance.cc b/src/nix/provenance.cc index 6d8ddf4cda1b..65e7436ff292 100644 --- a/src/nix/provenance.cc +++ b/src/nix/provenance.cc @@ -79,7 +79,9 @@ struct CmdProvenanceShow : StorePathsCommand fetchers::Input::fromAttrs(fetchSettings, fetchers::jsonToAttrs(*tree->attrs)), Path(flakePath.parent().value_or(CanonPath::root).rel())); logger->cout( - "← instantiated from flake output " ANSI_BOLD "%s#%s" ANSI_NORMAL, + "← %sinstantiated from %sflake output " ANSI_BOLD "%s#%s" ANSI_NORMAL, + flake->pure ? "" : ANSI_RED "impurely" ANSI_NORMAL " ", + flakeRef.input.isLocked(fetchSettings) ? "" : ANSI_RED "unlocked" ANSI_NORMAL " ", flakeRef.to_string(), flake->flakeOutput); break; @@ -89,7 +91,10 @@ struct CmdProvenanceShow : StorePathsCommand } } else if (auto tree = std::dynamic_pointer_cast(provenance)) { auto input = fetchers::Input::fromAttrs(fetchSettings, fetchers::jsonToAttrs(*tree->attrs)); - logger->cout("← from tree " ANSI_BOLD "%s" ANSI_NORMAL, input.to_string()); + logger->cout( + "← from %stree " ANSI_BOLD "%s" ANSI_NORMAL, + input.isLocked(fetchSettings) ? "" : ANSI_RED "unlocked" ANSI_NORMAL " ", + input.to_string()); break; } else if (auto subpath = std::dynamic_pointer_cast(provenance)) { logger->cout("← from file " ANSI_BOLD "%s" ANSI_NORMAL, subpath->subpath.abs()); diff --git a/tests/functional/flakes/provenance.sh b/tests/functional/flakes/provenance.sh index 6ce3bd38333e..7adefd96965a 100644 --- a/tests/functional/flakes/provenance.sh +++ b/tests/functional/flakes/provenance.sh @@ -51,6 +51,7 @@ builder=$(nix eval --raw "$flake1Dir#packages.$system.default._builder") "subpath": "/flake.nix", "type": "subpath" }, + "pure": true, "type": "flake" }, "type": "derivation" @@ -98,6 +99,13 @@ EOF EOF ) ]] +[[ "$(nix provenance show "$builder")" = $(cat < "$flake1Dir/somefile" git -C "$flake1Dir" add somefile nix build --impure --print-out-paths --no-link "$flake1Dir#packages.$system.default" -builder=$(nix eval --raw "$flake1Dir#packages.$system.default._builder") -[[ $(nix path-info --json --json-format 1 "$builder" | jq ".\"$builder\".provenance") = null ]] +[[ $(nix path-info --json --json-format 1 "$builder" | jq ".\"$builder\".provenance") != null ]] + +[[ "$(nix provenance show "$outPath")" = $(cat <