nix why-depends: Add option to show all edges causing a dependency
For example, without --all:
  $ nix why-depends nixpkgs.nixUnstable nixpkgs.libssh2
  /nix/store/s9n5gvj2l49b4n19nz6xl832654nf7n7-nix-1.12pre5511_c94f3d55
  └───lib/libnixstore.so: …/lib:/nix/store/w9ykqpl5v0r3vfwsgn408jqhs72cx96x-curl-7.55.0/lib…
      => /nix/store/w9ykqpl5v0r3vfwsgn408jqhs72cx96x-curl-7.55.0
      └───lib/libcurl.la: …ib -L/nix/store/4mbayl1y5hpjbjzkx8ndyhkv98kqw1wi-libssh2-1.8.0/l…
          => /nix/store/4mbayl1y5hpjbjzkx8ndyhkv98kqw1wi-libssh2-1.8.0
but with --all:
  $ nix why-depends -a nixpkgs.nixUnstable nixpkgs.libssh2
  /nix/store/s9n5gvj2l49b4n19nz6xl832654nf7n7-nix-1.12pre5511_c94f3d55
  ├───lib/libnixstore.so: …/lib:/nix/store/w9ykqpl5v0r3vfwsgn408jqhs72cx96x-curl-7.55.0/lib…
  │   => /nix/store/w9ykqpl5v0r3vfwsgn408jqhs72cx96x-curl-7.55.0
  │   └───lib/libcurl.la: …ib -L/nix/store/4mbayl1y5hpjbjzkx8ndyhkv98kqw1wi-libssh2-1.8.0/l…
  │       lib/libcurl.so.4.4.0: …/lib:/nix/store/4mbayl1y5hpjbjzkx8ndyhkv98kqw1wi-libssh2-1.8.0/l…
  │       => /nix/store/4mbayl1y5hpjbjzkx8ndyhkv98kqw1wi-libssh2-1.8.0
  └───lib/libnixstore.so: …/lib:/nix/store/bx2i9vi76lps6w9rr73fxf6my31s4dg5-aws-sdk-cpp-1.0…
      => /nix/store/bx2i9vi76lps6w9rr73fxf6my31s4dg5-aws-sdk-cpp-1.0.153
      └───lib/libaws-cpp-sdk-core.so: …e.so./nix/store/w9ykqpl5v0r3vfwsgn408jqhs72cx96x-curl-7.55.0/lib…
          lib/libaws-cpp-sdk-s3.so: …/lib:/nix/store/w9ykqpl5v0r3vfwsgn408jqhs72cx96x-curl-7.55.0/lib…
          => /nix/store/w9ykqpl5v0r3vfwsgn408jqhs72cx96x-curl-7.55.0
			
			
This commit is contained in:
		
							parent
							
								
									d41c5eb13f
								
							
						
					
					
						commit
						fc0ded3408
					
				
					 1 changed files with 156 additions and 34 deletions
				
			
		| 
						 | 
					@ -5,26 +5,44 @@
 | 
				
			||||||
#include "progress-bar.hh"
 | 
					#include "progress-bar.hh"
 | 
				
			||||||
#include "fs-accessor.hh"
 | 
					#include "fs-accessor.hh"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <queue>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using namespace nix;
 | 
					using namespace nix;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static std::string hilite(const std::string & s, size_t pos, size_t len)
 | 
					static std::string hilite(const std::string & s, size_t pos, size_t len,
 | 
				
			||||||
 | 
					    const std::string & colour = ANSI_RED)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    return
 | 
					    return
 | 
				
			||||||
        std::string(s, 0, pos)
 | 
					        std::string(s, 0, pos)
 | 
				
			||||||
        + ANSI_RED
 | 
					        + colour
 | 
				
			||||||
        + std::string(s, pos, len)
 | 
					        + std::string(s, pos, len)
 | 
				
			||||||
        + ANSI_NORMAL
 | 
					        + ANSI_NORMAL
 | 
				
			||||||
        + std::string(s, pos + len);
 | 
					        + 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
 | 
					struct CmdWhyDepends : SourceExprCommand
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    std::string _package, _dependency;
 | 
					    std::string _package, _dependency;
 | 
				
			||||||
 | 
					    bool all = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    CmdWhyDepends()
 | 
					    CmdWhyDepends()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        expectArg("package", &_package);
 | 
					        expectArg("package", &_package);
 | 
				
			||||||
        expectArg("dependency", &_dependency);
 | 
					        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 name() override
 | 
					    std::string name() override
 | 
				
			||||||
