-
Notifications
You must be signed in to change notification settings - Fork 0
/
command_path.cc
415 lines (355 loc) · 13.9 KB
/
command_path.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stddef.h>
#include <algorithm>
#include "base/command_line.h"
#include "base/containers/hash_tables.h"
#include "base/strings/stringprintf.h"
#include "tools/gn/commands.h"
#include "tools/gn/setup.h"
#include "tools/gn/standard_out.h"
namespace commands {
namespace {
enum class DepType {
NONE,
PUBLIC,
PRIVATE,
DATA
};
// The dependency paths are stored in a vector. Assuming the chain:
// A --[public]--> B --[private]--> C
// The stack will look like:
// [0] = A, NONE (this has no dep type since nobody depends on it)
// [1] = B, PUBLIC
// [2] = C, PRIVATE
using TargetDep = std::pair<const Target*, DepType>;
using PathVector = std::vector<TargetDep>;
// How to search.
enum class PrivateDeps { INCLUDE, EXCLUDE };
enum class DataDeps { INCLUDE, EXCLUDE };
enum class PrintWhat { ONE, ALL };
struct Options {
Options()
: print_what(PrintWhat::ONE),
public_only(false),
with_data(false) {
}
PrintWhat print_what;
bool public_only;
bool with_data;
};
typedef std::list<PathVector> WorkQueue;
struct Stats {
Stats() : public_paths(0), other_paths(0) {
}
int total_paths() const { return public_paths + other_paths; }
int public_paths;
int other_paths;
// Stores targets that have a path to the destination, and whether that
// path is public, private, or data.
std::map<const Target*, DepType> found_paths;
};
// If the implicit_last_dep is not "none", this type indicates the
// classification of the elided last part of path.
DepType ClassifyPath(const PathVector& path, DepType implicit_last_dep) {
DepType result;
if (implicit_last_dep != DepType::NONE)
result = implicit_last_dep;
else
result = DepType::PUBLIC;
// Skip the 0th one since that is always NONE.
for (size_t i = 1; i < path.size(); i++) {
// PRIVATE overrides PUBLIC, and DATA overrides everything (the idea is
// to find the worst link in the path).
if (path[i].second == DepType::PRIVATE) {
if (result == DepType::PUBLIC)
result = DepType::PRIVATE;
} else if (path[i].second == DepType::DATA) {
result = DepType::DATA;
}
}
return result;
}
const char* StringForDepType(DepType type) {
switch(type) {
case DepType::PUBLIC:
return "public";
case DepType::PRIVATE:
return "private";
case DepType::DATA:
return "data";
break;
case DepType::NONE:
default:
return "";
}
}
// Prints the given path. If the implicit_last_dep is not "none", the last
// dependency will show an elided dependency with the given annotation.
void PrintPath(const PathVector& path, DepType implicit_last_dep) {
if (path.empty())
return;
// Don't print toolchains unless they differ from the first target.
const Label& default_toolchain = path[0].first->label().GetToolchainLabel();
for (size_t i = 0; i < path.size(); i++) {
OutputString(path[i].first->label().GetUserVisibleName(default_toolchain));
// Output dependency type.
if (i == path.size() - 1) {
// Last one either gets the implicit last dep type or nothing.
if (implicit_last_dep != DepType::NONE) {
OutputString(std::string(" --> see ") +
StringForDepType(implicit_last_dep) +
" chain printed above...", DECORATION_DIM);
}
} else {
// Take type from the next entry.
OutputString(std::string(" --[") + StringForDepType(path[i + 1].second) +
"]-->", DECORATION_DIM);
}
OutputString("\n");
}
OutputString("\n");
}
void InsertTargetsIntoFoundPaths(const PathVector& path,
DepType implicit_last_dep,
Stats* stats) {
DepType type = ClassifyPath(path, implicit_last_dep);
bool inserted = false;
// Don't try to insert the 0th item in the list which is the "from" target.
// The search will be run more than once (for the different path types) and
// if the "from" target was in the list, subsequent passes could never run
// the starting point is alredy in the list of targets considered).
//
// One might imagine an alternate implementation where all items are counted
// here but the "from" item is erased at the beginning of each search, but
// that will mess up the metrics (the private search pass will find the
// same public paths as the previous public pass, "inserted" will be true
// here since the item wasn't found, and the public path will be
// double-counted in the stats.
for (size_t i = 1; i < path.size(); i++) {
const auto& pair = path[i];
// Don't overwrite an existing one. The algorithm works by first doing
// public, then private, then data, so anything already there is guaranteed
// at least as good as our addition.
if (stats->found_paths.find(pair.first) == stats->found_paths.end()) {
stats->found_paths.insert(std::make_pair(pair.first, type));
inserted = true;
}
}
if (inserted) {
// Only count this path in the stats if any part of it was actually new.
if (type == DepType::PUBLIC)
stats->public_paths++;
else
stats->other_paths++;
}
}
void BreadthFirstSearch(const Target* from, const Target* to,
PrivateDeps private_deps, DataDeps data_deps,
PrintWhat print_what,
Stats* stats) {
// Seed the initial stack with just the "from" target.
PathVector initial_stack;
initial_stack.emplace_back(from, DepType::NONE);
WorkQueue work_queue;
work_queue.push_back(initial_stack);
// Track checked targets to avoid checking the same once more than once.
std::set<const Target*> visited;
while (!work_queue.empty()) {
PathVector current_path = work_queue.front();
work_queue.pop_front();
const Target* current_target = current_path.back().first;
if (current_target == to) {
// Found a new path.
if (stats->total_paths() == 0 || print_what == PrintWhat::ALL)
PrintPath(current_path, DepType::NONE);
// Insert all nodes on the path into the found paths list. Since we're
// doing search breadth first, we know that the current path is the best
// path for all nodes on it.
InsertTargetsIntoFoundPaths(current_path, DepType::NONE, stats);
} else {
// Check for a path that connects to an already known-good one. Printing
// this here will mean the results aren't strictly in depth-first order
// since there could be many items on the found path this connects to.
// Doing this here will mean that the output is sorted by length of items
// printed (with the redundant parts of the path omitted) rather than
// complete path length.
const auto& found_current_target =
stats->found_paths.find(current_target);
if (found_current_target != stats->found_paths.end()) {
if (stats->total_paths() == 0 || print_what == PrintWhat::ALL)
PrintPath(current_path, found_current_target->second);
// Insert all nodes on the path into the found paths list since we know
// everything along this path also leads to the destination.
InsertTargetsIntoFoundPaths(current_path, found_current_target->second,
stats);
continue;
}
}
// If we've already checked this one, stop. This should be after the above
// check for a known-good check, because known-good ones will always have
// been previously visited.
if (visited.find(current_target) == visited.end())
visited.insert(current_target);
else
continue;
// Add public deps for this target to the queue.
for (const auto& pair : current_target->public_deps()) {
work_queue.push_back(current_path);
work_queue.back().push_back(TargetDep(pair.ptr, DepType::PUBLIC));
}
if (private_deps == PrivateDeps::INCLUDE) {
// Add private deps.
for (const auto& pair : current_target->private_deps()) {
work_queue.push_back(current_path);
work_queue.back().push_back(
TargetDep(pair.ptr, DepType::PRIVATE));
}
}
if (data_deps == DataDeps::INCLUDE) {
// Add data deps.
for (const auto& pair : current_target->data_deps()) {
work_queue.push_back(current_path);
work_queue.back().push_back(TargetDep(pair.ptr, DepType::DATA));
}
}
}
}
void DoSearch(const Target* from, const Target* to, const Options& options,
Stats* stats) {
BreadthFirstSearch(from, to, PrivateDeps::EXCLUDE, DataDeps::EXCLUDE,
options.print_what, stats);
if (!options.public_only) {
// Check private deps.
BreadthFirstSearch(from, to, PrivateDeps::INCLUDE,
DataDeps::EXCLUDE, options.print_what, stats);
if (options.with_data) {
// Check data deps.
BreadthFirstSearch(from, to, PrivateDeps::INCLUDE,
DataDeps::INCLUDE, options.print_what, stats);
}
}
}
} // namespace
const char kPath[] = "path";
const char kPath_HelpShort[] =
"path: Find paths between two targets.";
const char kPath_Help[] =
R"(gn path <out_dir> <target_one> <target_two>
Finds paths of dependencies between two targets. Each unique path will be
printed in one group, and groups will be separate by newlines. The two
targets can appear in either order (paths will be found going in either
direction).
By default, a single path will be printed. If there is a path with only
public dependencies, the shortest public path will be printed. Otherwise, the
shortest path using either public or private dependencies will be printed. If
--with-data is specified, data deps will also be considered. If there are
multiple shortest paths, an arbitrary one will be selected.
Interesting paths
In a large project, there can be 100's of millions of unique paths between a
very high level and a common low-level target. To make the output more useful
(and terminate in a reasonable time), GN will not revisit sub-paths
previously known to lead to the target.
Options
--all
Prints all "interesting" paths found rather than just the first one.
Public paths will be printed first in order of increasing length, followed
by non-public paths in order of increasing length.
--public
Considers only public paths. Can't be used with --with-data.
--with-data
Additionally follows data deps. Without this flag, only public and private
linked deps will be followed. Can't be used with --public.
Example
gn path out/Default //base //tools/gn
)";
int RunPath(const std::vector<std::string>& args) {
if (args.size() != 3) {
Err(Location(), "You're holding it wrong.",
"Usage: \"gn path <out_dir> <target_one> <target_two>\"")
.PrintToStdout();
return 1;
}
Setup* setup = new Setup;
if (!setup->DoSetup(args[0], false))
return 1;
if (!setup->Run())
return 1;
const Target* target1 = ResolveTargetFromCommandLineString(setup, args[1]);
if (!target1)
return 1;
const Target* target2 = ResolveTargetFromCommandLineString(setup, args[2]);
if (!target2)
return 1;
Options options;
options.print_what = base::CommandLine::ForCurrentProcess()->HasSwitch("all")
? PrintWhat::ALL : PrintWhat::ONE;
options.public_only =
base::CommandLine::ForCurrentProcess()->HasSwitch("public");
options.with_data =
base::CommandLine::ForCurrentProcess()->HasSwitch("with-data");
if (options.public_only && options.with_data) {
Err(Location(), "Can't use --public with --with-data for 'gn path'.",
"Your zealous over-use of arguments has inevitably resulted in an "
"invalid\ncombination of flags.").PrintToStdout();
return 1;
}
Stats stats;
DoSearch(target1, target2, options, &stats);
if (stats.total_paths() == 0) {
// If we don't find a path going "forwards", try the reverse direction.
// Deps can only go in one direction without having a cycle, which will
// have caused a run failure above.
DoSearch(target2, target1, options, &stats);
}
// This string is inserted in the results to annotate whether the result
// is only public or includes data deps or not.
const char* path_annotation = "";
if (options.public_only)
path_annotation = "public ";
else if (!options.with_data)
path_annotation = "non-data ";
if (stats.total_paths() == 0) {
// No results.
OutputString(base::StringPrintf(
"No %spaths found between these two targets.\n", path_annotation),
DECORATION_YELLOW);
} else if (stats.total_paths() == 1) {
// Exactly one result.
OutputString(base::StringPrintf("1 %spath found.", path_annotation),
DECORATION_YELLOW);
if (!options.public_only) {
if (stats.public_paths)
OutputString(" It is public.");
else
OutputString(" It is not public.");
}
OutputString("\n");
} else {
if (options.print_what == PrintWhat::ALL) {
// Showing all paths when there are many.
OutputString(base::StringPrintf("%d \"interesting\" %spaths found.",
stats.total_paths(), path_annotation),
DECORATION_YELLOW);
if (!options.public_only) {
OutputString(base::StringPrintf(" %d of them are public.",
stats.public_paths));
}
OutputString("\n");
} else {
// Showing one path when there are many.
OutputString(
base::StringPrintf("Showing one of %d \"interesting\" %spaths.",
stats.total_paths(), path_annotation),
DECORATION_YELLOW);
if (!options.public_only) {
OutputString(
base::StringPrintf(" %d of them are public.", stats.public_paths));
}
OutputString("\nUse --all to print all paths.\n");
}
}
return 0;
}
} // namespace commands