-
Notifications
You must be signed in to change notification settings - Fork 9
Add subcommand 'nix provenance show' #340
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| R""( | ||
|
|
||
| # Examples | ||
|
|
||
| * Show the provenance of a store path: | ||
|
|
||
| ```console | ||
| # nix provenance show /run/current-system | ||
| /nix/store/k145bdxhdb89i4fkvgdisdz1yh2wiymm-nixos-system-machine-25.05.20251210.d2b1213 | ||
| ← copied from cache.flakehub.com | ||
| ← built from derivation /nix/store/w3p3xkminq61hs00kihd34w1dglpj5s9-nixos-system-machine-25.05.20251210.d2b1213.drv (output out) | ||
| ← instantiated from flake output github:my-org/my-repo/6b03eb949597fe96d536e956a2c14da9901dbd21?dir=machine#nixosConfigurations.machine.config.system.build.toplevel | ||
| ``` | ||
|
|
||
| # Description | ||
|
|
||
| Show the provenance chain of one or more store paths. For each store path, this displays where it came from: what binary cache it was copied from, what flake it was built from, and so on. | ||
|
|
||
| The provenance chain shows the history of how the store path came to exist, including: | ||
|
|
||
| - **Copied**: The path was copied from another Nix store, typically a binary cache. | ||
| - **Built**: The path was built from a derivation. | ||
| - **Flake evaluation**: The derivation was instantiated during the evaluation of a flake output. | ||
| - **Fetched**: The path was obtained by fetching a source tree. | ||
|
|
||
| Note: if you want provenance in JSON format, use the `provenance` field returned by `nix path-info --json`. | ||
|
|
||
| )"" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,124 @@ | ||
| #include "nix/cmd/command.hh" | ||
| #include "nix/store/store-api.hh" | ||
| #include "nix/store/provenance.hh" | ||
| #include "nix/flake/provenance.hh" | ||
| #include "nix/fetchers/provenance.hh" | ||
| #include "nix/util/provenance.hh" | ||
|
|
||
| #include <memory> | ||
| #include <nlohmann/json.hpp> | ||
|
|
||
| using namespace nix; | ||
|
|
||
| struct CmdProvenance : NixMultiCommand | ||
| { | ||
| CmdProvenance() | ||
| : NixMultiCommand("provenance", RegisterCommand::getCommandsFor({"provenance"})) | ||
| { | ||
| } | ||
|
|
||
| std::string description() override | ||
| { | ||
| return "query and check the provenance of store paths"; | ||
| } | ||
|
|
||
| std::optional<ExperimentalFeature> experimentalFeature() override | ||
| { | ||
| return Xp::Provenance; | ||
| } | ||
|
|
||
| Category category() override | ||
| { | ||
| return catUtility; | ||
| } | ||
| }; | ||
|
|
||
| static auto rCmdProvenance = registerCommand<CmdProvenance>("provenance"); | ||
|
|
||
| struct CmdProvenanceShow : StorePathsCommand | ||
| { | ||
| std::string description() override | ||
| { | ||
| return "show the provenance chain of store paths"; | ||
| } | ||
|
|
||
| std::string doc() override | ||
| { | ||
| return | ||
| #include "provenance-show.md" | ||
| ; | ||
| } | ||
|
|
||
| void displayProvenance(Store & store, const StorePath & path, std::shared_ptr<const Provenance> provenance) | ||
| { | ||
| while (provenance) { | ||
| if (auto copied = std::dynamic_pointer_cast<const CopiedProvenance>(provenance)) { | ||
| logger->cout("← copied from " ANSI_BOLD "%s" ANSI_NORMAL, copied->from); | ||
| provenance = copied->next; | ||
| } else if (auto build = std::dynamic_pointer_cast<const BuildProvenance>(provenance)) { | ||
| logger->cout( | ||
| "← built from derivation " ANSI_BOLD "%s" ANSI_NORMAL " (output " ANSI_BOLD "%s" ANSI_NORMAL ")", | ||
| store.printStorePath(build->drvPath), | ||
| build->output); | ||
| provenance = build->next; | ||
| } else if (auto flake = std::dynamic_pointer_cast<const FlakeProvenance>(provenance)) { | ||
| // Collapse subpath/tree provenance into the flake provenance for legibility. | ||
| auto next = flake->next; | ||
| CanonPath flakePath("/flake.nix"); | ||
| if (auto subpath = std::dynamic_pointer_cast<const SubpathProvenance>(next)) { | ||
| next = subpath->next; | ||
| flakePath = subpath->subpath; | ||
| } | ||
| if (auto tree = std::dynamic_pointer_cast<const TreeProvenance>(next)) { | ||
| FlakeRef flakeRef( | ||
| 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, | ||
| flakeRef.to_string(), | ||
| flake->flakeOutput); | ||
| break; | ||
| } else { | ||
| logger->cout("← instantiated from flake output " ANSI_BOLD "%s" ANSI_NORMAL, flake->flakeOutput); | ||
| provenance = flake->next; | ||
| } | ||
| } else if (auto tree = std::dynamic_pointer_cast<const TreeProvenance>(provenance)) { | ||
| auto input = fetchers::Input::fromAttrs(fetchSettings, fetchers::jsonToAttrs(*tree->attrs)); | ||
| logger->cout("← from tree " ANSI_BOLD "%s" ANSI_NORMAL, input.to_string()); | ||
| break; | ||
| } else if (auto subpath = std::dynamic_pointer_cast<const SubpathProvenance>(provenance)) { | ||
| logger->cout("← from file " ANSI_BOLD "%s" ANSI_NORMAL, subpath->subpath.abs()); | ||
| provenance = subpath->next; | ||
| } else { | ||
| // Unknown or unhandled provenance type | ||
| auto json = provenance->to_json(); | ||
| auto typeIt = json.find("type"); | ||
| if (typeIt != json.end() && typeIt->is_string()) | ||
| logger->cout("← " ANSI_RED "unknown provenance type '%s'" ANSI_NORMAL, typeIt->get<std::string>()); | ||
| else | ||
| logger->cout("← " ANSI_RED "unknown provenance type" ANSI_NORMAL); | ||
| break; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| void run(ref<Store> store, StorePaths && storePaths) override | ||
| { | ||
| bool first = true; | ||
|
|
||
| for (auto & storePath : storePaths) { | ||
| auto info = store->queryPathInfo(storePath); | ||
| if (!first) | ||
| logger->cout(""); | ||
| first = false; | ||
| logger->cout(ANSI_BOLD "%s" ANSI_NORMAL, store->printStorePath(info->path)); | ||
|
|
||
| if (info->provenance) | ||
| displayProvenance(*store, storePath, info->provenance); | ||
| else | ||
| logger->cout(ANSI_RED " (no provenance information available)" ANSI_NORMAL); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should probably exit with an error in this case; wdyt?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure, a path having no provenance is in itself not an error. For a |
||
| } | ||
| } | ||
| }; | ||
|
|
||
| static auto rCmdProvenanceShow = registerCommand2<CmdProvenanceShow>({"provenance", "show"}); | ||
Uh oh!
There was an error while loading. Please reload this page.