| 
						 | 
					@ -67,66 +85,170 @@ struct CmdWhyDepends : SourceExprCommand
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        auto accessor = store->getFSAccessor();
 | 
					        auto accessor = store->getFSAccessor();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // FIXME: show the path through the dependency graph.
 | 
					        auto const inf = std::numeric_limits<size_t>::max();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        bool first = true;
 | 
					        struct Node
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            Path path;
 | 
				
			||||||
 | 
					            PathSet refs;
 | 
				
			||||||
 | 
					            PathSet rrefs;
 | 
				
			||||||
 | 
					            size_t dist = inf;
 | 
				
			||||||
 | 
					            Node * prev = nullptr;
 | 
				
			||||||
 | 
					            bool queued = false;
 | 
				
			||||||
 | 
					            bool visited = false;
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for (auto & path : closure) {
 | 
					        std::map<Path, Node> graph;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (path == dependencyPath && packagePath != dependencyPath)
 | 
					        for (auto & path : closure)
 | 
				
			||||||
                continue;
 | 
					            graph.emplace(path, Node{path, store->queryPathInfo(path)->references});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (!store->queryPathInfo(path)->references.count(dependencyPath))
 | 
					        // Transpose the graph.
 | 
				
			||||||
                continue;
 | 
					        for (auto & node : graph)
 | 
				
			||||||
 | 
					            for (auto & ref : node.second.refs)
 | 
				
			||||||
 | 
					                graph[ref].rrefs.insert(node.first);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (!first) std::cerr << "\n";
 | 
					        /* Run Dijkstra's shortest path algorithm to get the distance
 | 
				
			||||||
            first = false;
 | 
					           of every path in the closure to 'dependency'. */
 | 
				
			||||||
 | 
					        graph[dependencyPath].dist = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            std::cerr << fmt("%s:\n", path);
 | 
					        std::priority_queue<Node *> queue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            std::function<void(const Path &)> recurse;
 | 
					        queue.push(&graph.at(dependencyPath));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            recurse = [&](const Path & p) {
 | 
					        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<void(Node &, const string &, const string &)> printNode;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const string treeConn = "├───";
 | 
				
			||||||
 | 
					        const string treeLast = "└───";
 | 
				
			||||||
 | 
					        const string treeLine = "│   ";
 | 
				
			||||||
 | 
					        const string treeNull = "    ";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        struct BailOut { };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        printNode = [&](Node & node, const string & firstPad, const string & tailPad) {
 | 
				
			||||||
 | 
					            assert(node.dist != inf);
 | 
				
			||||||
 | 
					            std::cerr << fmt("%s%s%s%s" ANSI_NORMAL "\n",
 | 
				
			||||||
 | 
					                firstPad,
 | 
				
			||||||
 | 
					                node.visited ? "\e[38;5;244m" : "",
 | 
				
			||||||
 | 
					                firstPad != "" ? "=> " : "",
 | 
				
			||||||
 | 
					                node.path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (node.path == dependencyPath && !all) 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<size_t, Node *> refs;
 | 
				
			||||||
 | 
					            std::set<std::string> hashes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for (auto & ref : node.refs) {
 | 
				
			||||||
 | 
					                if (ref == node.path) continue;
 | 
				
			||||||
 | 
					                auto & node2 = graph.at(ref);
 | 
				
			||||||
 | 
					                if (node2.dist == inf) continue;
 | 
				
			||||||
 | 
					                refs.emplace(node2.dist, &node2);
 | 
				
			||||||
 | 
					                hashes.insert(storePathToHash(node2.path));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            /* For each reference, find the files and symlinks that
 | 
				
			||||||
 | 
					               contain the reference. */
 | 
				
			||||||
 | 
					            std::map<std::string, Strings> hits;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            std::function<void(const Path &)> visitPath;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            visitPath = [&](const Path & p) {
 | 
				
			||||||
                auto st = accessor->stat(p);
 | 
					                auto st = accessor->stat(p);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                auto p2 = p == path ? "/" : std::string(p, path.size() + 1);
 | 
					                auto p2 = p == node.path ? "/" : std::string(p, node.path.size() + 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                auto getColour = [&](const std::string & hash) {
 | 
				
			||||||
 | 
					                    return hash == dependencyPathHash ? ANSI_GREEN : ANSI_BLUE;
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (st.type == FSAccessor::Type::tDirectory) {
 | 
					                if (st.type == FSAccessor::Type::tDirectory) {
 | 
				
			||||||
                    auto names = accessor->readDirectory(p);
 | 
					                    auto names = accessor->readDirectory(p);
 | 
				
			||||||
                    for (auto & name : names)
 | 
					                    for (auto & name : names)
 | 
				
			||||||
                        recurse(p + "/" + name);
 | 
					                        visitPath(p + "/" + name);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                else if (st.type == FSAccessor::Type::tRegular) {
 | 
					                else if (st.type == FSAccessor::Type::tRegular) {
 | 
				
			||||||
                    auto contents = accessor->readFile(p);
 | 
					                    auto contents = accessor->readFile(p);
 | 
				
			||||||
                    auto pos = contents.find(dependencyPathHash);
 | 
					
 | 
				
			||||||
 | 
					                    for (auto & hash : hashes) {
 | 
				
			||||||
 | 
					                        auto pos = contents.find(hash);
 | 
				
			||||||
                        if (pos != std::string::npos) {
 | 
					                        if (pos != std::string::npos) {
 | 
				
			||||||
                            size_t margin = 16;
 | 
					                            size_t margin = 16;
 | 
				
			||||||
                            auto pos2 = pos >= margin ? pos - margin : 0;
 | 
					                            auto pos2 = pos >= margin ? pos - margin : 0;
 | 
				
			||||||
                        std::string fragment;
 | 
					                            hits[hash].emplace_back(fmt("%s: …%s…\n",
 | 
				
			||||||
                        for (char c : std::string(contents, pos2,
 | 
					 | 
				
			||||||
                                pos - pos2 + dependencyPathHash.size() + margin))
 | 
					 | 
				
			||||||
                        {
 | 
					 | 
				
			||||||
                            fragment += isprint(c) ? c : '.';
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        std::cerr << fmt("  %s: …%s…\n",
 | 
					 | 
				
			||||||
                                    p2,
 | 
					                                    p2,
 | 
				
			||||||
                            hilite(fragment, pos - pos2, storePathHashLen));
 | 
					                                    hilite(filterPrintable(
 | 
				
			||||||
 | 
					                                            std::string(contents, pos2, pos - pos2 + hash.size() + margin)),
 | 
				
			||||||
 | 
					                                        pos - pos2, storePathHashLen,
 | 
				
			||||||
 | 
					                                        getColour(hash))));
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                else if (st.type == FSAccessor::Type::tSymlink) {
 | 
					                else if (st.type == FSAccessor::Type::tSymlink) {
 | 
				
			||||||
                    auto target = accessor->readLink(p);
 | 
					                    auto target = accessor->readLink(p);
 | 
				
			||||||
                    auto pos = target.find(dependencyPathHash);
 | 
					
 | 
				
			||||||
                    if (pos != std::string::npos) {
 | 
					                    for (auto & hash : hashes) {
 | 
				
			||||||
                        std::cerr << fmt("  %s -> %s\n", p2, hilite(target, pos, storePathHashLen));
 | 
					                        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))));
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            recurse(path);
 | 
					            visitPath(node.path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for (auto & ref : refs) {
 | 
				
			||||||
 | 
					                auto hash = storePathToHash(ref.second->path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                bool last = all ? ref == *refs.rbegin() : true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                for (auto & hit : hits[hash]) {
 | 
				
			||||||
 | 
					                    bool first = hit == *hits[hash].begin();
 | 
				
			||||||
 | 
					                    std::cerr << 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 & ) { }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue