OLD | NEW |
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 Loading... |
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 |
OLD | NEW |