|
5 | 5 | #include "progress-bar.hh"
|
6 | 6 | #include "fs-accessor.hh"
|
7 | 7 |
|
| 8 | +#include <queue> |
| 9 | + |
8 | 10 | using namespace nix;
|
9 | 11 |
|
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) |
11 | 14 | {
|
12 | 15 | return
|
13 | 16 | std::string(s, 0, pos)
|
14 |
| - + ANSI_RED |
| 17 | + + colour |
15 | 18 | + std::string(s, pos, len)
|
16 | 19 | + ANSI_NORMAL
|
17 | 20 | + std::string(s, pos + len);
|
18 | 21 | }
|
19 | 22 |
|
| 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 | + |
20 | 31 | struct CmdWhyDepends : SourceExprCommand
|
21 | 32 | {
|
22 | 33 | std::string _package, _dependency;
|
| 34 | + bool all = false; |
23 | 35 |
|
24 | 36 | CmdWhyDepends()
|
25 | 37 | {
|
26 | 38 | expectArg("package", &_package);
|
27 | 39 | 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); |
28 | 46 | }
|
29 | 47 |
|
30 | 48 | std::string name() override
|
@@ -67,66 +85,170 @@ struct CmdWhyDepends : SourceExprCommand
|
67 | 85 |
|
68 | 86 | auto accessor = store->getFSAccessor();
|
69 | 87 |
|
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); |
71 | 110 |
|
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 | + } |
73 | 137 |
|
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; |
75 | 143 |
|
76 |
| - if (path == dependencyPath && packagePath != dependencyPath) |
77 |
| - continue; |
| 144 | + const string treeConn = "├───"; |
| 145 | + const string treeLast = "└───"; |
| 146 | + const string treeLine = "│ "; |
| 147 | + const string treeNull = " "; |
78 | 148 |
|
79 |
| - if (!store->queryPathInfo(path)->references.count(dependencyPath)) |
80 |
| - continue; |
| 149 | + struct BailOut { }; |
81 | 150 |
|
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); |
84 | 158 |
|
85 |
| - std::cerr << fmt("%s:\n", path); |
| 159 | + if (node.path == dependencyPath && !all) throw BailOut(); |
86 | 160 |
|
87 |
| - std::function<void(const Path &)> recurse; |
| 161 | + if (node.visited) return; |
| 162 | + node.visited = true; |
88 | 163 |
|
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) { |
90 | 184 | auto st = accessor->stat(p);
|
91 | 185 |
|
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 | + }; |
93 | 191 |
|
94 | 192 | if (st.type == FSAccessor::Type::tDirectory) {
|
95 | 193 | auto names = accessor->readDirectory(p);
|
96 | 194 | for (auto & name : names)
|
97 |
| - recurse(p + "/" + name); |
| 195 | + visitPath(p + "/" + name); |
98 | 196 | }
|
99 | 197 |
|
100 | 198 | else if (st.type == FSAccessor::Type::tRegular) {
|
101 | 199 | 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 |
| - } |
112 | 200 |
|
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 | + } |
116 | 213 | }
|
117 | 214 | }
|
118 | 215 |
|
119 | 216 | else if (st.type == FSAccessor::Type::tSymlink) {
|
120 | 217 | 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)))); |
124 | 224 | }
|
125 | 225 | }
|
126 | 226 | };
|
127 | 227 |
|
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 & ) { } |
130 | 252 | }
|
131 | 253 | };
|
132 | 254 |
|
|
1 commit comments
copumpkin commentedon Sep 11, 2017
Delightful!