Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(325)

Side by Side Diff: tools/gn/command_path.cc

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

Powered by Google App Engine
This is Rietveld 408576698