OLD | NEW |
---|---|
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2013 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 #include <set> | 8 #include <set> |
9 #include <sstream> | 9 #include <sstream> |
10 | 10 |
11 #include "base/command_line.h" | 11 #include "base/command_line.h" |
12 #include "build/build_config.h" | 12 #include "base/json/json_writer.h" |
13 #include "base/memory/ptr_util.h" | |
13 #include "tools/gn/commands.h" | 14 #include "tools/gn/commands.h" |
14 #include "tools/gn/config.h" | 15 #include "tools/gn/config.h" |
15 #include "tools/gn/config_values_extractors.h" | 16 #include "tools/gn/desc_builder.h" |
16 #include "tools/gn/deps_iterator.h" | |
17 #include "tools/gn/filesystem_utils.h" | |
18 #include "tools/gn/item.h" | |
19 #include "tools/gn/label.h" | |
20 #include "tools/gn/runtime_deps.h" | |
21 #include "tools/gn/setup.h" | 17 #include "tools/gn/setup.h" |
22 #include "tools/gn/standard_out.h" | 18 #include "tools/gn/standard_out.h" |
23 #include "tools/gn/substitution_writer.h" | |
24 #include "tools/gn/switches.h" | 19 #include "tools/gn/switches.h" |
25 #include "tools/gn/target.h" | 20 #include "tools/gn/target.h" |
26 #include "tools/gn/variables.h" | 21 #include "tools/gn/variables.h" |
27 | 22 |
28 namespace commands { | 23 namespace commands { |
29 | 24 |
30 namespace { | 25 namespace { |
31 | 26 |
32 // Desc-specific command line switches. | 27 // Desc-specific command line switches. |
33 const char kBlame[] = "blame"; | 28 const char kBlame[] = "blame"; |
34 const char kTree[] = "tree"; | 29 const char kTree[] = "tree"; |
35 | 30 const char kAll[] = "all"; |
36 // Prints the given directory in a nice way for the user to view. | 31 |
37 std::string FormatSourceDir(const SourceDir& dir) { | 32 // Prints value with specified indentation level |
38 #if defined(OS_WIN) | 33 void PrintValue(const base::Value* value, int indentLevel) { |
39 // On Windows we fix up system absolute paths to look like native ones. | 34 std::string indent(indentLevel * 2, ' '); |
40 // Internally, they'll look like "/C:\foo\bar/" | 35 const base::ListValue* list_value = nullptr; |
41 if (dir.is_system_absolute()) { | 36 const base::DictionaryValue* dict_value = nullptr; |
42 std::string buf = dir.value(); | 37 std::string string_value; |
43 if (buf.size() > 3 && buf[2] == ':') { | 38 bool bool_value = false; |
44 buf.erase(buf.begin()); // Erase beginning slash. | 39 if (value->GetAsList(&list_value)) { |
45 return buf; | 40 for (const auto& v : *list_value) { |
46 } | 41 PrintValue(v.get(), indentLevel); |
47 } | 42 } |
48 #endif | 43 } else if (value->GetAsString(&string_value)) { |
49 return dir.value(); | 44 OutputString(indent); |
50 } | 45 OutputString(string_value); |
51 | |
52 void RecursiveCollectChildDeps(const Target* target, | |
53 std::set<const Target*>* result); | |
54 | |
55 void RecursiveCollectDeps(const Target* target, | |
56 std::set<const Target*>* result) { | |
57 if (result->find(target) != result->end()) | |
58 return; // Already did this target. | |
59 result->insert(target); | |
60 | |
61 RecursiveCollectChildDeps(target, result); | |
62 } | |
63 | |
64 void RecursiveCollectChildDeps(const Target* target, | |
65 std::set<const Target*>* result) { | |
66 for (const auto& pair : target->GetDeps(Target::DEPS_ALL)) | |
67 RecursiveCollectDeps(pair.ptr, result); | |
68 } | |
69 | |
70 // Prints dependencies of the given target (not the target itself). If the | |
71 // set is non-null, new targets encountered will be added to the set, and if | |
72 // a dependency is in the set already, it will not be recused into. When the | |
73 // set is null, all dependencies will be printed. | |
74 void RecursivePrintDeps(const Target* target, | |
75 const Label& default_toolchain, | |
76 std::set<const Target*>* seen_targets, | |
77 int indent_level) { | |
78 // Combine all deps into one sorted list. | |
79 std::vector<LabelTargetPair> sorted_deps; | |
80 for (const auto& pair : target->GetDeps(Target::DEPS_ALL)) | |
81 sorted_deps.push_back(pair); | |
82 std::sort(sorted_deps.begin(), sorted_deps.end(), | |
83 LabelPtrLabelLess<Target>()); | |
84 | |
85 std::string indent(indent_level * 2, ' '); | |
86 for (const auto& pair : sorted_deps) { | |
87 const Target* cur_dep = pair.ptr; | |
88 | |
89 OutputString(indent + | |
90 cur_dep->label().GetUserVisibleName(default_toolchain)); | |
91 bool print_children = true; | |
92 if (seen_targets) { | |
93 if (seen_targets->find(cur_dep) == seen_targets->end()) { | |
94 // New target, mark it visited. | |
95 seen_targets->insert(cur_dep); | |
96 } else { | |
97 // Already seen. | |
98 print_children = false; | |
99 // Only print "..." if something is actually elided, which means that | |
100 // the current target has children. | |
101 if (!cur_dep->public_deps().empty() || | |
102 !cur_dep->private_deps().empty() || | |
103 !cur_dep->data_deps().empty()) | |
104 OutputString("..."); | |
105 } | |
106 } | |
107 | |
108 OutputString("\n"); | 46 OutputString("\n"); |
109 if (print_children) { | 47 } else if (value->GetAsBoolean(&bool_value)) { |
110 RecursivePrintDeps(cur_dep, default_toolchain, seen_targets, | 48 OutputString(indent); |
111 indent_level + 1); | 49 OutputString(bool_value ? "true" : "false"); |
112 } | 50 OutputString("\n"); |
113 } | 51 } else if (value->GetAsDictionary(&dict_value)) { |
114 } | 52 base::DictionaryValue::Iterator iter(*dict_value); |
115 | 53 while (!iter.IsAtEnd()) { |
116 void PrintDeps(const Target* target, bool display_header) { | 54 OutputString(indent + iter.key() + "\n"); |
117 const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess(); | 55 PrintValue(&iter.value(), indentLevel + 1); |
118 Label toolchain_label = target->label().GetToolchainLabel(); | 56 iter.Advance(); |
119 | 57 } |
120 // Tree mode is separate. | 58 } else if (value->IsType(base::Value::TYPE_NULL)) { |
121 if (cmdline->HasSwitch(kTree)) { | 59 OutputString(indent + "<null>\n"); |
122 if (display_header) | 60 } |
123 OutputString("\nDependency tree\n"); | 61 } |
124 | 62 |
125 if (cmdline->HasSwitch("all")) { | 63 // Default handler for property |
126 // Show all tree deps with no eliding. | 64 void DefaultHandler(const std::string& name, const base::Value* value) { |
127 RecursivePrintDeps(target, toolchain_label, nullptr, 1); | 65 OutputString("\n"); |
66 OutputString(name); | |
67 OutputString("\n"); | |
68 PrintValue(value, 1); | |
69 } | |
70 | |
71 // Specific handler for properties that needs different treatment | |
72 | |
73 void TypeHandler(const std::string& name, const base::Value* value) { | |
brettw
2016/07/06 22:19:26
I think you can delete this function if you take m
matt.k
2016/07/08 00:39:07
Done.
| |
74 std::string type; | |
75 if (value->GetAsString(&type)) { | |
76 auto t = DescBuilder::GetOutputTypeForString(type); | |
77 OutputString("Type: ", DECORATION_YELLOW); | |
78 OutputString(Target::GetStringForOutputType(t)); | |
79 OutputString("\n"); | |
80 } | |
81 } | |
82 | |
83 void ToolchainHandler(const std::string& name, const base::Value* value) { | |
84 std::string toolchain; | |
85 if (value->GetAsString(&toolchain)) { | |
86 OutputString("Toolchain: ", DECORATION_YELLOW); | |
87 OutputString(toolchain + "\n"); | |
88 } | |
89 } | |
90 | |
91 void VisibilityHandler(const std::string& name, const base::Value* value) { | |
92 const base::ListValue* list; | |
93 if (value->GetAsList(&list)) { | |
94 if (list->empty()) { | |
95 base::StringValue str("(no visibility)"); | |
96 DefaultHandler(name, &str); | |
128 } else { | 97 } else { |
129 // Don't recurse into duplicates. | 98 DefaultHandler(name, value); |
130 std::set<const Target*> seen_targets; | 99 } |
131 RecursivePrintDeps(target, toolchain_label, &seen_targets, 1); | 100 } |
132 } | 101 } |
133 return; | 102 |
134 } | 103 void PublicHandler(const std::string& name, const base::Value* value) { |
135 | 104 std::string p; |
136 // Collect the deps to display. | 105 if (value->GetAsString(&p)) { |
137 if (cmdline->HasSwitch("all")) { | 106 if (p == "*") { |
138 // Show all dependencies. | 107 base::StringValue str("[All headers listed in the sources are public.]"); |
139 if (display_header) | 108 DefaultHandler(name, &str); |
140 OutputString("\nAll recursive dependencies\n"); | 109 return; |
141 | 110 } |
142 std::set<const Target*> all_deps; | 111 } |
143 RecursiveCollectChildDeps(target, &all_deps); | 112 DefaultHandler(name, value); |
144 FilterAndPrintTargetSet(display_header, all_deps); | 113 } |
114 | |
115 void ConfigsHandler(const std::string& name, const base::Value* value) { | |
116 bool tree = base::CommandLine::ForCurrentProcess()->HasSwitch(kTree); | |
117 if (tree) | |
118 DefaultHandler(name + " tree (in order applying)", value); | |
119 else | |
120 DefaultHandler(name + " (in order applying, try also --tree)", value); | |
121 } | |
122 | |
123 void DepsHandler(const std::string& name, const base::Value* value) { | |
124 bool tree = base::CommandLine::ForCurrentProcess()->HasSwitch(kTree); | |
125 bool all = base::CommandLine::ForCurrentProcess()->HasSwitch(kTree); | |
126 if (tree) { | |
127 DefaultHandler("Dependency tree", value); | |
145 } else { | 128 } else { |
146 std::vector<const Target*> deps; | 129 if (!all) { |
147 // Show direct dependencies only. | 130 DefaultHandler( |
148 if (display_header) { | 131 "Direct dependencies " |
149 OutputString( | 132 "(try also \"--all\", \"--tree\", or even \"--all --tree\")", |
150 "\nDirect dependencies " | 133 value); |
151 "(try also \"--all\", \"--tree\", or even \"--all --tree\")\n"); | 134 } else { |
152 } | 135 DefaultHandler("All recursive dependencies", value); |
153 for (const auto& pair : target->GetDeps(Target::DEPS_ALL)) | 136 } |
154 deps.push_back(pair.ptr); | 137 } |
155 std::sort(deps.begin(), deps.end()); | 138 } |
156 FilterAndPrintTargets(display_header, &deps); | 139 |
157 } | 140 // Outputs need special processing when output patterns are present |
brettw
2016/07/06 22:19:26
Style: period at end of comment.
matt.k
2016/07/08 00:39:07
Done.
| |
158 } | 141 void ProcessOutputs(base::DictionaryValue* target) { |
159 | 142 base::ListValue* patterns = nullptr; |
160 // libs and lib_dirs are special in that they're inherited. We don't currently | 143 base::ListValue* outputs = nullptr; |
161 // implement a blame feature for this since the bottom-up inheritance makes | 144 target->GetList("output_patterns", &patterns); |
162 // this difficult. | 145 target->GetList(variables::kOutputs, &outputs); |
163 void PrintLibDirs(const Target* target, bool display_header) { | 146 |
164 const OrderedSet<SourceDir>& lib_dirs = target->all_lib_dirs(); | 147 if (outputs || patterns) { |
165 if (lib_dirs.empty()) | |
166 return; | |
167 | |
168 if (display_header) | |
169 OutputString("\nlib_dirs\n"); | |
170 | |
171 for (size_t i = 0; i < lib_dirs.size(); i++) | |
172 OutputString(" " + FormatSourceDir(lib_dirs[i]) + "\n"); | |
173 } | |
174 | |
175 void PrintLibs(const Target* target, bool display_header) { | |
176 const OrderedSet<LibFile>& libs = target->all_libs(); | |
177 if (libs.empty()) | |
178 return; | |
179 | |
180 if (display_header) | |
181 OutputString("\nlibs\n"); | |
182 | |
183 for (size_t i = 0; i < libs.size(); i++) | |
184 OutputString(" " + libs[i].value() + "\n"); | |
185 } | |
186 | |
187 void PrintPublic(const Target* target, bool display_header) { | |
188 if (display_header) | |
189 OutputString("\npublic\n"); | |
190 | |
191 if (target->all_headers_public()) { | |
192 OutputString(" [All headers listed in the sources are public.]\n"); | |
193 return; | |
194 } | |
195 | |
196 Target::FileList public_headers = target->public_headers(); | |
197 std::sort(public_headers.begin(), public_headers.end()); | |
198 for (const auto& hdr : public_headers) | |
199 OutputString(" " + hdr.value() + "\n"); | |
200 } | |
201 | |
202 void PrintCheckIncludes(const Target* target, bool display_header) { | |
203 if (display_header) | |
204 OutputString("\ncheck_includes\n"); | |
205 | |
206 if (target->check_includes()) | |
207 OutputString(" true\n"); | |
208 else | |
209 OutputString(" false\n"); | |
210 } | |
211 | |
212 void PrintAllowCircularIncludesFrom(const Target* target, bool display_header) { | |
213 if (display_header) | |
214 OutputString("\nallow_circular_includes_from\n"); | |
215 | |
216 Label toolchain_label = target->label().GetToolchainLabel(); | |
217 for (const auto& cur : target->allow_circular_includes_from()) | |
218 OutputString(" " + cur.GetUserVisibleName(toolchain_label) + "\n"); | |
219 } | |
220 | |
221 void PrintVisibility(const Target* target, bool display_header) { | |
222 if (display_header) | |
223 OutputString("\nvisibility\n"); | |
224 | |
225 OutputString(target->visibility().Describe(2, false)); | |
226 } | |
227 | |
228 void PrintTestonly(const Target* target, bool display_header) { | |
229 if (display_header) | |
230 OutputString("\ntestonly\n"); | |
231 | |
232 if (target->testonly()) | |
233 OutputString(" true\n"); | |
234 else | |
235 OutputString(" false\n"); | |
236 } | |
237 | |
238 // Recursively prints subconfigs of a config. | |
239 void PrintSubConfigs(const Config* config, int indent_level) { | |
240 if (config->configs().empty()) | |
241 return; | |
242 | |
243 std::string indent(indent_level * 2, ' '); | |
244 Label toolchain_label = config->label().GetToolchainLabel(); | |
245 for (const auto& pair : config->configs()) { | |
246 OutputString( | |
247 indent + pair.label.GetUserVisibleName(toolchain_label) + "\n"); | |
248 PrintSubConfigs(pair.ptr, indent_level + 1); | |
249 } | |
250 } | |
251 | |
252 // This allows configs stored as either std::vector<LabelConfigPair> or | |
253 // UniqueVector<LabelConfigPair> to be printed. | |
254 template <class VectorType> | |
255 void PrintConfigsVector(const Item* item, | |
256 const VectorType& configs, | |
257 const std::string& heading, | |
258 bool display_header) { | |
259 if (configs.empty()) | |
260 return; | |
261 | |
262 bool tree = base::CommandLine::ForCurrentProcess()->HasSwitch(kTree); | |
263 | |
264 // Don't sort since the order determines how things are processed. | |
265 if (display_header) { | |
266 if (tree) | |
267 OutputString("\n" + heading + " tree (in order applying)\n"); | |
268 else | |
269 OutputString("\n" + heading + " (in order applying, try also --tree)\n"); | |
270 } | |
271 | |
272 Label toolchain_label = item->label().GetToolchainLabel(); | |
273 for (const auto& config : configs) { | |
274 OutputString(" " + config.label.GetUserVisibleName(toolchain_label) + | |
275 "\n"); | |
276 if (tree) | |
277 PrintSubConfigs(config.ptr, 2); // 2 = start with double-indent. | |
278 } | |
279 } | |
280 | |
281 void PrintConfigs(const Target* target, bool display_header) { | |
282 PrintConfigsVector(target, target->configs().vector(), "configs", | |
283 display_header); | |
284 } | |
285 | |
286 void PrintConfigs(const Config* config, bool display_header) { | |
287 PrintConfigsVector(config, config->configs().vector(), "configs", | |
288 display_header); | |
289 } | |
290 | |
291 void PrintPublicConfigs(const Target* target, bool display_header) { | |
292 PrintConfigsVector(target, target->public_configs(), | |
293 "public_configs", display_header); | |
294 } | |
295 | |
296 void PrintAllDependentConfigs(const Target* target, bool display_header) { | |
297 PrintConfigsVector(target, target->all_dependent_configs(), | |
298 "all_dependent_configs", display_header); | |
299 } | |
300 | |
301 void PrintFileList(const Target::FileList& files, | |
302 const std::string& header, | |
303 bool indent_extra, | |
304 bool display_header) { | |
305 if (files.empty()) | |
306 return; | |
307 | |
308 if (display_header) | |
309 OutputString("\n" + header + "\n"); | |
310 | |
311 std::string indent = indent_extra ? " " : " "; | |
312 | |
313 Target::FileList sorted = files; | |
314 std::sort(sorted.begin(), sorted.end()); | |
315 for (const auto& elem : sorted) | |
316 OutputString(indent + elem.value() + "\n"); | |
317 } | |
318 | |
319 void PrintSources(const Target* target, bool display_header) { | |
320 PrintFileList(target->sources(), "sources", false, display_header); | |
321 } | |
322 | |
323 void PrintInputs(const Target* target, bool display_header) { | |
324 PrintFileList(target->inputs(), "inputs", false, display_header); | |
325 } | |
326 | |
327 void PrintOutputs(const Target* target, bool display_header) { | |
328 if (display_header) | |
329 OutputString("\noutputs\n"); | 148 OutputString("\noutputs\n"); |
330 | 149 int indent = 1; |
331 if (target->output_type() == Target::ACTION) { | 150 if (patterns) { |
332 // Action, print out outputs, don't apply sources to it. | 151 OutputString(" Output patterns\n"); |
333 for (const auto& elem : target->action_values().outputs().list()) { | 152 indent = 2; |
334 OutputString(" " + elem.AsString() + "\n"); | 153 PrintValue(patterns, indent); |
335 } | |
336 } else if (target->output_type() == Target::CREATE_BUNDLE) { | |
337 std::vector<SourceFile> output_files; | |
338 target->bundle_data().GetOutputsAsSourceFiles(target->settings(), | |
339 &output_files); | |
340 PrintFileList(output_files, std::string(), true, false); | |
341 } else if (target->output_type() == Target::ACTION_FOREACH) { | |
342 const SubstitutionList& outputs = target->action_values().outputs(); | |
343 if (!outputs.required_types().empty()) { | |
344 // Display the pattern and resolved pattern separately, since there are | |
345 // subtitutions used. | |
346 OutputString(" Output pattern\n"); | |
347 for (const auto& elem : outputs.list()) | |
348 OutputString(" " + elem.AsString() + "\n"); | |
349 | |
350 // Now display what that resolves to given the sources. | |
351 OutputString("\n Resolved output file list\n"); | 154 OutputString("\n Resolved output file list\n"); |
352 } | 155 } |
353 | 156 if (outputs) |
354 // Resolved output list. | 157 PrintValue(outputs, indent); |
355 std::vector<SourceFile> output_files; | 158 |
356 SubstitutionWriter::ApplyListToSources(target->settings(), outputs, | 159 target->Remove("output_patterns", nullptr); |
357 target->sources(), &output_files); | 160 target->Remove(variables::kOutputs, nullptr); |
358 PrintFileList(output_files, std::string(), true, false); | 161 } |
359 } else { | |
360 DCHECK(target->IsBinary()); | |
361 const Tool* tool = target->toolchain()->GetToolForTargetFinalOutput(target); | |
362 | |
363 std::vector<OutputFile> output_files; | |
364 SubstitutionWriter::ApplyListToLinkerAsOutputFile( | |
365 target, tool, tool->outputs(), &output_files); | |
366 | |
367 std::vector<SourceFile> output_files_as_source_file; | |
368 for (const OutputFile& output_file : output_files) { | |
369 output_files_as_source_file.push_back( | |
370 output_file.AsSourceFile(target->settings()->build_settings())); | |
371 } | |
372 | |
373 PrintFileList(output_files_as_source_file, std::string(), true, false); | |
374 } | |
375 } | |
376 | |
377 void PrintScript(const Target* target, bool display_header) { | |
378 if (display_header) | |
379 OutputString("\nscript\n"); | |
380 OutputString(" " + target->action_values().script().value() + "\n"); | |
381 } | |
382 | |
383 void PrintArgs(const Target* target, bool display_header) { | |
384 if (display_header) | |
385 OutputString("\nargs\n"); | |
386 for (const auto& elem : target->action_values().args().list()) { | |
387 OutputString(" " + elem.AsString() + "\n"); | |
388 } | |
389 } | |
390 | |
391 void PrintDepfile(const Target* target, bool display_header) { | |
392 if (target->action_values().depfile().empty()) | |
393 return; | |
394 if (display_header) | |
395 OutputString("\ndepfile\n"); | |
396 OutputString(" " + target->action_values().depfile().AsString() + "\n"); | |
397 } | |
398 | |
399 // Attribute the origin for attributing from where a target came from. Does | |
400 // nothing if the input is null or it does not have a location. | |
401 void OutputSourceOfDep(const ParseNode* origin, std::ostream& out) { | |
402 if (!origin) | |
403 return; | |
404 Location location = origin->GetRange().begin(); | |
405 out << " (Added by " + location.file()->name().value() << ":" | |
406 << location.line_number() << ")\n"; | |
407 } | |
408 | |
409 // Templatized writer for writing out different config value types. | |
410 template<typename T> struct DescValueWriter {}; | |
411 template<> struct DescValueWriter<std::string> { | |
412 void operator()(const std::string& str, std::ostream& out) const { | |
413 out << " " << str << "\n"; | |
414 } | |
415 }; | |
416 | |
417 template<> struct DescValueWriter<SourceDir> { | |
418 void operator()(const SourceDir& dir, std::ostream& out) const { | |
419 out << " " << FormatSourceDir(dir) << "\n"; | |
420 } | |
421 }; | |
422 | |
423 template<> struct DescValueWriter<LibFile> { | |
424 void operator()(const LibFile& lib, std::ostream& out) const { | |
425 if (lib.is_source_file()) | |
426 out << " " << lib.source_file().value() << "\n"; | |
427 else | |
428 out << " " << lib.value() << "\n"; | |
429 } | |
430 }; | |
431 | |
432 // Writes a given config value type to the string, optionally with attribution. | |
433 // This should match RecursiveTargetConfigToStream in the order it traverses. | |
434 template<typename T> void OutputRecursiveTargetConfig( | |
435 const Target* target, | |
436 const char* header_name, | |
437 const std::vector<T>& (ConfigValues::* getter)() const) { | |
438 bool display_blame = | |
439 base::CommandLine::ForCurrentProcess()->HasSwitch(kBlame); | |
440 | |
441 DescValueWriter<T> writer; | |
442 std::ostringstream out; | |
443 | |
444 for (ConfigValuesIterator iter(target); !iter.done(); iter.Next()) { | |
445 if ((iter.cur().*getter)().empty()) | |
446 continue; | |
447 | |
448 // Optional blame sub-head. | |
449 if (display_blame) { | |
450 const Config* config = iter.GetCurrentConfig(); | |
451 if (config) { | |
452 // Source of this value is a config. | |
453 out << " From " << config->label().GetUserVisibleName(false) << "\n"; | |
454 OutputSourceOfDep(iter.origin(), out); | |
455 } else { | |
456 // Source of this value is the target itself. | |
457 out << " From " << target->label().GetUserVisibleName(false) << "\n"; | |
458 } | |
459 } | |
460 | |
461 // Actual values. | |
462 ConfigValuesToStream(iter.cur(), getter, writer, out); | |
463 } | |
464 | |
465 std::string out_str = out.str(); | |
466 if (!out_str.empty()) { | |
467 if (header_name) | |
468 OutputString("\n" + std::string(header_name) + "\n"); | |
469 OutputString(out_str); | |
470 } | |
471 } | |
472 | |
473 template<typename T> void OutputConfigValueArray( | |
474 const ConfigValues& values, | |
475 const char* header_name, | |
476 const std::vector<T>& (ConfigValues::* getter)() const) { | |
477 std::ostringstream out; | |
478 | |
479 DescValueWriter<T> writer; | |
480 for (const T& cur : (values.*getter)()) | |
481 writer(cur, out); | |
482 | |
483 std::string out_str = out.str(); | |
484 if (!out_str.empty()) { | |
485 if (header_name) | |
486 OutputString("\n" + std::string(header_name) + "\n"); | |
487 OutputString(out_str); | |
488 } | |
489 } | |
490 | |
491 void PrintRuntimeDeps(const Target* target) { | |
492 bool display_blame = | |
493 base::CommandLine::ForCurrentProcess()->HasSwitch(kBlame); | |
494 Label toolchain = target->label().GetToolchainLabel(); | |
495 | |
496 const Target* previous_from = NULL; | |
497 for (const auto& pair : ComputeRuntimeDeps(target)) { | |
498 if (display_blame) { | |
499 // Generally a target's runtime deps will be listed sequentially, so | |
500 // group them and don't duplicate the "from" label for two in a row. | |
501 if (previous_from == pair.second) { | |
502 OutputString(" "); // Just indent. | |
503 } else { | |
504 previous_from = pair.second; | |
505 OutputString("From "); | |
506 OutputString(pair.second->label().GetUserVisibleName(toolchain)); | |
507 OutputString("\n "); // Make the file name indented. | |
508 } | |
509 } | |
510 OutputString(pair.first.value()); | |
511 OutputString("\n"); | |
512 } | |
513 } | |
514 | |
515 // If "what" is empty, prints all PCH info. If "what" is nonempty, prints only | |
516 // the things that match (if any). Returns true if anything was printed. | |
517 bool PrintPrecompiledHeaderInfo(const ConfigValues& values, | |
518 const std::string& what, | |
519 bool display_headers) { | |
520 bool found_match = false; | |
521 if (what == variables::kPrecompiledHeader || what.empty()) { | |
522 if (!values.precompiled_header().empty()) { | |
523 if (display_headers) | |
524 OutputString("\nprecompiled_header\n"); | |
525 OutputString(values.precompiled_header() + "\n"); | |
526 } | |
527 found_match = true; | |
528 } | |
529 if (what == variables::kPrecompiledSource || what.empty()) { | |
530 if (!values.precompiled_source().is_null()) { | |
531 if (display_headers) | |
532 OutputString("\nprecompiled_source\n"); | |
533 OutputString(values.precompiled_source().value() + "\n"); | |
534 } | |
535 found_match = true; | |
536 } | |
537 return found_match; | |
538 } | 162 } |
539 | 163 |
540 bool PrintTarget(const Target* target, | 164 bool PrintTarget(const Target* target, |
541 const std::string& what, | 165 const std::string& what, |
542 bool display_target_header) { | 166 bool single_target, |
543 if (display_target_header) { | 167 bool all, |
544 OutputString("Target: ", DECORATION_YELLOW); | 168 bool tree, |
545 OutputString(target->label().GetUserVisibleName(false) + "\n"); | 169 bool blame) { |
546 OutputString("Type: ", DECORATION_YELLOW); | 170 std::unique_ptr<base::DictionaryValue> dict = |
547 OutputString(std::string( | 171 DescBuilder::DescriptionForTarget(target, what, all, tree, blame); |
548 Target::GetStringForOutputType(target->output_type())) + "\n"); | 172 if (!what.empty() && dict->empty()) { |
549 OutputString("Toolchain: ", DECORATION_YELLOW); | |
550 OutputString( | |
551 target->label().GetToolchainLabel().GetUserVisibleName(false) + "\n"); | |
552 } | |
553 | |
554 // Display headers when outputting everything. | |
555 bool display_headers = what.empty(); | |
556 bool is_binary_output = target->IsBinary(); | |
557 | |
558 bool found_match = false; | |
559 | |
560 // General target meta variables. | |
561 if (what.empty() || what == variables::kVisibility) { | |
562 PrintVisibility(target, display_headers); | |
563 found_match = true; | |
564 } | |
565 if (what.empty() || what == variables::kTestonly) { | |
566 PrintTestonly(target, display_headers); | |
567 found_match = true; | |
568 } | |
569 | |
570 // Binary target meta variables. | |
571 if (is_binary_output) { | |
572 if (what.empty() || what == variables::kCheckIncludes) { | |
573 PrintCheckIncludes(target, display_headers); | |
574 found_match = true; | |
575 } | |
576 if (what.empty() || what == variables::kAllowCircularIncludesFrom) { | |
577 PrintAllowCircularIncludesFrom(target, display_headers); | |
578 found_match = true; | |
579 } | |
580 } | |
581 | |
582 // Sources and inputs. | |
583 if (what.empty() || what == variables::kSources) { | |
584 PrintSources(target, display_headers); | |
585 found_match = true; | |
586 } | |
587 if (what.empty() || what == variables::kPublic) { | |
588 PrintPublic(target, display_headers); | |
589 found_match = true; | |
590 } | |
591 if (what.empty() || what == variables::kInputs) { | |
592 PrintInputs(target, display_headers); | |
593 found_match = true; | |
594 } | |
595 | |
596 // Configs. Configs set directly on a target are only relevant for binary | |
597 // targets | |
598 if (is_binary_output && (what.empty() || what == variables::kConfigs)) { | |
599 PrintConfigs(target, display_headers); | |
600 found_match = true; | |
601 } | |
602 | |
603 // Dependent/public configs can be applied to anything. | |
604 if (what.empty() || what == variables::kPublicConfigs) { | |
605 PrintPublicConfigs(target, display_headers); | |
606 found_match = true; | |
607 } | |
608 if (what.empty() || what == variables::kAllDependentConfigs) { | |
609 PrintAllDependentConfigs(target, display_headers); | |
610 found_match = true; | |
611 } | |
612 | |
613 // Action values. | |
614 if (target->output_type() == Target::ACTION || | |
615 target->output_type() == Target::ACTION_FOREACH) { | |
616 if (what.empty() || what == variables::kScript) { | |
617 PrintScript(target, display_headers); | |
618 found_match = true; | |
619 } | |
620 if (what.empty() || what == variables::kArgs) { | |
621 PrintArgs(target, display_headers); | |
622 found_match = true; | |
623 } | |
624 if (what.empty() || what == variables::kDepfile) { | |
625 PrintDepfile(target, display_headers); | |
626 found_match = true; | |
627 } | |
628 } | |
629 | |
630 // Outputs. | |
631 if (target->output_type() != Target::SOURCE_SET && | |
632 target->output_type() != Target::GROUP) { | |
633 if (what.empty() || what == variables::kOutputs) { | |
634 PrintOutputs(target, display_headers); | |
635 found_match = true; | |
636 } | |
637 } | |
638 | |
639 // Values from configs only apply to binary targets. | |
640 if (is_binary_output) { | |
641 #define CONFIG_VALUE_ARRAY_HANDLER(name, type) \ | |
642 if (what.empty() || what == #name) { \ | |
643 OutputRecursiveTargetConfig<type>( \ | |
644 target, display_headers ? #name : nullptr, &ConfigValues::name); \ | |
645 found_match = true; \ | |
646 } | |
647 | |
648 CONFIG_VALUE_ARRAY_HANDLER(arflags, std::string) | |
649 CONFIG_VALUE_ARRAY_HANDLER(asmflags, std::string) | |
650 CONFIG_VALUE_ARRAY_HANDLER(cflags, std::string) | |
651 CONFIG_VALUE_ARRAY_HANDLER(cflags_c, std::string) | |
652 CONFIG_VALUE_ARRAY_HANDLER(cflags_cc, std::string) | |
653 CONFIG_VALUE_ARRAY_HANDLER(cflags_objc, std::string) | |
654 CONFIG_VALUE_ARRAY_HANDLER(cflags_objcc, std::string) | |
655 CONFIG_VALUE_ARRAY_HANDLER(defines, std::string) | |
656 CONFIG_VALUE_ARRAY_HANDLER(include_dirs, SourceDir) | |
657 CONFIG_VALUE_ARRAY_HANDLER(ldflags, std::string) | |
658 // Libs and lib_dirs are handled specially below. | |
659 | |
660 #undef CONFIG_VALUE_ARRAY_HANDLER | |
661 | |
662 found_match |= PrintPrecompiledHeaderInfo(target->config_values(), | |
663 what, display_headers); | |
664 } | |
665 | |
666 // Deps | |
667 if (what.empty() || what == "deps") { | |
668 PrintDeps(target, display_headers); | |
669 found_match = true; | |
670 } | |
671 | |
672 // Runtime deps are special, print only when explicitly asked for and not in | |
673 // overview mode. | |
674 if (what == "runtime_deps") { | |
675 PrintRuntimeDeps(target); | |
676 found_match = true; | |
677 } | |
678 | |
679 // Libs can be part of any target and get recursively pushed up the chain, | |
680 // so display them regardless of target type. | |
681 if (what.empty() || what == variables::kLibs) { | |
682 PrintLibs(target, display_headers); | |
683 found_match = true; | |
684 } | |
685 if (what.empty() || what == variables::kLibDirs) { | |
686 PrintLibDirs(target, display_headers); | |
687 found_match = true; | |
688 } | |
689 | |
690 if (!found_match) { | |
691 OutputString("Don't know how to display \"" + what + "\" for \"" + | 173 OutputString("Don't know how to display \"" + what + "\" for \"" + |
692 Target::GetStringForOutputType(target->output_type()) + "\".\n"); | 174 Target::GetStringForOutputType(target->output_type()) + |
175 "\".\n"); | |
693 return false; | 176 return false; |
694 } | 177 } |
178 // Print single value, without any headers | |
179 if (!what.empty() && dict->size() == 1 && single_target) { | |
180 base::DictionaryValue::Iterator iter(*dict); | |
181 PrintValue(&iter.value(), 0); | |
182 return true; | |
183 } | |
184 | |
185 OutputString("Target ", DECORATION_YELLOW); | |
186 OutputString(target->label().GetUserVisibleName(false)); | |
187 OutputString("\n"); | |
188 | |
189 std::unique_ptr<base::Value> v; | |
190 #define HANDLER(property, handler_name) \ | |
191 if (dict->Remove(property, &v)) { \ | |
192 handler_name(property, v.get()); \ | |
193 } | |
194 | |
195 // Entries with DefaultHandler are present to enforce order | |
196 HANDLER("type", TypeHandler); | |
197 HANDLER("toolchain", ToolchainHandler); | |
198 HANDLER(variables::kVisibility, VisibilityHandler); | |
199 HANDLER(variables::kTestonly, DefaultHandler); | |
200 HANDLER(variables::kCheckIncludes, DefaultHandler); | |
201 HANDLER(variables::kAllowCircularIncludesFrom, DefaultHandler); | |
202 HANDLER(variables::kSources, DefaultHandler); | |
203 HANDLER(variables::kPublic, PublicHandler); | |
204 HANDLER(variables::kInputs, DefaultHandler); | |
205 HANDLER(variables::kConfigs, ConfigsHandler); | |
206 HANDLER(variables::kPublicConfigs, ConfigsHandler); | |
207 HANDLER(variables::kAllDependentConfigs, ConfigsHandler); | |
208 HANDLER(variables::kScript, DefaultHandler); | |
209 HANDLER(variables::kArgs, DefaultHandler); | |
210 HANDLER(variables::kDepfile, DefaultHandler); | |
211 ProcessOutputs(dict.get()); | |
212 HANDLER("bundle_data", DefaultHandler); | |
213 HANDLER(variables::kArflags, DefaultHandler); | |
214 HANDLER(variables::kAsmflags, DefaultHandler); | |
215 HANDLER(variables::kCflags, DefaultHandler); | |
216 HANDLER(variables::kCflagsC, DefaultHandler); | |
217 HANDLER(variables::kCflagsCC, DefaultHandler); | |
218 HANDLER(variables::kCflagsObjC, DefaultHandler); | |
219 HANDLER(variables::kCflagsObjCC, DefaultHandler); | |
220 HANDLER(variables::kDefines, DefaultHandler); | |
221 HANDLER(variables::kIncludeDirs, DefaultHandler); | |
222 HANDLER(variables::kLdflags, DefaultHandler); | |
223 HANDLER(variables::kPrecompiledHeader, DefaultHandler); | |
224 HANDLER(variables::kPrecompiledSource, DefaultHandler); | |
225 HANDLER(variables::kDeps, DepsHandler); | |
226 HANDLER(variables::kLibs, DefaultHandler); | |
227 HANDLER(variables::kLibDirs, DefaultHandler); | |
228 | |
229 #undef HANDLER | |
230 | |
231 // Process the rest (if any) | |
232 base::DictionaryValue::Iterator iter(*dict); | |
233 while (!iter.IsAtEnd()) { | |
234 DefaultHandler(iter.key(), &iter.value()); | |
235 iter.Advance(); | |
236 } | |
237 | |
695 return true; | 238 return true; |
696 } | 239 } |
697 | 240 |
698 bool PrintConfig(const Config* config, | 241 bool PrintConfig(const Config* config, |
699 const std::string& what, | 242 const std::string& what, |
700 bool display_config_header) { | 243 bool single_config) { |
701 const ConfigValues& values = config->resolved_values(); | 244 std::unique_ptr<base::DictionaryValue> dict = |
702 | 245 DescBuilder::DescriptionForConfig(config, what); |
703 if (display_config_header) { | 246 if (!what.empty() && dict->empty()) { |
704 OutputString("Config: ", DECORATION_YELLOW); | |
705 OutputString(config->label().GetUserVisibleName(false) + "\n"); | |
706 OutputString("Toolchain: ", DECORATION_YELLOW); | |
707 OutputString( | |
708 config->label().GetToolchainLabel().GetUserVisibleName(false) + "\n"); | |
709 if (what.empty() && !config->configs().empty()) { | |
710 OutputString( | |
711 "(This is a composite config, the values below are after the\n" | |
712 "expansion of the child configs.)\n"); | |
713 } | |
714 } | |
715 | |
716 // Display headers when outputting everything. | |
717 bool display_headers = what.empty(); | |
718 | |
719 if (what.empty() || what == variables::kConfigs) | |
720 PrintConfigs(config, display_headers); | |
721 | |
722 #define CONFIG_VALUE_ARRAY_HANDLER(name, type) \ | |
723 if (what.empty() || what == #name) { \ | |
724 OutputConfigValueArray<type>(values, display_headers ? #name : nullptr, \ | |
725 &ConfigValues::name); \ | |
726 found_match = true; \ | |
727 } | |
728 | |
729 bool found_match = false; | |
730 | |
731 CONFIG_VALUE_ARRAY_HANDLER(arflags, std::string) | |
732 CONFIG_VALUE_ARRAY_HANDLER(asmflags, std::string) | |
733 CONFIG_VALUE_ARRAY_HANDLER(cflags, std::string) | |
734 CONFIG_VALUE_ARRAY_HANDLER(cflags_c, std::string) | |
735 CONFIG_VALUE_ARRAY_HANDLER(cflags_cc, std::string) | |
736 CONFIG_VALUE_ARRAY_HANDLER(cflags_objc, std::string) | |
737 CONFIG_VALUE_ARRAY_HANDLER(cflags_objcc, std::string) | |
738 CONFIG_VALUE_ARRAY_HANDLER(defines, std::string) | |
739 CONFIG_VALUE_ARRAY_HANDLER(include_dirs, SourceDir) | |
740 CONFIG_VALUE_ARRAY_HANDLER(ldflags, std::string) | |
741 CONFIG_VALUE_ARRAY_HANDLER(lib_dirs, SourceDir) | |
742 CONFIG_VALUE_ARRAY_HANDLER(libs, LibFile) | |
743 | |
744 #undef CONFIG_VALUE_ARRAY_HANDLER | |
745 | |
746 // Handles all PCH-related variables. | |
747 found_match |= PrintPrecompiledHeaderInfo(config->resolved_values(), | |
748 what, display_headers); | |
749 | |
750 if (!found_match) { | |
751 OutputString("Don't know how to display \"" + what + "\" for a config.\n"); | 247 OutputString("Don't know how to display \"" + what + "\" for a config.\n"); |
752 return false; | 248 return false; |
753 } | 249 } |
250 // Print single value, without any headers | |
251 if (!what.empty() && dict->size() == 1 && single_config) { | |
252 base::DictionaryValue::Iterator iter(*dict); | |
253 PrintValue(&iter.value(), 0); | |
254 return true; | |
255 } | |
256 | |
257 OutputString("Config: ", DECORATION_YELLOW); | |
258 OutputString(config->label().GetUserVisibleName(false)); | |
259 OutputString("\n"); | |
260 | |
261 std::unique_ptr<base::Value> v; | |
262 #define HANDLER(property, handler_name) \ | |
263 if (dict->Remove(property, &v)) { \ | |
264 handler_name(property, v.get()); \ | |
265 } | |
266 | |
267 HANDLER("toolchain", ToolchainHandler); | |
268 if (!config->configs().empty()) { | |
269 OutputString( | |
270 "(This is a composite config, the values below are after the\n" | |
271 "expansion of the child configs.)\n"); | |
272 } | |
273 HANDLER(variables::kArflags, DefaultHandler); | |
274 HANDLER(variables::kAsmflags, DefaultHandler); | |
275 HANDLER(variables::kCflags, DefaultHandler); | |
276 HANDLER(variables::kCflagsC, DefaultHandler); | |
277 HANDLER(variables::kCflagsCC, DefaultHandler); | |
278 HANDLER(variables::kCflagsObjC, DefaultHandler); | |
279 HANDLER(variables::kCflagsObjCC, DefaultHandler); | |
280 HANDLER(variables::kDefines, DefaultHandler); | |
281 HANDLER(variables::kIncludeDirs, DefaultHandler); | |
282 HANDLER(variables::kLdflags, DefaultHandler); | |
283 HANDLER(variables::kLibs, DefaultHandler); | |
284 HANDLER(variables::kLibDirs, DefaultHandler); | |
285 HANDLER(variables::kPrecompiledHeader, DefaultHandler); | |
286 HANDLER(variables::kPrecompiledSource, DefaultHandler); | |
287 | |
288 #undef HANDLER | |
289 | |
754 return true; | 290 return true; |
755 } | 291 } |
756 | 292 |
757 } // namespace | 293 } // namespace |
758 | 294 |
759 // desc ------------------------------------------------------------------------ | 295 // desc ------------------------------------------------------------------------ |
760 | 296 |
761 const char kDesc[] = "desc"; | 297 const char kDesc[] = "desc"; |
762 const char kDesc_HelpShort[] = | 298 const char kDesc_HelpShort[] = |
763 "desc: Show lots of insightful information about a target or config."; | 299 "desc: Show lots of insightful information about a target or config."; |
764 const char kDesc_Help[] = | 300 const char kDesc_Help[] = |
765 "gn desc <out_dir> <label or pattern> [<what to show>] [--blame]\n" | 301 "gn desc <out_dir> <label or pattern> [<what to show>] [--blame] " |
302 "[--format=json]\n" | |
766 "\n" | 303 "\n" |
767 " Displays information about a given target or config. The build\n" | 304 " Displays information about a given target or config. The build\n" |
768 " build parameters will be taken for the build in the given <out_dir>.\n" | 305 " build parameters will be taken for the build in the given <out_dir>.\n" |
769 "\n" | 306 "\n" |
770 " The <label or pattern> can be a target label, a config label, or a\n" | 307 " The <label or pattern> can be a target label, a config label, or a\n" |
771 " label pattern (see \"gn help label_pattern\"). A label pattern will\n" | 308 " label pattern (see \"gn help label_pattern\"). A label pattern will\n" |
772 " only match targets.\n" | 309 " only match targets.\n" |
773 "\n" | 310 "\n" |
774 "Possibilities for <what to show>\n" | 311 "Possibilities for <what to show>\n" |
775 "\n" | 312 "\n" |
(...skipping 83 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
859 " eliding will be performed.\n" | 396 " eliding will be performed.\n" |
860 "\n" | 397 "\n" |
861 " The \"deps\", \"public_deps\", and \"data_deps\" will all be\n" | 398 " The \"deps\", \"public_deps\", and \"data_deps\" will all be\n" |
862 " included in the tree.\n" | 399 " included in the tree.\n" |
863 "\n" | 400 "\n" |
864 " Tree output can not be used with the filtering or output flags:\n" | 401 " Tree output can not be used with the filtering or output flags:\n" |
865 " --as, --type, --testonly.\n" | 402 " --as, --type, --testonly.\n" |
866 "\n" | 403 "\n" |
867 TARGET_TYPE_FILTER_COMMAND_LINE_HELP | 404 TARGET_TYPE_FILTER_COMMAND_LINE_HELP |
868 "\n" | 405 "\n" |
406 "Format\n" | |
407 "\n" | |
408 " Optional --format=json flag can be specified to switch output to JSON.\n" | |
brettw
2016/07/06 22:19:26
Can you just move this up to the "Shared flags" se
matt.k
2016/07/08 00:39:07
Done.
| |
409 "\n" | |
869 "Note\n" | 410 "Note\n" |
870 "\n" | 411 "\n" |
871 " This command will show the full name of directories and source files,\n" | 412 " This command will show the full name of directories and source files,\n" |
872 " but when directories and source paths are written to the build file,\n" | 413 " but when directories and source paths are written to the build file,\n" |
873 " they will be adjusted to be relative to the build directory. So the\n" | 414 " they will be adjusted to be relative to the build directory. So the\n" |
874 " values for paths displayed by this command won't match (but should\n" | 415 " values for paths displayed by this command won't match (but should\n" |
875 " mean the same thing).\n" | 416 " mean the same thing).\n" |
876 "\n" | 417 "\n" |
877 "Examples\n" | 418 "Examples\n" |
878 "\n" | 419 "\n" |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
915 | 456 |
916 if (!ResolveFromCommandLineInput( | 457 if (!ResolveFromCommandLineInput( |
917 setup, target_list, cmdline->HasSwitch(switches::kAllToolchains), | 458 setup, target_list, cmdline->HasSwitch(switches::kAllToolchains), |
918 &target_matches, &config_matches, &toolchain_matches, &file_matches)) | 459 &target_matches, &config_matches, &toolchain_matches, &file_matches)) |
919 return 1; | 460 return 1; |
920 | 461 |
921 std::string what_to_print; | 462 std::string what_to_print; |
922 if (args.size() == 3) | 463 if (args.size() == 3) |
923 what_to_print = args[2]; | 464 what_to_print = args[2]; |
924 | 465 |
925 bool multiple_outputs = (target_matches.size() + config_matches.size()) > 1; | 466 bool json = cmdline->GetSwitchValueASCII("format") == "json"; |
926 | 467 |
927 // Display headers for each target when printing all values, or when printing | 468 if (json) { |
928 // multiple targets or configs. | 469 // Convert all targets/configs to JSON, serialize and print them |
929 bool display_item_header = multiple_outputs || what_to_print.empty(); | 470 auto res = base::WrapUnique(new base::DictionaryValue()); |
471 if (!target_matches.empty()) { | |
472 for (const auto* target : target_matches) { | |
473 res->Set(target->label().GetUserVisibleName( | |
474 target->settings()->default_toolchain_label()), | |
475 DescBuilder::DescriptionForTarget( | |
476 target, what_to_print, cmdline->HasSwitch(kAll), | |
477 cmdline->HasSwitch(kTree), cmdline->HasSwitch(kBlame))); | |
478 } | |
479 } else if (!config_matches.empty()) { | |
480 for (const auto* config : config_matches) { | |
481 res->Set(config->label().GetUserVisibleName(false), | |
482 DescBuilder::DescriptionForConfig(config, what_to_print)); | |
483 } | |
484 } | |
485 std::string s; | |
486 base::JSONWriter::WriteWithOptions( | |
487 *res.get(), base::JSONWriter::OPTIONS_PRETTY_PRINT, &s); | |
488 OutputString(s); | |
489 } else { | |
490 // Regular (non-json) formatted output | |
491 bool multiple_outputs = (target_matches.size() + config_matches.size()) > 1; | |
930 | 492 |
931 bool printed_output = false; | 493 bool printed_output = false; |
932 for (const Target* target : target_matches) { | 494 for (const Target* target : target_matches) { |
933 if (printed_output) | 495 if (printed_output) |
934 OutputString("\n\n"); | 496 OutputString("\n\n"); |
935 printed_output = true; | 497 printed_output = true; |
936 | 498 |
937 if (!PrintTarget(target, what_to_print, display_item_header)) | 499 if (!PrintTarget(target, what_to_print, !multiple_outputs, |
938 return 1; | 500 cmdline->HasSwitch(kAll), cmdline->HasSwitch(kTree), |
939 } | 501 cmdline->HasSwitch(kBlame))) |
940 for (const Config* config : config_matches) { | 502 return 1; |
941 if (printed_output) | 503 } |
942 OutputString("\n\n"); | 504 for (const Config* config : config_matches) { |
943 printed_output = true; | 505 if (printed_output) |
506 OutputString("\n\n"); | |
507 printed_output = true; | |
944 | 508 |
945 if (!PrintConfig(config, what_to_print, display_item_header)) | 509 if (!PrintConfig(config, what_to_print, !multiple_outputs)) |
946 return 1; | 510 return 1; |
511 } | |
947 } | 512 } |
948 | 513 |
949 return 0; | 514 return 0; |
950 } | 515 } |
951 | 516 |
952 } // namespace commands | 517 } // namespace commands |
OLD | NEW |