• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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