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
|
@ -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…
Reference in a new issue