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" |
| 14 #include "base/strings/string_util.h" |
13 #include "tools/gn/commands.h" | 15 #include "tools/gn/commands.h" |
14 #include "tools/gn/config.h" | 16 #include "tools/gn/config.h" |
15 #include "tools/gn/config_values_extractors.h" | 17 #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" | 18 #include "tools/gn/setup.h" |
22 #include "tools/gn/standard_out.h" | 19 #include "tools/gn/standard_out.h" |
23 #include "tools/gn/substitution_writer.h" | |
24 #include "tools/gn/switches.h" | 20 #include "tools/gn/switches.h" |
25 #include "tools/gn/target.h" | 21 #include "tools/gn/target.h" |
26 #include "tools/gn/variables.h" | 22 #include "tools/gn/variables.h" |
27 | 23 |
28 namespace commands { | 24 namespace commands { |
29 | 25 |
30 namespace { | 26 namespace { |
31 | 27 |
32 // Desc-specific command line switches. | 28 // Desc-specific command line switches. |
33 const char kBlame[] = "blame"; | 29 const char kBlame[] = "blame"; |
34 const char kTree[] = "tree"; | 30 const char kTree[] = "tree"; |
35 | 31 const char kAll[] = "all"; |
36 // Prints the given directory in a nice way for the user to view. | 32 |
37 std::string FormatSourceDir(const SourceDir& dir) { | 33 // Prints value with specified indentation level |
38 #if defined(OS_WIN) | 34 void PrintValue(const base::Value* value, int indentLevel) { |
39 // On Windows we fix up system absolute paths to look like native ones. | 35 std::string indent(indentLevel * 2, ' '); |
40 // Internally, they'll look like "/C:\foo\bar/" | 36 const base::ListValue* list_value = nullptr; |
41 if (dir.is_system_absolute()) { | 37 const base::DictionaryValue* dict_value = nullptr; |
42 std::string buf = dir.value(); | 38 std::string string_value; |
43 if (buf.size() > 3 && buf[2] == ':') { | 39 bool bool_value = false; |
44 buf.erase(buf.begin()); // Erase beginning slash. | 40 if (value->GetAsList(&list_value)) { |
45 return buf; | 41 for (const auto& v : *list_value) { |
46 } | 42 PrintValue(v.get(), indentLevel); |
47 } | 43 } |
48 #endif | 44 } else if (value->GetAsString(&string_value)) { |
49 return dir.value(); | 45 OutputString(indent); |
50 } | 46 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"); | 47 OutputString("\n"); |
109 if (print_children) { | 48 } else if (value->GetAsBoolean(&bool_value)) { |
110 RecursivePrintDeps(cur_dep, default_toolchain, seen_targets, | 49 OutputString(indent); |
111 indent_level + 1); | 50 OutputString(bool_value ? "true" : "false"); |
112 } | 51 OutputString("\n"); |
113 } | 52 } else if (value->GetAsDictionary(&dict_value)) { |
114 } | 53 base::DictionaryValue::Iterator iter(*dict_value); |
115 | 54 while (!iter.IsAtEnd()) { |
116 void PrintDeps(const Target* target, bool display_header) { | 55 OutputString(indent + iter.key() + "\n"); |
117 const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess(); | 56 PrintValue(&iter.value(), indentLevel + 1); |
118 Label toolchain_label = target->label().GetToolchainLabel(); | 57 iter.Advance(); |
119 | 58 } |
120 // Tree mode is separate. | 59 } else if (value->IsType(base::Value::TYPE_NULL)) { |
121 if (cmdline->HasSwitch(kTree)) { | 60 OutputString(indent + "<null>\n"); |
122 if (display_header) | 61 } |
123 OutputString("\nDependency tree\n"); | 62 } |
124 | 63 |
125 if (cmdline->HasSwitch("all")) { | 64 // Default handler for property |
126 // Show all tree deps with no eliding. | 65 void DefaultHandler(const std::string& name, const base::Value* value) { |
127 RecursivePrintDeps(target, toolchain_label, nullptr, 1); | 66 OutputString("\n"); |
| 67 OutputString(name); |
| 68 OutputString("\n"); |
| 69 PrintValue(value, 1); |
| 70 } |
| 71 |
| 72 // Specific handler for properties that need different treatment |
| 73 |
| 74 // Prints label and property value on one line, capitalizing the label. |
| 75 void LabelHandler(std::string name, const base::Value* value) { |
| 76 name[0] = base::ToUpperASCII(name[0]); |
| 77 std::string string_value; |
| 78 if (value->GetAsString(&string_value)) { |
| 79 OutputString(name + ": ", DECORATION_YELLOW); |
| 80 OutputString(string_value + "\n"); |
| 81 } |
| 82 } |
| 83 |
| 84 void VisibilityHandler(const std::string& name, const base::Value* value) { |
| 85 const base::ListValue* list; |
| 86 if (value->GetAsList(&list)) { |
| 87 if (list->empty()) { |
| 88 base::StringValue str("(no visibility)"); |
| 89 DefaultHandler(name, &str); |
128 } else { | 90 } else { |
129 // Don't recurse into duplicates. | 91 DefaultHandler(name, value); |
130 std::set<const Target*> seen_targets; | 92 } |
131 RecursivePrintDeps(target, toolchain_label, &seen_targets, 1); | 93 } |
132 } | 94 } |
133 return; | 95 |
134 } | 96 void PublicHandler(const std::string& name, const base::Value* value) { |
135 | 97 std::string p; |
136 // Collect the deps to display. | 98 if (value->GetAsString(&p)) { |
137 if (cmdline->HasSwitch("all")) { | 99 if (p == "*") { |
138 // Show all dependencies. | 100 base::StringValue str("[All headers listed in the sources are public.]"); |
139 if (display_header) | 101 DefaultHandler(name, &str); |
140 OutputString("\nAll recursive dependencies\n"); | 102 return; |
141 | 103 } |
142 std::set<const Target*> all_deps; | 104 } |
143 RecursiveCollectChildDeps(target, &all_deps); | 105 DefaultHandler(name, value); |
144 FilterAndPrintTargetSet(display_header, all_deps); | 106 } |
| 107 |
| 108 void ConfigsHandler(const std::string& name, const base::Value* value) { |
| 109 bool tree = base::CommandLine::ForCurrentProcess()->HasSwitch(kTree); |
| 110 if (tree) |
| 111 DefaultHandler(name + " tree (in order applying)", value); |
| 112 else |
| 113 DefaultHandler(name + " (in order applying, try also --tree)", value); |
| 114 } |
| 115 |
| 116 void DepsHandler(const std::string& name, const base::Value* value) { |
| 117 bool tree = base::CommandLine::ForCurrentProcess()->HasSwitch(kTree); |
| 118 bool all = base::CommandLine::ForCurrentProcess()->HasSwitch(kTree); |
| 119 if (tree) { |
| 120 DefaultHandler("Dependency tree", value); |
145 } else { | 121 } else { |
146 std::vector<const Target*> deps; | 122 if (!all) { |
147 // Show direct dependencies only. | 123 DefaultHandler( |
148 if (display_header) { | 124 "Direct dependencies " |
149 OutputString( | 125 "(try also \"--all\", \"--tree\", or even \"--all --tree\")", |
150 "\nDirect dependencies " | 126 value); |
151 "(try also \"--all\", \"--tree\", or even \"--all --tree\")\n"); | 127 } else { |
152 } | 128 DefaultHandler("All recursive dependencies", value); |
153 for (const auto& pair : target->GetDeps(Target::DEPS_ALL)) | 129 } |
154 deps.push_back(pair.ptr); | 130 } |
155 std::sort(deps.begin(), deps.end()); | 131 } |
156 FilterAndPrintTargets(display_header, &deps); | 132 |
157 } | 133 // Outputs need special processing when output patterns are present. |
158 } | 134 void ProcessOutputs(base::DictionaryValue* target) { |
159 | 135 base::ListValue* patterns = nullptr; |
160 // libs and lib_dirs are special in that they're inherited. We don't currently | 136 base::ListValue* outputs = nullptr; |
161 // implement a blame feature for this since the bottom-up inheritance makes | 137 target->GetList("output_patterns", &patterns); |
162 // this difficult. | 138 target->GetList(variables::kOutputs, &outputs); |
163 void PrintLibDirs(const Target* target, bool display_header) { | 139 |
164 const OrderedSet<SourceDir>& lib_dirs = target->all_lib_dirs(); | 140 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"); | 141 OutputString("\noutputs\n"); |
330 | 142 int indent = 1; |
331 if (target->output_type() == Target::ACTION) { | 143 if (patterns) { |
332 // Action, print out outputs, don't apply sources to it. | 144 OutputString(" Output patterns\n"); |
333 for (const auto& elem : target->action_values().outputs().list()) { | 145 indent = 2; |
334 OutputString(" " + elem.AsString() + "\n"); | 146 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"); | 147 OutputString("\n Resolved output file list\n"); |
352 } | 148 } |
353 | 149 if (outputs) |
354 // Resolved output list. | 150 PrintValue(outputs, indent); |
355 std::vector<SourceFile> output_files; | 151 |
356 SubstitutionWriter::ApplyListToSources(target->settings(), outputs, | 152 target->Remove("output_patterns", nullptr); |
357 target->sources(), &output_files); | 153 target->Remove(variables::kOutputs, nullptr); |
358 PrintFileList(output_files, std::string(), true, false); | 154 } |
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 } | 155 } |
539 | 156 |
540 bool PrintTarget(const Target* target, | 157 bool PrintTarget(const Target* target, |
541 const std::string& what, | 158 const std::string& what, |
542 bool display_target_header) { | 159 bool single_target, |
543 if (display_target_header) { | 160 bool all, |
544 OutputString("Target: ", DECORATION_YELLOW); | 161 bool tree, |
545 OutputString(target->label().GetUserVisibleName(false) + "\n"); | 162 bool blame) { |
546 OutputString("Type: ", DECORATION_YELLOW); | 163 std::unique_ptr<base::DictionaryValue> dict = |
547 OutputString(std::string( | 164 DescBuilder::DescriptionForTarget(target, what, all, tree, blame); |
548 Target::GetStringForOutputType(target->output_type())) + "\n"); | 165 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 \"" + | 166 OutputString("Don't know how to display \"" + what + "\" for \"" + |
692 Target::GetStringForOutputType(target->output_type()) + "\".\n"); | 167 Target::GetStringForOutputType(target->output_type()) + |
| 168 "\".\n"); |
693 return false; | 169 return false; |
694 } | 170 } |
| 171 // Print single value, without any headers |
| 172 if (!what.empty() && dict->size() == 1 && single_target) { |
| 173 base::DictionaryValue::Iterator iter(*dict); |
| 174 PrintValue(&iter.value(), 0); |
| 175 return true; |
| 176 } |
| 177 |
| 178 OutputString("Target ", DECORATION_YELLOW); |
| 179 OutputString(target->label().GetUserVisibleName(false)); |
| 180 OutputString("\n"); |
| 181 |
| 182 std::unique_ptr<base::Value> v; |
| 183 #define HANDLER(property, handler_name) \ |
| 184 if (dict->Remove(property, &v)) { \ |
| 185 handler_name(property, v.get()); \ |
| 186 } |
| 187 |
| 188 // Entries with DefaultHandler are present to enforce order |
| 189 HANDLER("type", LabelHandler); |
| 190 HANDLER("toolchain", LabelHandler); |
| 191 HANDLER(variables::kVisibility, VisibilityHandler); |
| 192 HANDLER(variables::kTestonly, DefaultHandler); |
| 193 HANDLER(variables::kCheckIncludes, DefaultHandler); |
| 194 HANDLER(variables::kAllowCircularIncludesFrom, DefaultHandler); |
| 195 HANDLER(variables::kSources, DefaultHandler); |
| 196 HANDLER(variables::kPublic, PublicHandler); |
| 197 HANDLER(variables::kInputs, DefaultHandler); |
| 198 HANDLER(variables::kConfigs, ConfigsHandler); |
| 199 HANDLER(variables::kPublicConfigs, ConfigsHandler); |
| 200 HANDLER(variables::kAllDependentConfigs, ConfigsHandler); |
| 201 HANDLER(variables::kScript, DefaultHandler); |
| 202 HANDLER(variables::kArgs, DefaultHandler); |
| 203 HANDLER(variables::kDepfile, DefaultHandler); |
| 204 ProcessOutputs(dict.get()); |
| 205 HANDLER("bundle_data", DefaultHandler); |
| 206 HANDLER(variables::kArflags, DefaultHandler); |
| 207 HANDLER(variables::kAsmflags, DefaultHandler); |
| 208 HANDLER(variables::kCflags, DefaultHandler); |
| 209 HANDLER(variables::kCflagsC, DefaultHandler); |
| 210 HANDLER(variables::kCflagsCC, DefaultHandler); |
| 211 HANDLER(variables::kCflagsObjC, DefaultHandler); |
| 212 HANDLER(variables::kCflagsObjCC, DefaultHandler); |
| 213 HANDLER(variables::kDefines, DefaultHandler); |
| 214 HANDLER(variables::kIncludeDirs, DefaultHandler); |
| 215 HANDLER(variables::kLdflags, DefaultHandler); |
| 216 HANDLER(variables::kPrecompiledHeader, DefaultHandler); |
| 217 HANDLER(variables::kPrecompiledSource, DefaultHandler); |
| 218 HANDLER(variables::kDeps, DepsHandler); |
| 219 HANDLER(variables::kLibs, DefaultHandler); |
| 220 HANDLER(variables::kLibDirs, DefaultHandler); |
| 221 |
| 222 #undef HANDLER |
| 223 |
| 224 // Process the rest (if any) |
| 225 base::DictionaryValue::Iterator iter(*dict); |
| 226 while (!iter.IsAtEnd()) { |
| 227 DefaultHandler(iter.key(), &iter.value()); |
| 228 iter.Advance(); |
| 229 } |
| 230 |
695 return true; | 231 return true; |
696 } | 232 } |
697 | 233 |
698 bool PrintConfig(const Config* config, | 234 bool PrintConfig(const Config* config, |
699 const std::string& what, | 235 const std::string& what, |
700 bool display_config_header) { | 236 bool single_config) { |
701 const ConfigValues& values = config->resolved_values(); | 237 std::unique_ptr<base::DictionaryValue> dict = |
702 | 238 DescBuilder::DescriptionForConfig(config, what); |
703 if (display_config_header) { | 239 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"); | 240 OutputString("Don't know how to display \"" + what + "\" for a config.\n"); |
752 return false; | 241 return false; |
753 } | 242 } |
| 243 // Print single value, without any headers |
| 244 if (!what.empty() && dict->size() == 1 && single_config) { |
| 245 base::DictionaryValue::Iterator iter(*dict); |
| 246 PrintValue(&iter.value(), 0); |
| 247 return true; |
| 248 } |
| 249 |
| 250 OutputString("Config: ", DECORATION_YELLOW); |
| 251 OutputString(config->label().GetUserVisibleName(false)); |
| 252 OutputString("\n"); |
| 253 |
| 254 std::unique_ptr<base::Value> v; |
| 255 #define HANDLER(property, handler_name) \ |
| 256 if (dict->Remove(property, &v)) { \ |
| 257 handler_name(property, v.get()); \ |
| 258 } |
| 259 |
| 260 HANDLER("toolchain", LabelHandler); |
| 261 if (!config->configs().empty()) { |
| 262 OutputString( |
| 263 "(This is a composite config, the values below are after the\n" |
| 264 "expansion of the child configs.)\n"); |
| 265 } |
| 266 HANDLER(variables::kArflags, DefaultHandler); |
| 267 HANDLER(variables::kAsmflags, DefaultHandler); |
| 268 HANDLER(variables::kCflags, DefaultHandler); |
| 269 HANDLER(variables::kCflagsC, DefaultHandler); |
| 270 HANDLER(variables::kCflagsCC, DefaultHandler); |
| 271 HANDLER(variables::kCflagsObjC, DefaultHandler); |
| 272 HANDLER(variables::kCflagsObjCC, DefaultHandler); |
| 273 HANDLER(variables::kDefines, DefaultHandler); |
| 274 HANDLER(variables::kIncludeDirs, DefaultHandler); |
| 275 HANDLER(variables::kLdflags, DefaultHandler); |
| 276 HANDLER(variables::kLibs, DefaultHandler); |
| 277 HANDLER(variables::kLibDirs, DefaultHandler); |
| 278 HANDLER(variables::kPrecompiledHeader, DefaultHandler); |
| 279 HANDLER(variables::kPrecompiledSource, DefaultHandler); |
| 280 |
| 281 #undef HANDLER |
| 282 |
754 return true; | 283 return true; |
755 } | 284 } |
756 | 285 |
757 } // namespace | 286 } // namespace |
758 | 287 |
759 // desc ------------------------------------------------------------------------ | 288 // desc ------------------------------------------------------------------------ |
760 | 289 |
761 const char kDesc[] = "desc"; | 290 const char kDesc[] = "desc"; |
762 const char kDesc_HelpShort[] = | 291 const char kDesc_HelpShort[] = |
763 "desc: Show lots of insightful information about a target or config."; | 292 "desc: Show lots of insightful information about a target or config."; |
764 const char kDesc_Help[] = | 293 const char kDesc_Help[] = |
765 "gn desc <out_dir> <label or pattern> [<what to show>] [--blame]\n" | 294 "gn desc <out_dir> <label or pattern> [<what to show>] [--blame] " |
| 295 "[--format=json]\n" |
766 "\n" | 296 "\n" |
767 " Displays information about a given target or config. The build\n" | 297 " 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" | 298 " build parameters will be taken for the build in the given <out_dir>.\n" |
769 "\n" | 299 "\n" |
770 " The <label or pattern> can be a target label, a config label, or a\n" | 300 " 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" | 301 " label pattern (see \"gn help label_pattern\"). A label pattern will\n" |
772 " only match targets.\n" | 302 " only match targets.\n" |
773 "\n" | 303 "\n" |
774 "Possibilities for <what to show>\n" | 304 "Possibilities for <what to show>\n" |
775 "\n" | 305 "\n" |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
807 "\n" | 337 "\n" |
808 " The output is a list of file names relative to the build\n" | 338 " The output is a list of file names relative to the build\n" |
809 " directory. See \"gn help runtime_deps\" for how this is computed.\n" | 339 " directory. See \"gn help runtime_deps\" for how this is computed.\n" |
810 " This also works with \"--blame\" to see the source of the\n" | 340 " This also works with \"--blame\" to see the source of the\n" |
811 " dependency.\n" | 341 " dependency.\n" |
812 "\n" | 342 "\n" |
813 "Shared flags\n" | 343 "Shared flags\n" |
814 "\n" | 344 "\n" |
815 ALL_TOOLCHAINS_SWITCH_HELP | 345 ALL_TOOLCHAINS_SWITCH_HELP |
816 "\n" | 346 "\n" |
| 347 " --format=json\n" |
| 348 " Format the output as JSON instead of text.\n" |
| 349 "\n" |
817 "Target flags\n" | 350 "Target flags\n" |
818 "\n" | 351 "\n" |
819 " --blame\n" | 352 " --blame\n" |
820 " Used with any value specified on a config, this will name\n" | 353 " Used with any value specified on a config, this will name\n" |
821 " the config that cause that target to get the flag. This doesn't\n" | 354 " the config that cause that target to get the flag. This doesn't\n" |
822 " currently work for libs and lib_dirs because those are inherited\n" | 355 " currently work for libs and lib_dirs because those are inherited\n" |
823 " and are more complicated to figure out the blame (patches\n" | 356 " and are more complicated to figure out the blame (patches\n" |
824 " welcome).\n" | 357 " welcome).\n" |
825 "\n" | 358 "\n" |
826 "Configs\n" | 359 "Configs\n" |
(...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
915 | 448 |
916 if (!ResolveFromCommandLineInput( | 449 if (!ResolveFromCommandLineInput( |
917 setup, target_list, cmdline->HasSwitch(switches::kAllToolchains), | 450 setup, target_list, cmdline->HasSwitch(switches::kAllToolchains), |
918 &target_matches, &config_matches, &toolchain_matches, &file_matches)) | 451 &target_matches, &config_matches, &toolchain_matches, &file_matches)) |
919 return 1; | 452 return 1; |
920 | 453 |
921 std::string what_to_print; | 454 std::string what_to_print; |
922 if (args.size() == 3) | 455 if (args.size() == 3) |
923 what_to_print = args[2]; | 456 what_to_print = args[2]; |
924 | 457 |
925 bool multiple_outputs = (target_matches.size() + config_matches.size()) > 1; | 458 bool json = cmdline->GetSwitchValueASCII("format") == "json"; |
926 | 459 |
927 // Display headers for each target when printing all values, or when printing | 460 if (json) { |
928 // multiple targets or configs. | 461 // Convert all targets/configs to JSON, serialize and print them |
929 bool display_item_header = multiple_outputs || what_to_print.empty(); | 462 auto res = base::WrapUnique(new base::DictionaryValue()); |
| 463 if (!target_matches.empty()) { |
| 464 for (const auto* target : target_matches) { |
| 465 res->Set(target->label().GetUserVisibleName( |
| 466 target->settings()->default_toolchain_label()), |
| 467 DescBuilder::DescriptionForTarget( |
| 468 target, what_to_print, cmdline->HasSwitch(kAll), |
| 469 cmdline->HasSwitch(kTree), cmdline->HasSwitch(kBlame))); |
| 470 } |
| 471 } else if (!config_matches.empty()) { |
| 472 for (const auto* config : config_matches) { |
| 473 res->Set(config->label().GetUserVisibleName(false), |
| 474 DescBuilder::DescriptionForConfig(config, what_to_print)); |
| 475 } |
| 476 } |
| 477 std::string s; |
| 478 base::JSONWriter::WriteWithOptions( |
| 479 *res.get(), base::JSONWriter::OPTIONS_PRETTY_PRINT, &s); |
| 480 OutputString(s); |
| 481 } else { |
| 482 // Regular (non-json) formatted output |
| 483 bool multiple_outputs = (target_matches.size() + config_matches.size()) > 1; |
930 | 484 |
931 bool printed_output = false; | 485 bool printed_output = false; |
932 for (const Target* target : target_matches) { | 486 for (const Target* target : target_matches) { |
933 if (printed_output) | 487 if (printed_output) |
934 OutputString("\n\n"); | 488 OutputString("\n\n"); |
935 printed_output = true; | 489 printed_output = true; |
936 | 490 |
937 if (!PrintTarget(target, what_to_print, display_item_header)) | 491 if (!PrintTarget(target, what_to_print, !multiple_outputs, |
938 return 1; | 492 cmdline->HasSwitch(kAll), cmdline->HasSwitch(kTree), |
939 } | 493 cmdline->HasSwitch(kBlame))) |
940 for (const Config* config : config_matches) { | 494 return 1; |
941 if (printed_output) | 495 } |
942 OutputString("\n\n"); | 496 for (const Config* config : config_matches) { |
943 printed_output = true; | 497 if (printed_output) |
| 498 OutputString("\n\n"); |
| 499 printed_output = true; |
944 | 500 |
945 if (!PrintConfig(config, what_to_print, display_item_header)) | 501 if (!PrintConfig(config, what_to_print, !multiple_outputs)) |
946 return 1; | 502 return 1; |
| 503 } |
947 } | 504 } |
948 | 505 |
949 return 0; | 506 return 0; |
950 } | 507 } |
951 | 508 |
952 } // namespace commands | 509 } // namespace commands |
OLD | NEW |