diff --git a/src/libcmd/include/nix/cmd/installable-attr-path.hh b/src/libcmd/include/nix/cmd/installable-attr-path.hh index 474bb358ec9..ef9dac81334 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 diff --git a/src/libstore/globals.cc b/src/libstore/globals.cc index 4acfd91969a..982a4f0b692 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 23ffc0d49f8..d0a134b9c4e 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 f742888b362..897c94b31ac 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 0fa38658d76..c61dcee8257 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 a0790d7523b..d47776655e5 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 2df68128f45..3fddb42e789 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 41e1851160a..6d98b7ae357 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 933436983f3..25fedd3cf49 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 <