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