Allow diff-closures to output JSON
Change-Id: Ia17ea93d98b38e3415c35257daf07c7978f50ef4
This commit is contained in:
@@ -139,6 +139,10 @@ midnightveil:
|
||||
ncfavier:
|
||||
github: ncfavier
|
||||
|
||||
pamplemousse:
|
||||
display_name: Xavier Maso
|
||||
github: pamplemousse
|
||||
|
||||
piegames:
|
||||
display_name: piegames
|
||||
forgejo: piegames
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
synopsis: "Allow `nix store diff-closures` to output JSON"
|
||||
issues: []
|
||||
cls: [2360]
|
||||
category: Improvements
|
||||
credits: [pamplemousse]
|
||||
---
|
||||
|
||||
Add the `--json` option to the `nix store diff-closures` command to allow users to collect diff information into a machine readable format.
|
||||
|
||||
```bash
|
||||
$ build/lix/nix/nix store diff-closures --json /run/current-system /nix/store/n1prick95pihd4lkv58nn3pzg1yivcdb-neovim-0.10.4/bin/nvim | jq | head -n 23
|
||||
{
|
||||
"packages": {
|
||||
"02overridedns": {
|
||||
"sizeDelta": -688,
|
||||
"versionsAfter": [],
|
||||
"versionsBefore": [
|
||||
""
|
||||
]
|
||||
},
|
||||
"50-coredump.conf": {
|
||||
"sizeDelta": -1976,
|
||||
"versionsAfter": [],
|
||||
"versionsBefore": [
|
||||
""
|
||||
]
|
||||
},
|
||||
"Diff": {
|
||||
"sizeDelta": -514864,
|
||||
"versionsAfter": [],
|
||||
"versionsBefore": [
|
||||
"0.4.1"
|
||||
]
|
||||
},
|
||||
```
|
||||
@@ -375,6 +375,7 @@ kj::Promise<Result<void>> printClosureDiff(
|
||||
ref<Store> store,
|
||||
const StorePath & beforePath,
|
||||
const StorePath & afterPath,
|
||||
bool json,
|
||||
std::string_view indent);
|
||||
|
||||
}
|
||||
|
||||
+87
-13
@@ -6,18 +6,53 @@
|
||||
#include "lix/libstore/names.hh"
|
||||
#include "lix/libutil/result.hh"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <regex>
|
||||
|
||||
namespace nix {
|
||||
|
||||
static constexpr std::string_view CLOSURE_DIFF_SCHEMA_VERSION = "lix-closure-diff-v1";
|
||||
|
||||
struct Info
|
||||
{
|
||||
std::string outputName;
|
||||
};
|
||||
|
||||
struct DiffInfoForPackage
|
||||
{
|
||||
int64_t sizeDelta;
|
||||
std::set<std::string> addedVersions;
|
||||
std::set<std::string> removedVersions;
|
||||
};
|
||||
|
||||
// name -> version -> store paths
|
||||
typedef std::map<std::string, std::map<std::string, std::map<StorePath, Info>>> GroupedPaths;
|
||||
|
||||
typedef std::map<std::string, DiffInfoForPackage> DiffInfo;
|
||||
|
||||
nlohmann::json toJSON(const DiffInfo & diff)
|
||||
{
|
||||
nlohmann::json res = nlohmann::json::object();
|
||||
nlohmann::json content = nlohmann::json::object();
|
||||
|
||||
for (auto & [name, item] : diff) {
|
||||
auto packageContent = nlohmann::json::object();
|
||||
|
||||
if (!item.removedVersions.empty() || !item.addedVersions.empty()) {
|
||||
packageContent["versionsBefore"] = item.removedVersions;
|
||||
packageContent["versionsAfter"] = item.addedVersions;
|
||||
}
|
||||
packageContent["sizeDelta"] = item.sizeDelta;
|
||||
|
||||
content[name] = std::move(packageContent);
|
||||
}
|
||||
|
||||
res["packages"] = std::move(content);
|
||||
res["schema"] = CLOSURE_DIFF_SCHEMA_VERSION;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static kj::Promise<Result<GroupedPaths>>
|
||||
getClosureInfo(ref<Store> store, const StorePath & toplevel)
|
||||
try {
|
||||
@@ -50,11 +85,10 @@ try {
|
||||
co_return result::current_exception();
|
||||
}
|
||||
|
||||
kj::Promise<Result<void>> printClosureDiff(
|
||||
kj::Promise<Result<DiffInfo>> getDiffInfo(
|
||||
ref<Store> store,
|
||||
const StorePath & beforePath,
|
||||
const StorePath & afterPath,
|
||||
std::string_view indent)
|
||||
const StorePath & afterPath)
|
||||
try {
|
||||
auto beforeClosure = TRY_AWAIT(getClosureInfo(store, beforePath));
|
||||
auto afterClosure = TRY_AWAIT(getClosureInfo(store, afterPath));
|
||||
@@ -63,6 +97,8 @@ try {
|
||||
for (auto & [name, _] : beforeClosure) allNames.insert(name);
|
||||
for (auto & [name, _] : afterClosure) allNames.insert(name);
|
||||
|
||||
DiffInfo itemsToPrint;
|
||||
|
||||
for (auto & name : allNames) {
|
||||
auto & beforeVersions = beforeClosure[name];
|
||||
auto & afterVersions = afterClosure[name];
|
||||
@@ -84,7 +120,6 @@ try {
|
||||
auto beforeSize = TRY_AWAIT(totalSize(beforeVersions));
|
||||
auto afterSize = TRY_AWAIT(totalSize(afterVersions));
|
||||
auto sizeDelta = (int64_t) afterSize - (int64_t) beforeSize;
|
||||
auto showDelta = std::abs(sizeDelta) >= 8 * 1024;
|
||||
|
||||
std::set<std::string> removed, unchanged;
|
||||
for (auto & [version, _] : beforeVersions)
|
||||
@@ -94,17 +129,56 @@ try {
|
||||
for (auto & [version, _] : afterVersions)
|
||||
if (!beforeVersions.count(version)) added.insert(version);
|
||||
|
||||
if (showDelta || !removed.empty() || !added.empty()) {
|
||||
std::vector<std::string> items;
|
||||
if (!removed.empty() || !added.empty())
|
||||
items.push_back(fmt("%s → %s", showVersions(removed), showVersions(added)));
|
||||
if (showDelta)
|
||||
items.push_back(fmt("%s%+.1f KiB" ANSI_NORMAL, sizeDelta > 0 ? ANSI_RED : ANSI_GREEN, sizeDelta / 1024.0));
|
||||
logger->cout("%s%s: %s", indent, name, concatStringsSep(", ", items));
|
||||
if (!removed.empty() || !added.empty()) {
|
||||
auto info = DiffInfoForPackage {
|
||||
.sizeDelta = sizeDelta,
|
||||
.addedVersions = added,
|
||||
.removedVersions = removed
|
||||
};
|
||||
|
||||
itemsToPrint[name] = std::move(info);
|
||||
}
|
||||
}
|
||||
|
||||
co_return itemsToPrint;
|
||||
} catch (...) {
|
||||
co_return result::current_exception();
|
||||
}
|
||||
|
||||
void renderDiffInfo(
|
||||
DiffInfo diff,
|
||||
const std::string_view indent)
|
||||
{
|
||||
for (auto & [name, item] : diff) {
|
||||
auto showDelta = std::abs(item.sizeDelta) >= 8 * 1024;
|
||||
|
||||
std::vector<std::string> line;
|
||||
if (!item.removedVersions.empty() || !item.addedVersions.empty())
|
||||
line.push_back(fmt("%s → %s", showVersions(item.removedVersions), showVersions(item.addedVersions)));
|
||||
if (showDelta)
|
||||
line.push_back(fmt("%s%+.1f KiB" ANSI_NORMAL, item.sizeDelta > 0 ? ANSI_RED : ANSI_GREEN, item.sizeDelta / 1024.0));
|
||||
logger->cout("%s%s: %s", indent, name, concatStringsSep(", ", line));
|
||||
}
|
||||
}
|
||||
|
||||
kj::Promise<Result<void>> printClosureDiff(
|
||||
ref<Store> store,
|
||||
const StorePath & beforePath,
|
||||
const StorePath & afterPath,
|
||||
const bool json,
|
||||
const std::string_view indent)
|
||||
try {
|
||||
DiffInfo diff = TRY_AWAIT(getDiffInfo(store, beforePath, afterPath));
|
||||
|
||||
if (json) {
|
||||
logger->cout(toJSON(diff).dump());
|
||||
} else {
|
||||
renderDiffInfo(diff, indent);
|
||||
}
|
||||
|
||||
co_return result::success();
|
||||
} catch (...) {
|
||||
|
||||
co_return result::current_exception();
|
||||
}
|
||||
|
||||
@@ -112,7 +186,7 @@ try {
|
||||
|
||||
using namespace nix;
|
||||
|
||||
struct CmdDiffClosures : SourceExprCommand, MixOperateOnOptions
|
||||
struct CmdDiffClosures : SourceExprCommand, MixJSON, MixOperateOnOptions
|
||||
{
|
||||
std::string _before, _after;
|
||||
|
||||
@@ -141,7 +215,7 @@ struct CmdDiffClosures : SourceExprCommand, MixOperateOnOptions
|
||||
auto beforePath = Installable::toStorePath(*state, getEvalStore(), store, Realise::Outputs, operateOn, before);
|
||||
auto after = parseInstallable(*state, store, _after);
|
||||
auto afterPath = Installable::toStorePath(*state, getEvalStore(), store, Realise::Outputs, operateOn, after);
|
||||
aio().blockOn(printClosureDiff(store, beforePath, afterPath, ""));
|
||||
aio().blockOn(printClosureDiff(store, beforePath, afterPath, json, ""));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+44
-13
@@ -26,28 +26,59 @@ of packages, as well as changes in store path sizes.
|
||||
|
||||
For each package name in the two closures (where a package name is
|
||||
defined as the name component of a store path excluding the version),
|
||||
if there is a change in the set of versions of the package, or a
|
||||
change in the size of the store paths of more than 8 KiB, it prints a
|
||||
line like this:
|
||||
it returns information about the change in the set of versions of the
|
||||
package, and the change in the size of the store paths.
|
||||
|
||||
```console
|
||||
dolphin: 20.08.1 → 20.08.2, +13.9 KiB
|
||||
```
|
||||
|
||||
No size change is shown if it's below the threshold. If the package
|
||||
does not exist in either the *before* or *after* closures, it is
|
||||
represented using `∅` (empty set) on the appropriate side of the
|
||||
arrow. If a package has an empty version string, the version is
|
||||
rendered as `ε` (epsilon).
|
||||
If the package does not exist in either the *before* or *after*
|
||||
closures, it is represented using `∅` (empty set) on the appropriate
|
||||
side of the arrow. If a package has an empty version string, the
|
||||
version is rendered as `ε` (epsilon).
|
||||
|
||||
There may be multiple versions of a package in each closure. In that
|
||||
case, only the changed versions are shown. Thus,
|
||||
|
||||
```console
|
||||
libfoo: 1.2, 1.3 → 1.4
|
||||
1.2, 1.3 → 1.4
|
||||
```
|
||||
|
||||
leaves open the possibility that there are other versions (e.g. `1.1`)
|
||||
that exist in both closures.
|
||||
|
||||
## Regular output
|
||||
|
||||
The regular output prints lines like this:
|
||||
|
||||
```console
|
||||
dolphin: 20.08.1 → 20.08.2, +13.9 KiB
|
||||
```
|
||||
|
||||
No size change is shown if it's below the 8 KiB threshold.
|
||||
|
||||
## JSON output
|
||||
|
||||
With `--json`, the output is in a JSON representation suitable for automatic
|
||||
processing by other tools. The resulting object will contain a `schema` key
|
||||
specifying the version of the output structure, and a `packages` key listing
|
||||
the diff information for each package in both closures.
|
||||
|
||||
The printed result looks like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"packages": {
|
||||
"zlib": {
|
||||
"sizeDelta": 118856,
|
||||
"versionsAfter": [
|
||||
"1.3.1"
|
||||
],
|
||||
"versionsBefore": [
|
||||
"1.3"
|
||||
]
|
||||
}
|
||||
// ...
|
||||
},
|
||||
"schema": "lix-closure-diff-v1"
|
||||
}
|
||||
```
|
||||
|
||||
)""
|
||||
|
||||
@@ -481,6 +481,7 @@ struct CmdProfileDiffClosures : virtual StoreCommand, MixDefaultProfile
|
||||
aio().blockOn(printClosureDiff(store,
|
||||
store->followLinksToStorePath(prevGen->path),
|
||||
store->followLinksToStorePath(gen.path),
|
||||
false,
|
||||
" "));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user