#include "command.hh" #include "store-api.hh" #include "progress-bar.hh" #include "fs-accessor.hh" #include "shared.hh" #include using namespace nix; static std::string hilite(const std::string & s, size_t pos, size_t len, const std::string & colour = ANSI_RED) { return std::string(s, 0, pos) + colour + std::string(s, pos, len) + ANSI_NORMAL + std::string(s, pos + len); } static std::string filterPrintable(const std::string & s) { std::string res; for (char c : s) res += isprint(c) ? c : '.'; return res; } struct CmdWhyDepends : SourceExprCommand { std::string _package, _dependency; bool all = false; CmdWhyDepends() { expectArg("package", &_package); expectArg("dependency", &_dependency); mkFlag() .longName("all") .shortName('a') .description("show all edges in the dependency graph leading from 'package' to 'dependency', rather than just a shortest path") .set(&all, true); } std::string description() override { return "show why a package has another package in its closure"; } Examples examples() override { return { Example{ "To show one path through the dependency graph leading from Hello to Glibc:", "nix why-depends nixpkgs.hello nixpkgs.glibc" }, Example{ "To show all files and paths in the dependency graph leading from Thunderbird to libX11:", "nix why-depends --all nixpkgs.thunderbird nixpkgs.xorg.libX11" }, Example{ "To show why Glibc depends on itself:", "nix why-depends nixpkgs.glibc nixpkgs.glibc" }, }; } void run(ref store) override { auto package = parseInstallable(store, _package); auto packagePath = toStorePath(store, Build, package); auto dependency = parseInstallable(store, _dependency); auto dependencyPath = toStorePath(store, NoBuild, dependency); auto dependencyPathHash = storePathToHash(store->printStorePath(dependencyPath)); StorePathSet closure; store->computeFSClosure({packagePath}, closure, false, false); if (!closure.count(dependencyPath)) { printError("'%s' does not depend on '%s'", package->what(), dependency->what()); return; } stopProgressBar(); // FIXME auto accessor = store->getFSAccessor(); auto const inf = std::numeric_limits::max(); struct Node { StorePath path; StorePathSet refs; StorePathSet rrefs; size_t dist = inf; Node * prev = nullptr; bool queued = false; bool visited = false; }; std::map graph; for (auto & path : closure) graph.emplace(path.clone(), Node{path.clone(), cloneStorePathSet(store->queryPathInfo(path)->references)}); // Transpose the graph. for (auto & node : graph) for (auto & ref : node.second.refs) graph.find(ref)->second.rrefs.insert(node.first.clone()); /* Run Dijkstra's shortest path algorithm to get the distance of every path in the closure to 'dependency'. */ graph[dependencyPath.clone()].dist = 0; std::priority_queue queue; queue.push(&graph.at(dependencyPath)); while (!queue.empty()) { auto & node = *queue.top(); queue.pop(); for (auto & rref : node.rrefs) { auto & node2 = graph.at(rref); auto dist = node.dist + 1; if (dist < node2.dist) { node2.dist = dist; node2.prev = &node; if (!node2.queued) { node2.queued = true; queue.push(&node2); } } } } /* Print the subgraph of nodes that have 'dependency' in their closure (i.e., that have a non-infinite distance to 'dependency'). Print every edge on a path between `package` and `dependency`. */ std::function printNode; struct BailOut { }; printNode = [&](Node & node, const string & firstPad, const string & tailPad) { auto pathS = store->printStorePath(node.path); assert(node.dist != inf); std::cout << fmt("%s%s%s%s" ANSI_NORMAL "\n", firstPad, node.visited ? "\e[38;5;244m" : "", firstPad != "" ? "→ " : "", pathS); if (node.path == dependencyPath && !all && packagePath != dependencyPath) throw BailOut(); if (node.visited) return; node.visited = true; /* Sort the references by distance to `dependency` to ensure that the shortest path is printed first. */ std::multimap refs; std::set hashes; for (auto & ref : node.refs) { if (ref == node.path && packagePath != dependencyPath) continue; auto & node2 = graph.at(ref); if (node2.dist == inf) continue; refs.emplace(node2.dist, &node2); hashes.insert(storePathToHash(store->printStorePath(node2.path))); } /* For each reference, find the files and symlinks that contain the reference. */ std::map hits; std::function visitPath; visitPath = [&](const Path & p) { auto st = accessor->stat(p); auto p2 = p == pathS ? "/" : std::string(p, pathS.size() + 1); auto getColour = [&](const std::string & hash) { return hash == dependencyPathHash ? ANSI_GREEN : ANSI_BLUE; }; if (st.type == FSAccessor::Type::tDirectory) { auto names = accessor->readDirectory(p); for (auto & name : names) visitPath(p + "/" + name); } else if (st.type == FSAccessor::Type::tRegular) { auto contents = accessor->readFile(p); for (auto & hash : hashes) { auto pos = contents.find(hash); if (pos != std::string::npos) { size_t margin = 32; auto pos2 = pos >= margin ? pos - margin : 0; hits[hash].emplace_back(fmt("%s: …%s…\n", p2, hilite(filterPrintable( std::string(contents, pos2, pos - pos2 + hash.size() + margin)), pos - pos2, storePathHashLen, getColour(hash)))); } } } else if (st.type == FSAccessor::Type::tSymlink) { auto target = accessor->readLink(p); for (auto & hash : hashes) { auto pos = target.find(hash); if (pos != std::string::npos) hits[hash].emplace_back(fmt("%s -> %s\n", p2, hilite(target, pos, storePathHashLen, getColour(hash)))); } } }; // FIXME: should use scanForReferences(). visitPath(pathS); RunPager pager; for (auto & ref : refs) { auto hash = storePathToHash(store->printStorePath(ref.second->path)); bool last = all ? ref == *refs.rbegin() : true; for (auto & hit : hits[hash]) { bool first = hit == *hits[hash].begin(); std::cout << tailPad << (first ? (last ? treeLast : treeConn) : (last ? treeNull : treeLine)) << hit; if (!all) break; } printNode(*ref.second, tailPad + (last ? treeNull : treeLine), tailPad + (last ? treeNull : treeLine)); } }; try { printNode(graph.at(packagePath), "", ""); } catch (BailOut & ) { } } }; static auto r1 = registerCommand("why-depends");