#include "command.hh" #include "cmd-profiles.hh" #include "shared.hh" #include "store-api.hh" #include "common-args.hh" #include "names.hh" #include 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()}; // Used to keep name alive through being potentially overwritten below // (to not invalidate the references from the regex result) // // n.b. cannot be just path.name().{begin,end}() since that returns const // char *, which does not, for some reason, convert as required on // libstdc++. Seems like a libstdc++ bug or standard bug to me... we // can afford the allocation in any case. const std::string origName{path.name()}; std::string outputName; if (std::regex_match(origName, match, regex)) { name = match[1]; outputName = match[2]; } DrvName drvName(name); groupedPaths[drvName.name][drvName.version].emplace(path, Info { .outputName = outputName }); } return groupedPaths; } void printClosureDiff( ref store, const StorePath & beforePath, const StorePath & afterPath, std::string_view indent) { 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 = std::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)); logger->cout("%s%s: %s", indent, name, concatStringsSep(", ", items)); } } } } using namespace nix; struct CmdDiffClosures : SourceExprCommand, MixOperateOnOptions { 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"; } std::string doc() override { return #include "diff-closures.md" ; } void run(ref store) override { auto before = parseInstallable(store, _before); auto beforePath = Installable::toStorePath(getEvalStore(), store, Realise::Outputs, operateOn, before); auto after = parseInstallable(store, _after); auto afterPath = Installable::toStorePath(getEvalStore(), store, Realise::Outputs, operateOn, after); printClosureDiff(store, beforePath, afterPath, ""); } }; static auto rCmdDiffClosures = registerCommand2({"store", "diff-closures"});