From 0356f14459f1fbb855fd5ec75424c2a4c84c1000 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 15 Jul 2020 16:18:06 +0200 Subject: [PATCH] Add 'nix diff-closures' command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This command makes it easier to see what changed between two closures, i.e. what packages/versions got added or removed, and whether there were any notable changes in path size. For example: $ nix diff-closures /nix/var/nix/profiles/system-655-link /nix/var/nix/profiles/system-658-link blender-bin: 2.83.0 → 2.83.2, -294.2 KiB curl: 7.68.0 → 7.70.0, +19.1 KiB firmware-linux-nonfree: 2020-01-22 → 2020-05-19, +30827.7 KiB ibus: -21.8 KiB initrd-linux: 5.4.46 → 5.4.51, +16.9 KiB libexif: 0.6.21 → 0.6.22, +497.6 KiB linux: 5.4.46 → 5.4.51, +13.2 KiB mesa: 19.3.3 → 19.3.5, -183.9 KiB nix: 2.4pre20200701_6ff9aa8 → 2.4pre20200708_9223603, +9.7 KiB nix-bash-completions: 0.6.8 → ∅, -57.6 KiB nixos-system-hagbard: 20.03.20200615.a84b797 → 20.03.20200713.add5529 nvidia-persistenced: 440.82 → 440.100 nvidia-settings: 440.82 → 440.100 nvidia-x11: 440.82-5.4.46 → 440.100-5.4.51, +664.7 KiB pcre: 8.43 → 8.44 php: 7.3.16 → 7.3.20, -26.2 KiB python3.7-youtube-dl: 2020.06.06 → 2020.06.16.1, +8.4 KiB samba: 4.11.5 → 4.11.9, +30.1 KiB sane-backends: 1.0.28 → 1.0.30, +680.5 KiB source: -182.0 KiB zfs-kernel: 0.8.3-5.4.46 → 0.8.4-5.4.51, +9.9 KiB zfs-user: 0.8.3 → 0.8.4, +20.1 KiB --- src/nix/diff-closures.cc | 134 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 src/nix/diff-closures.cc diff --git a/src/nix/diff-closures.cc b/src/nix/diff-closures.cc new file mode 100644 index 000000000..df0b26ab1 --- /dev/null +++ b/src/nix/diff-closures.cc @@ -0,0 +1,134 @@ +#include "command.hh" +#include "shared.hh" +#include "store-api.hh" +#include "common-args.hh" +#include "names.hh" + +#include + +using namespace nix; + +struct Info +{ + std::string outputName; +}; + +// name -> version -> store paths +typedef std::map>> GroupedPaths; + +GroupedPaths getClosureInfo(ref store, const StorePath & toplevel) +{ + StorePathSet closure; + store->computeFSClosure({toplevel}, closure); + + GroupedPaths groupedPaths; + + for (auto & path : closure) { + /* Strip the output name. Unfortunately this is ambiguous (we + can't distinguish between output names like "bin" and + version suffixes like "unstable"). */ + static std::regex regex("(.*)-([a-z]+|lib32|lib64)"); + std::smatch match; + std::string name(path.name()); + std::string outputName; + if (std::regex_match(name, match, regex)) { + name = match[1]; + outputName = match[2]; + } + + DrvName drvName(name); + groupedPaths[drvName.name][drvName.version].emplace(path, Info { .outputName = outputName }); + } + + return groupedPaths; +} + +std::string showVersions(const std::set & versions) +{ + if (versions.empty()) return "∅"; + std::set versions2; + for (auto & version : versions) + versions2.insert(version.empty() ? "ε" : version); + return concatStringsSep(", ", versions2); +} + +struct CmdDiffClosures : SourceExprCommand +{ + std::string _before, _after; + + CmdDiffClosures() + { + expectArg("before", &_before); + expectArg("after", &_after); + } + + std::string description() override + { + return "show what packages and versions were added and removed between two closures"; + } + + Category category() override { return catSecondary; } + + Examples examples() override + { + return { + { + "To show what got added and removed between two versions of the NixOS system profile:", + "nix diff-closures /nix/var/nix/profiles/system-655-link /nix/var/nix/profiles/system-658-link", + }, + }; + } + + void run(ref store) override + { + auto before = parseInstallable(*this, store, _before, false); + auto beforePath = toStorePath(store, Build, before); + auto after = parseInstallable(*this, store, _after, false); + auto afterPath = toStorePath(store, NoBuild, after); + + auto beforeClosure = getClosureInfo(store, beforePath); + auto afterClosure = getClosureInfo(store, afterPath); + + std::set allNames; + for (auto & [name, _] : beforeClosure) allNames.insert(name); + for (auto & [name, _] : afterClosure) allNames.insert(name); + + for (auto & name : allNames) { + auto & beforeVersions = beforeClosure[name]; + auto & afterVersions = afterClosure[name]; + + auto totalSize = [&](const std::map> & versions) + { + uint64_t sum = 0; + for (auto & [_, paths] : versions) + for (auto & [path, _] : paths) + sum += store->queryPathInfo(path)->narSize; + return sum; + }; + + auto beforeSize = totalSize(beforeVersions); + auto afterSize = totalSize(afterVersions); + auto sizeDelta = (int64_t) afterSize - (int64_t) beforeSize; + auto showDelta = abs(sizeDelta) >= 8 * 1024; + + std::set removed, unchanged; + for (auto & [version, _] : beforeVersions) + if (!afterVersions.count(version)) removed.insert(version); else unchanged.insert(version); + + std::set added; + for (auto & [version, _] : afterVersions) + if (!beforeVersions.count(version)) added.insert(version); + + if (showDelta || !removed.empty() || !added.empty()) { + std::vector 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)); + std::cout << fmt("%s: %s\n", name, concatStringsSep(", ", items)); + } + } + } +}; + +static auto r1 = registerCommand("diff-closures");