From 84de60d9426773f64a747283c4d2ba3dbdb00444 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 27 Feb 2026 22:54:15 +0100 Subject: [PATCH 1/5] Drop unnecessary nlohmann include --- src/libcmd/include/nix/cmd/installable-attr-path.hh | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/libcmd/include/nix/cmd/installable-attr-path.hh b/src/libcmd/include/nix/cmd/installable-attr-path.hh index 474bb358ec91..ef9dac813346 100644 --- a/src/libcmd/include/nix/cmd/installable-attr-path.hh +++ b/src/libcmd/include/nix/cmd/installable-attr-path.hh @@ -21,8 +21,6 @@ #include #include -#include - namespace nix { class InstallableAttrPath : public InstallableValue From c4919a55231baba267ebe96e3c468b00e79f0f77 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 27 Feb 2026 23:37:34 +0100 Subject: [PATCH 2/5] Add tags to BuildProvenance These are name/value pairs from the build host that can be configured through the `build-provenance-tags` setting. --- src/libstore/globals.cc | 16 ++++++++++++++++ src/libstore/include/nix/store/globals.hh | 9 +++++++++ src/libstore/include/nix/store/provenance.hh | 7 +++++++ src/libstore/provenance.cc | 7 ++++++- src/libstore/unix/build/derivation-builder.cc | 7 ++++++- src/nix/provenance.cc | 2 ++ tests/functional/common/init.sh | 1 + tests/functional/flakes/provenance.sh | 12 ++++++++++++ 8 files changed, 59 insertions(+), 2 deletions(-) diff --git a/src/libstore/globals.cc b/src/libstore/globals.cc index 4acfd91969ae..982a4f0b6928 100644 --- a/src/libstore/globals.cc +++ b/src/libstore/globals.cc @@ -422,6 +422,22 @@ std::string BaseSetting::to_string() const return nlohmann::json(value).dump(); } +template<> +std::map BaseSetting>::parse(const std::string & str) const +{ + try { + return nlohmann::json::parse(str).template get>(); + } catch (std::exception & e) { + throw UsageError("parsing setting '%s': %s", name, e.what()); + } +} + +template<> +std::string BaseSetting>::to_string() const +{ + return nlohmann::json(value).dump(); +} + template<> void BaseSetting::appendOrSet(PathsInChroot newValue, bool append) { diff --git a/src/libstore/include/nix/store/globals.hh b/src/libstore/include/nix/store/globals.hh index 23ffc0d49f8c..d0a134b9c4e1 100644 --- a/src/libstore/include/nix/store/globals.hh +++ b/src/libstore/include/nix/store/globals.hh @@ -1462,6 +1462,15 @@ public: )"}; std::optional getHostName(); + + Setting> buildProvenanceTags{ + this, + {}, + "build-provenance-tags", + R"( + Arbitrary name/value pairs that are recorded in the build provenance of store paths built by this machine. + This can be used to tag builds with metadata such as the CI job URL, build cluster name, etc. + )"}; }; // FIXME: don't use a global variable. diff --git a/src/libstore/include/nix/store/provenance.hh b/src/libstore/include/nix/store/provenance.hh index f742888b362a..897c94b31ace 100644 --- a/src/libstore/include/nix/store/provenance.hh +++ b/src/libstore/include/nix/store/provenance.hh @@ -23,6 +23,11 @@ struct BuildProvenance : Provenance */ std::optional buildHost; + /** + * User-defined tags from the build host. + */ + std::map tags; + /** * The system type of the derivation. */ @@ -39,11 +44,13 @@ struct BuildProvenance : Provenance const StorePath & drvPath, const OutputName & output, std::optional buildHost, + std::map tags, std::string system, std::shared_ptr next) : drvPath(drvPath) , output(output) , buildHost(std::move(buildHost)) + , tags(std::move(tags)) , system(std::move(system)) , next(std::move(next)) { diff --git a/src/libstore/provenance.cc b/src/libstore/provenance.cc index 0fa38658d769..c61dcee8257e 100644 --- a/src/libstore/provenance.cc +++ b/src/libstore/provenance.cc @@ -12,6 +12,7 @@ nlohmann::json BuildProvenance::to_json() const {"buildHost", buildHost}, {"system", system}, {"next", next ? next->to_json() : nlohmann::json(nullptr)}, + {"tags", tags}, }; } @@ -23,10 +24,14 @@ Provenance::Register registerBuildProvenance("build", [](nlohmann::json json) { std::optional buildHost; if (auto p = optionalValueAt(obj, "buildHost")) buildHost = p->get>(); + std::map tags; + if (auto p = optionalValueAt(obj, "tags"); p && !p->is_null()) + tags = p->get>(); auto buildProv = make_ref( StorePath(getString(valueAt(obj, "drv"))), getString(valueAt(obj, "output")), - buildHost, + std::move(buildHost), + std::move(tags), getString(valueAt(obj, "system")), next); return buildProv; diff --git a/src/libstore/unix/build/derivation-builder.cc b/src/libstore/unix/build/derivation-builder.cc index a0790d7523b4..d47776655e5d 100644 --- a/src/libstore/unix/build/derivation-builder.cc +++ b/src/libstore/unix/build/derivation-builder.cc @@ -1873,7 +1873,12 @@ SingleDrvOutputs DerivationBuilderImpl::registerOutputs() newInfo.ultimate = true; if (experimentalFeatureSettings.isEnabled(Xp::Provenance)) newInfo.provenance = std::make_shared( - drvPath, outputName, settings.getHostName(), drv.platform, drvProvenance); + drvPath, + outputName, + settings.getHostName(), + settings.buildProvenanceTags.get(), + drv.platform, + drvProvenance); store.signPathInfo(newInfo); finish(newInfo.path); diff --git a/src/nix/provenance.cc b/src/nix/provenance.cc index 2df68128f458..3fddb42e789a 100644 --- a/src/nix/provenance.cc +++ b/src/nix/provenance.cc @@ -79,6 +79,8 @@ struct CmdProvenanceShow : StorePathsCommand build->output, build->buildHost.value_or("unknown host").c_str(), build->system); + for (auto & [tagName, tagValue] : build->tags) + logger->cout(" tag " ANSI_BOLD "%s" ANSI_NORMAL ": %s", tagName, tagValue); provenance = build->next; } diff --git a/tests/functional/common/init.sh b/tests/functional/common/init.sh index 41e1851160a7..6d98b7ae357e 100755 --- a/tests/functional/common/init.sh +++ b/tests/functional/common/init.sh @@ -53,6 +53,7 @@ substituters = flake-registry = $TEST_ROOT/registry.json show-trace = true host-name = test-host +build-provenance-tags = {"pr": "1234", "branch": "main"} include nix.conf.extra trusted-users = $(whoami) ${_NIX_TEST_EXTRA_CONFIG:-} diff --git a/tests/functional/flakes/provenance.sh b/tests/functional/flakes/provenance.sh index 933436983f38..25fedd3cf496 100644 --- a/tests/functional/flakes/provenance.sh +++ b/tests/functional/flakes/provenance.sh @@ -58,6 +58,10 @@ builder=$(nix eval --raw "$flake1Dir#packages.$system.default._builder") }, "output": "out", "system": "$system", + "tags": { + "branch": "main", + "pr": "1234" + }, "type": "build" } EOF @@ -172,6 +176,10 @@ nix copy --from "file://$binaryCache" "$outPath" --no-check-sigs }, "output": "out", "system": "$system", + "tags": { + "branch": "main", + "pr": "1234" + }, "type": "build" }, "type": "copied" @@ -186,6 +194,8 @@ unset _NIX_FORCE_HTTP $outPath ← copied from file://$binaryCache ← built from derivation $drvPath (output out) on test-host for $system + tag branch: main + tag pr: 1234 ← with derivation metadata { "license": [ @@ -258,6 +268,8 @@ nix build --impure --print-out-paths --no-link "$flake1Dir#packages.$system.defa [[ "$(nix provenance show "$outPath")" = "$(cat < Date: Wed, 11 Mar 2026 01:00:06 +0100 Subject: [PATCH 3/5] Restrict tag names --- src/libstore/include/nix/store/provenance.hh | 10 +------- src/libstore/provenance.cc | 27 ++++++++++++++++++++ tests/functional/flakes/provenance.sh | 5 ++++ 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/src/libstore/include/nix/store/provenance.hh b/src/libstore/include/nix/store/provenance.hh index 897c94b31ace..c3ae4f8a6b23 100644 --- a/src/libstore/include/nix/store/provenance.hh +++ b/src/libstore/include/nix/store/provenance.hh @@ -46,15 +46,7 @@ struct BuildProvenance : Provenance std::optional buildHost, std::map tags, std::string system, - std::shared_ptr next) - : drvPath(drvPath) - , output(output) - , buildHost(std::move(buildHost)) - , tags(std::move(tags)) - , system(std::move(system)) - , next(std::move(next)) - { - } + std::shared_ptr next); nlohmann::json to_json() const override; }; diff --git a/src/libstore/provenance.cc b/src/libstore/provenance.cc index c61dcee8257e..9fde815b4d06 100644 --- a/src/libstore/provenance.cc +++ b/src/libstore/provenance.cc @@ -1,8 +1,35 @@ #include "nix/store/provenance.hh" #include "nix/util/json-utils.hh" +#include + namespace nix { +static void checkProvenanceTagName(std::string_view name) +{ + static const std::regex tagNameRegex("^[A-Za-z_][A-Za-z0-9_+\\-]*$"); + if (!std::regex_match(name.begin(), name.end(), tagNameRegex)) + throw Error("tag name '%s' is invalid", name); +} + +BuildProvenance::BuildProvenance( + const StorePath & drvPath, + const OutputName & output, + std::optional buildHost, + std::map tags, + std::string system, + std::shared_ptr next) + : drvPath(drvPath) + , output(output) + , buildHost(std::move(buildHost)) + , tags(std::move(tags)) + , system(std::move(system)) + , next(std::move(next)) +{ + for (const auto & [name, value] : this->tags) + checkProvenanceTagName(name); +} + nlohmann::json BuildProvenance::to_json() const { return { diff --git a/tests/functional/flakes/provenance.sh b/tests/functional/flakes/provenance.sh index 25fedd3cf496..2004ccd5e693 100644 --- a/tests/functional/flakes/provenance.sh +++ b/tests/functional/flakes/provenance.sh @@ -365,3 +365,8 @@ nix provenance verify "$path" echo barf > "$TEST_ROOT/hello.txt" expectStderr 1 nix provenance verify "$path" | grepQuiet "hash mismatch for URL" + +# Test invalid tag names +for name in "123-invalid" "invalid tag" "invalid@tag" "-invalid" " foo"; do + expectStderr 1 nix build --build-provenance-tags "{\"$name\": \"value\"}" --no-link "$flake1Dir#packages.$system.default" 2>&1 | grepQuiet "tag name '$name' is invalid" +done From 82d6ae0d541dbfe5840021084b01d9edce4a9131 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 12 Mar 2026 00:35:12 +0100 Subject: [PATCH 4/5] nix flake check: Set provenance --- src/libcmd/flake-schemas.cc | 7 +++++++ src/libcmd/include/nix/cmd/flake-schemas.hh | 1 + src/nix/flake.cc | 2 ++ 3 files changed, 10 insertions(+) diff --git a/src/libcmd/flake-schemas.cc b/src/libcmd/flake-schemas.cc index 725326c0fd75..44fb4d22fe2b 100644 --- a/src/libcmd/flake-schemas.cc +++ b/src/libcmd/flake-schemas.cc @@ -3,6 +3,7 @@ #include "nix/fetchers/fetch-to-store.hh" #include "nix/util/memory-source-accessor.hh" #include "nix/util/mounted-source-accessor.hh" +#include "nix/flake/provenance.hh" namespace nix::flake_schemas { @@ -162,12 +163,18 @@ void forEachOutput( void visit( std::optional system, ref node, + std::shared_ptr provenance, std::function visitLeaf, std::function)> visitNonLeaf, std::function node, const std::vector & systems)> visitFiltered) { Activity act(*logger, lvlInfo, actUnknown, fmt("evaluating '%s'", node->getAttrPathStr())); + PushProvenance pushedProvenance( + node->root->state, + provenance ? std::make_shared(provenance, node->getAttrPathStr(), evalSettings.pureEval) + : nullptr); + /* Apply the system type filter. */ if (system) { if (auto forSystems = Node(node).forSystems()) { diff --git a/src/libcmd/include/nix/cmd/flake-schemas.hh b/src/libcmd/include/nix/cmd/flake-schemas.hh index c1ceefdc35aa..32653d910588 100644 --- a/src/libcmd/include/nix/cmd/flake-schemas.hh +++ b/src/libcmd/include/nix/cmd/flake-schemas.hh @@ -65,6 +65,7 @@ typedef std::function attr, bool isLast)> void visit( std::optional system, ref node, + std::shared_ptr provenance, std::function visitLeaf, std::function)> visitNonLeaf, std::function node, const std::vector & systems)> visitFiltered); diff --git a/src/nix/flake.cc b/src/nix/flake.cc index 454e10d4ecf2..c0587742691f 100644 --- a/src/nix/flake.cc +++ b/src/nix/flake.cc @@ -383,6 +383,7 @@ struct CmdFlakeCheck : FlakeCommand, MixFlakeSchemas flake_schemas::visit( checkAllSystems ? std::optional() : localSystem, node, + flake->flake.provenance, [&](const flake_schemas::Leaf & leaf) { try { @@ -899,6 +900,7 @@ struct CmdFlakeShow : FlakeCommand, MixJSON, MixFlakeSchemas flake_schemas::visit( showAllSystems ? std::optional() : localSystem, node, + flake->flake.provenance, [&](const flake_schemas::Leaf & leaf) { if (auto what = leaf.what()) From 73806cab7b2449017630b1cd7054bbc6e40415bb Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 12 Mar 2026 00:56:35 +0100 Subject: [PATCH 5/5] Sync with upstream flake-schemas --- src/libcmd/builtin-flake-schemas.nix | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libcmd/builtin-flake-schemas.nix b/src/libcmd/builtin-flake-schemas.nix index f326b413c9e6..a8b78b167eb9 100644 --- a/src/libcmd/builtin-flake-schemas.nix +++ b/src/libcmd/builtin-flake-schemas.nix @@ -82,12 +82,12 @@ inventory = self.lib.derivationsInventory "package" false; }; - dockerImagesSchema = { + ociImagesSchema = { version = 1; doc = '' - The `dockerImages` flake output contains derivations that build valid Docker images. + The `ociImages` flake output contains derivations that build valid Open Container Initiative images. ''; - inventory = self.lib.derivationsInventory "Docker image" false; + inventory = self.lib.derivationsInventory "OCI image" false; }; legacyPackagesSchema = { @@ -432,7 +432,7 @@ schemas.homeModules = homeModulesSchema; schemas.darwinConfigurations = darwinConfigurationsSchema; schemas.darwinModules = darwinModulesSchema; - schemas.dockerImages = dockerImagesSchema; + schemas.ociImages = ociImagesSchema; schemas.bundlers = bundlersSchema; }; }