Skip to content

Commit fc0ded3

Browse files
committedSep 11, 2017
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
1 parent d41c5eb commit fc0ded3

File tree

1 file changed

+156
-34
lines changed

1 file changed

+156
-34
lines changed
 

‎src/nix/why-depends.cc

+156-34
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,44 @@
55
#include "progress-bar.hh"
66
#include "fs-accessor.hh"
77

8+
#include <queue>
9+
810
using namespace nix;
911

10-
static std::string hilite(const std::string & s, size_t pos, size_t len)
12+
static std::string hilite(const std::string & s, size_t pos, size_t len,
13+
const std::string & colour = ANSI_RED)
1114
{
1215
return
1316
std::string(s, 0, pos)
14-
+ ANSI_RED
17+
+ colour
1518
+ std::string(s, pos, len)
1619
+ ANSI_NORMAL
1720
+ std::string(s, pos + len);
1821
}
1922

23+
static std::string filterPrintable(const std::string & s)
24+
{
25+
std::string res;
26+
for (char c : s)
27+
res += isprint(c) ? c : '.';
28+
return res;
29+
}
30+
2031
struct CmdWhyDepends : SourceExprCommand
2132
{
2233
std::string _package, _dependency;
34+
bool all = false;
2335

2436
CmdWhyDepends()
2537
{
2638
expectArg("package", &_package);
2739
expectArg("dependency", &_dependency);
40+
41+
mkFlag()
42+
.longName("all")
43+
.shortName('a')
44+
.description("show all edges in the dependency graph leading from 'package' to 'dependency', rather than just a shortest path")
45+
.set(&all, true);
2846
}
2947

3048
std::string name() override
@@ -67,66 +85,170 @@ struct CmdWhyDepends : SourceExprCommand
6785

6886
auto accessor = store->getFSAccessor();
6987

70-
// FIXME: show the path through the dependency graph.
88+
auto const inf = std::numeric_limits<size_t>::max();
89+
90+
struct Node
91+
{
92+
Path path;
93+
PathSet refs;
94+
PathSet rrefs;
95+
size_t dist = inf;
96+
Node * prev = nullptr;
97+
bool queued = false;
98+
bool visited = false;
99+
};
100+
101+
std::map<Path, Node> graph;
102+
103+
for (auto & path : closure)
104+
graph.emplace(path, Node{path, store->queryPathInfo(path)->references});
105+
106+
// Transpose the graph.
107+
for (auto & node : graph)
108+
for (auto & ref : node.second.refs)
109+
graph[ref].rrefs.insert(node.first);
71110

72-
bool first = true;
111+
/* Run Dijkstra's shortest path algorithm to get the distance
112+
of every path in the closure to 'dependency'. */
113+
graph[dependencyPath].dist = 0;
114+
115+
std::priority_queue<Node *> queue;
116+
117+
queue.push(&graph.at(dependencyPath));
118+
119+
while (!queue.empty()) {
120+
auto & node = *queue.top();
121+
queue.pop();
122+
123+
for (auto & rref : node.rrefs) {
124+
auto & node2 = graph.at(rref);
125+
auto dist = node.dist + 1;
126+
if (dist < node2.dist) {
127+
node2.dist = dist;
128+
node2.prev = &node;
129+
if (!node2.queued) {
130+
node2.queued = true;
131+
queue.push(&node2);
132+
}
133+
}
134+
135+
}
136+
}
73137

74-
for (auto & path : closure) {
138+
/* Print the subgraph of nodes that have 'dependency' in their
139+
closure (i.e., that have a non-infinite distance to
140+
'dependency'). Print every edge on a path between `package`
141+
and `dependency`. */
142+
std::function<void(Node &, const string &, const string &)> printNode;
75143

76-
if (path == dependencyPath && packagePath != dependencyPath)
77-
continue;
144+
const string treeConn = "├───";
145+
const string treeLast = "└───";
146+
const string treeLine = "";
147+
const string treeNull = " ";
78148

79-
if (!store->queryPathInfo(path)->references.count(dependencyPath))
80-
continue;
149+
struct BailOut { };
81150

82-
if (!first) std::cerr << "\n";
83-
first = false;
151+
printNode = [&](Node & node, const string & firstPad, const string & tailPad) {
152+
assert(node.dist != inf);
153+
std::cerr << fmt("%s%s%s%s" ANSI_NORMAL "\n",
154+
firstPad,
155+
node.visited ? "\e[38;5;244m" : "",
156+
firstPad != "" ? "=> " : "",
157+
node.path);
84158

85-
std::cerr << fmt("%s:\n", path);
159+
if (node.path == dependencyPath && !all) throw BailOut();
86160

87-
std::function<void(const Path &)> recurse;
161+
if (node.visited) return;
162+
node.visited = true;
88163

89-
recurse = [&](const Path & p) {
164+
/* Sort the references by distance to `dependency` to
165+
ensure that the shortest path is printed first. */
166+
std::multimap<size_t, Node *> refs;
167+
std::set<std::string> hashes;
168+
169+
for (auto & ref : node.refs) {
170+
if (ref == node.path) continue;
171+
auto & node2 = graph.at(ref);
172+
if (node2.dist == inf) continue;
173+
refs.emplace(node2.dist, &node2);
174+
hashes.insert(storePathToHash(node2.path));
175+
}
176+
177+
/* For each reference, find the files and symlinks that
178+
contain the reference. */
179+
std::map<std::string, Strings> hits;
180+
181+
std::function<void(const Path &)> visitPath;
182+
183+
visitPath = [&](const Path & p) {
90184
auto st = accessor->stat(p);
91185

92-
auto p2 = p == path ? "/" : std::string(p, path.size() + 1);
186+
auto p2 = p == node.path ? "/" : std::string(p, node.path.size() + 1);
187+
188+
auto getColour = [&](const std::string & hash) {
189+
return hash == dependencyPathHash ? ANSI_GREEN : ANSI_BLUE;
190+
};
93191

94192
if (st.type == FSAccessor::Type::tDirectory) {
95193
auto names = accessor->readDirectory(p);
96194
for (auto & name : names)
97-
recurse(p + "/" + name);
195+
visitPath(p + "/" + name);
98196
}
99197

100198
else if (st.type == FSAccessor::Type::tRegular) {
101199
auto contents = accessor->readFile(p);
102-
auto pos = contents.find(dependencyPathHash);
103-
if (pos != std::string::npos) {
104-
size_t margin = 16;
105-
auto pos2 = pos >= margin ? pos - margin : 0;
106-
std::string fragment;
107-
for (char c : std::string(contents, pos2,
108-
pos - pos2 + dependencyPathHash.size() + margin))
109-
{
110-
fragment += isprint(c) ? c : '.';
111-
}
112200

113-
std::cerr << fmt(" %s: …%s…\n",
114-
p2,
115-
hilite(fragment, pos - pos2, storePathHashLen));
201+
for (auto & hash : hashes) {
202+
auto pos = contents.find(hash);
203+
if (pos != std::string::npos) {
204+
size_t margin = 16;
205+
auto pos2 = pos >= margin ? pos - margin : 0;
206+
hits[hash].emplace_back(fmt("%s: …%s…\n",
207+
p2,
208+
hilite(filterPrintable(
209+
std::string(contents, pos2, pos - pos2 + hash.size() + margin)),
210+
pos - pos2, storePathHashLen,
211+
getColour(hash))));
212+
}
116213
}
117214
}
118215

119216
else if (st.type == FSAccessor::Type::tSymlink) {
120217
auto target = accessor->readLink(p);
121-
auto pos = target.find(dependencyPathHash);
122-
if (pos != std::string::npos) {
123-
std::cerr << fmt(" %s -> %s\n", p2, hilite(target, pos, storePathHashLen));
218+
219+
for (auto & hash : hashes) {
220+
auto pos = target.find(hash);
221+
if (pos != std::string::npos)
222+
hits[hash].emplace_back(fmt("%s -> %s\n", p2,
223+
hilite(target, pos, storePathHashLen, getColour(hash))));
124224
}
125225
}
126226
};
127227

128-
recurse(path);
129-
}
228+
visitPath(node.path);
229+
230+
for (auto & ref : refs) {
231+
auto hash = storePathToHash(ref.second->path);
232+
233+
bool last = all ? ref == *refs.rbegin() : true;
234+
235+
for (auto & hit : hits[hash]) {
236+
bool first = hit == *hits[hash].begin();
237+
std::cerr << tailPad
238+
<< (first ? (last ? treeLast : treeConn) : (last ? treeNull : treeLine))
239+
<< hit;
240+
if (!all) break;
241+
}
242+
243+
printNode(*ref.second,
244+
tailPad + (last ? treeNull : treeLine),
245+
tailPad + (last ? treeNull : treeLine));
246+
}
247+
};
248+
249+
try {
250+
printNode(graph.at(packagePath), "", "");
251+
} catch (BailOut & ) { }
130252
}
131253
};
132254

1 commit comments

Comments
 (1)

copumpkin commented on Sep 11, 2017

@copumpkin
Member

Delightful!

Please sign in to comment.