Add ctrl+mousewheel plumbing to MimeHandlerView.
[chromium-blink-merge.git] / tools / gn / command_desc.cc
blobccce001e9666280a02f9f3e0b8a6a619dcd2e569
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
3 // found in the LICENSE file.
5 #include <algorithm>
6 #include <set>
7 #include <sstream>
9 #include "base/command_line.h"
10 #include "tools/gn/commands.h"
11 #include "tools/gn/config.h"
12 #include "tools/gn/config_values_extractors.h"
13 #include "tools/gn/deps_iterator.h"
14 #include "tools/gn/filesystem_utils.h"
15 #include "tools/gn/item.h"
16 #include "tools/gn/label.h"
17 #include "tools/gn/setup.h"
18 #include "tools/gn/standard_out.h"
19 #include "tools/gn/substitution_writer.h"
20 #include "tools/gn/target.h"
21 #include "tools/gn/variables.h"
23 namespace commands {
25 namespace {
27 // Prints the given directory in a nice way for the user to view.
28 std::string FormatSourceDir(const SourceDir& dir) {
29 #if defined(OS_WIN)
30 // On Windows we fix up system absolute paths to look like native ones.
31 // Internally, they'll look like "/C:\foo\bar/"
32 if (dir.is_system_absolute()) {
33 std::string buf = dir.value();
34 if (buf.size() > 3 && buf[2] == ':') {
35 buf.erase(buf.begin()); // Erase beginning slash.
36 return buf;
39 #endif
40 return dir.value();
43 void RecursiveCollectChildDeps(const Target* target, std::set<Label>* result);
45 void RecursiveCollectDeps(const Target* target, std::set<Label>* result) {
46 if (result->find(target->label()) != result->end())
47 return; // Already did this target.
48 result->insert(target->label());
50 RecursiveCollectChildDeps(target, result);
53 void RecursiveCollectChildDeps(const Target* target, std::set<Label>* result) {
54 for (DepsIterator iter(target); !iter.done(); iter.Advance())
55 RecursiveCollectDeps(iter.target(), result);
58 // Prints dependencies of the given target (not the target itself). If the
59 // set is non-null, new targets encountered will be added to the set, and if
60 // a dependency is in the set already, it will not be recused into. When the
61 // set is null, all dependencies will be printed.
62 void RecursivePrintDeps(const Target* target,
63 const Label& default_toolchain,
64 std::set<const Target*>* seen_targets,
65 int indent_level) {
66 // Combine all deps into one sorted list.
67 std::vector<LabelTargetPair> sorted_deps;
68 for (DepsIterator iter(target); !iter.done(); iter.Advance())
69 sorted_deps.push_back(iter.pair());
70 std::sort(sorted_deps.begin(), sorted_deps.end(),
71 LabelPtrLabelLess<Target>());
73 std::string indent(indent_level * 2, ' ');
74 for (size_t i = 0; i < sorted_deps.size(); i++) {
75 const Target* cur_dep = sorted_deps[i].ptr;
77 OutputString(indent +
78 cur_dep->label().GetUserVisibleName(default_toolchain));
79 bool print_children = true;
80 if (seen_targets) {
81 if (seen_targets->find(cur_dep) == seen_targets->end()) {
82 // New target, mark it visited.
83 seen_targets->insert(cur_dep);
84 } else {
85 // Already seen.
86 print_children = false;
87 // Only print "..." if something is actually elided, which means that
88 // the current target has children.
89 if (!cur_dep->public_deps().empty() ||
90 !cur_dep->private_deps().empty() ||
91 !cur_dep->data_deps().empty())
92 OutputString("...");
96 OutputString("\n");
97 if (print_children) {
98 RecursivePrintDeps(cur_dep, default_toolchain, seen_targets,
99 indent_level + 1);
104 void PrintDeps(const Target* target, bool display_header) {
105 const CommandLine* cmdline = CommandLine::ForCurrentProcess();
106 Label toolchain_label = target->label().GetToolchainLabel();
108 // Tree mode is separate.
109 if (cmdline->HasSwitch("tree")) {
110 if (display_header)
111 OutputString("\nDependency tree:\n");
113 if (cmdline->HasSwitch("all")) {
114 // Show all tree deps with no eliding.
115 RecursivePrintDeps(target, toolchain_label, NULL, 1);
116 } else {
117 // Don't recurse into duplicates.
118 std::set<const Target*> seen_targets;
119 RecursivePrintDeps(target, toolchain_label, &seen_targets, 1);
121 return;
124 // Collect the deps to display.
125 std::vector<Label> deps;
126 if (cmdline->HasSwitch("all")) {
127 // Show all dependencies.
128 if (display_header)
129 OutputString("\nAll recursive dependencies:\n");
131 std::set<Label> all_deps;
132 RecursiveCollectChildDeps(target, &all_deps);
133 for (std::set<Label>::iterator i = all_deps.begin();
134 i != all_deps.end(); ++i)
135 deps.push_back(*i);
136 } else {
137 // Show direct dependencies only.
138 if (display_header) {
139 OutputString(
140 "\nDirect dependencies "
141 "(try also \"--all\", \"--tree\", or even \"--all --tree\"):\n");
143 for (DepsIterator iter(target); !iter.done(); iter.Advance())
144 deps.push_back(iter.label());
147 std::sort(deps.begin(), deps.end());
148 for (size_t i = 0; i < deps.size(); i++)
149 OutputString(" " + deps[i].GetUserVisibleName(toolchain_label) + "\n");
152 void PrintForwardDependentConfigsFrom(const Target* target,
153 bool display_header) {
154 if (target->forward_dependent_configs().empty())
155 return;
157 if (display_header)
158 OutputString("\nforward_dependent_configs_from:\n");
160 // Collect the sorted list of deps.
161 std::vector<Label> forward;
162 for (size_t i = 0; i < target->forward_dependent_configs().size(); i++)
163 forward.push_back(target->forward_dependent_configs()[i].label);
164 std::sort(forward.begin(), forward.end());
166 Label toolchain_label = target->label().GetToolchainLabel();
167 for (size_t i = 0; i < forward.size(); i++)
168 OutputString(" " + forward[i].GetUserVisibleName(toolchain_label) + "\n");
171 // libs and lib_dirs are special in that they're inherited. We don't currently
172 // implement a blame feature for this since the bottom-up inheritance makes
173 // this difficult.
174 void PrintLibDirs(const Target* target, bool display_header) {
175 const OrderedSet<SourceDir>& lib_dirs = target->all_lib_dirs();
176 if (lib_dirs.empty())
177 return;
179 if (display_header)
180 OutputString("\nlib_dirs\n");
182 for (size_t i = 0; i < lib_dirs.size(); i++)
183 OutputString(" " + FormatSourceDir(lib_dirs[i]) + "\n");
186 void PrintLibs(const Target* target, bool display_header) {
187 const OrderedSet<std::string>& libs = target->all_libs();
188 if (libs.empty())
189 return;
191 if (display_header)
192 OutputString("\nlibs\n");
194 for (size_t i = 0; i < libs.size(); i++)
195 OutputString(" " + libs[i] + "\n");
198 void PrintPublic(const Target* target, bool display_header) {
199 if (display_header)
200 OutputString("\npublic:\n");
202 if (target->all_headers_public()) {
203 OutputString(" [All headers listed in the sources are public.]\n");
204 return;
207 Target::FileList public_headers = target->public_headers();
208 std::sort(public_headers.begin(), public_headers.end());
209 for (size_t i = 0; i < public_headers.size(); i++)
210 OutputString(" " + public_headers[i].value() + "\n");
213 void PrintCheckIncludes(const Target* target, bool display_header) {
214 if (display_header)
215 OutputString("\ncheck_includes:\n");
217 if (target->check_includes())
218 OutputString(" true\n");
219 else
220 OutputString(" false\n");
223 void PrintAllowCircularIncludesFrom(const Target* target, bool display_header) {
224 if (display_header)
225 OutputString("\nallow_circular_includes_from:\n");
227 Label toolchain_label = target->label().GetToolchainLabel();
228 const std::set<Label>& allow = target->allow_circular_includes_from();
229 for (std::set<Label>::const_iterator iter = allow.begin();
230 iter != allow.end(); ++iter)
231 OutputString(" " + iter->GetUserVisibleName(toolchain_label) + "\n");
234 void PrintVisibility(const Target* target, bool display_header) {
235 if (display_header)
236 OutputString("\nvisibility:\n");
238 OutputString(target->visibility().Describe(2, false));
241 void PrintTestonly(const Target* target, bool display_header) {
242 if (display_header)
243 OutputString("\ntestonly:\n");
245 if (target->testonly())
246 OutputString(" true\n");
247 else
248 OutputString(" false\n");
251 void PrintConfigsVector(const Target* target,
252 const LabelConfigVector& configs,
253 const std::string& heading,
254 bool display_header) {
255 if (configs.empty())
256 return;
258 // Don't sort since the order determines how things are processed.
259 if (display_header)
260 OutputString("\n" + heading + " (in order applying):\n");
262 Label toolchain_label = target->label().GetToolchainLabel();
263 for (size_t i = 0; i < configs.size(); i++) {
264 OutputString(" " +
265 configs[i].label.GetUserVisibleName(toolchain_label) + "\n");
269 void PrintConfigsVector(const Target* target,
270 const UniqueVector<LabelConfigPair>& configs,
271 const std::string& heading,
272 bool display_header) {
273 if (configs.empty())
274 return;
276 // Don't sort since the order determines how things are processed.
277 if (display_header)
278 OutputString("\n" + heading + " (in order applying):\n");
280 Label toolchain_label = target->label().GetToolchainLabel();
281 for (size_t i = 0; i < configs.size(); i++) {
282 OutputString(" " +
283 configs[i].label.GetUserVisibleName(toolchain_label) + "\n");
287 void PrintConfigs(const Target* target, bool display_header) {
288 PrintConfigsVector(target, target->configs().vector(), "configs",
289 display_header);
292 void PrintPublicConfigs(const Target* target, bool display_header) {
293 PrintConfigsVector(target, target->public_configs(),
294 "public_configs", display_header);
297 void PrintAllDependentConfigs(const Target* target, bool display_header) {
298 PrintConfigsVector(target, target->all_dependent_configs(),
299 "all_dependent_configs", display_header);
302 void PrintFileList(const Target::FileList& files,
303 const std::string& header,
304 bool indent_extra,
305 bool display_header) {
306 if (files.empty())
307 return;
309 if (display_header)
310 OutputString("\n" + header + ":\n");
312 std::string indent = indent_extra ? " " : " ";
314 Target::FileList sorted = files;
315 std::sort(sorted.begin(), sorted.end());
316 for (size_t i = 0; i < sorted.size(); i++)
317 OutputString(indent + sorted[i].value() + "\n");
320 void PrintSources(const Target* target, bool display_header) {
321 PrintFileList(target->sources(), "sources", false, display_header);
324 void PrintInputs(const Target* target, bool display_header) {
325 PrintFileList(target->inputs(), "inputs", false, display_header);
328 void PrintOutputs(const Target* target, bool display_header) {
329 if (display_header)
330 OutputString("\noutputs:\n");
332 if (target->output_type() == Target::ACTION) {
333 // Action, print out outputs, don't apply sources to it.
334 for (size_t i = 0; i < target->action_values().outputs().list().size();
335 i++) {
336 OutputString(" " +
337 target->action_values().outputs().list()[i].AsString() +
338 "\n");
340 } else {
341 const SubstitutionList& outputs = target->action_values().outputs();
342 if (!outputs.required_types().empty()) {
343 // Display the pattern and resolved pattern separately, since there are
344 // subtitutions used.
345 OutputString(" Output pattern:\n");
346 for (size_t i = 0; i < outputs.list().size(); i++)
347 OutputString(" " + outputs.list()[i].AsString() + "\n");
349 // Now display what that resolves to given the sources.
350 OutputString("\n Resolved output file list:\n");
353 // Resolved output list.
354 std::vector<SourceFile> output_files;
355 SubstitutionWriter::ApplyListToSources(target->settings(), outputs,
356 target->sources(), &output_files);
357 PrintFileList(output_files, "", true, false);
361 void PrintScript(const Target* target, bool display_header) {
362 if (display_header)
363 OutputString("\nscript:\n");
364 OutputString(" " + target->action_values().script().value() + "\n");
367 void PrintArgs(const Target* target, bool display_header) {
368 if (display_header)
369 OutputString("\nargs:\n");
370 for (size_t i = 0; i < target->action_values().args().list().size(); i++) {
371 OutputString(" " +
372 target->action_values().args().list()[i].AsString() + "\n");
376 void PrintDepfile(const Target* target, bool display_header) {
377 if (target->action_values().depfile().empty())
378 return;
379 if (display_header)
380 OutputString("\ndepfile:\n");
381 OutputString(" " + target->action_values().depfile().AsString() + "\n");
384 // Attribute the origin for attributing from where a target came from. Does
385 // nothing if the input is null or it does not have a location.
386 void OutputSourceOfDep(const ParseNode* origin, std::ostream& out) {
387 if (!origin)
388 return;
389 Location location = origin->GetRange().begin();
390 out << " (Added by " + location.file()->name().value() << ":"
391 << location.line_number() << ")\n";
394 // Templatized writer for writing out different config value types.
395 template<typename T> struct DescValueWriter {};
396 template<> struct DescValueWriter<std::string> {
397 void operator()(const std::string& str, std::ostream& out) const {
398 out << " " << str << "\n";
401 template<> struct DescValueWriter<SourceDir> {
402 void operator()(const SourceDir& dir, std::ostream& out) const {
403 out << " " << FormatSourceDir(dir) << "\n";
407 // Writes a given config value type to the string, optionally with attribution.
408 // This should match RecursiveTargetConfigToStream in the order it traverses.
409 template<typename T> void OutputRecursiveTargetConfig(
410 const Target* target,
411 const char* header_name,
412 const std::vector<T>& (ConfigValues::* getter)() const) {
413 bool display_blame = CommandLine::ForCurrentProcess()->HasSwitch("blame");
415 DescValueWriter<T> writer;
416 std::ostringstream out;
418 for (ConfigValuesIterator iter(target); !iter.done(); iter.Next()) {
419 if ((iter.cur().*getter)().empty())
420 continue;
422 // Optional blame sub-head.
423 if (display_blame) {
424 const Config* config = iter.GetCurrentConfig();
425 if (config) {
426 // Source of this value is a config.
427 out << " From " << config->label().GetUserVisibleName(false) << "\n";
428 OutputSourceOfDep(iter.origin(), out);
429 } else {
430 // Source of this value is the target itself.
431 out << " From " << target->label().GetUserVisibleName(false) << "\n";
435 // Actual values.
436 ConfigValuesToStream(iter.cur(), getter, writer, out);
439 std::string out_str = out.str();
440 if (!out_str.empty()) {
441 OutputString("\n" + std::string(header_name) + "\n");
442 OutputString(out_str);
446 } // namespace
448 // desc ------------------------------------------------------------------------
450 const char kDesc[] = "desc";
451 const char kDesc_HelpShort[] =
452 "desc: Show lots of insightful information about a target.";
453 const char kDesc_Help[] =
454 "gn desc <out_dir> <target label> [<what to show>]\n"
455 " [--blame] [--all | --tree]\n"
456 "\n"
457 " Displays information about a given labeled target for the given build.\n"
458 " The build parameters will be taken for the build in the given\n"
459 " <out_dir>.\n"
460 "\n"
461 "Possibilities for <what to show>:\n"
462 " (If unspecified an overall summary will be displayed.)\n"
463 "\n"
464 " sources\n"
465 " Source files.\n"
466 "\n"
467 " inputs\n"
468 " Additional input dependencies.\n"
469 "\n"
470 " public\n"
471 " Public header files.\n"
472 "\n"
473 " check_includes\n"
474 " Whether \"gn check\" checks this target for include usage.\n"
475 "\n"
476 " allow_circular_includes_from\n"
477 " Permit includes from these targets.\n"
478 "\n"
479 " visibility\n"
480 " Prints which targets can depend on this one.\n"
481 "\n"
482 " testonly\n"
483 " Whether this target may only be used in tests.\n"
484 "\n"
485 " configs\n"
486 " Shows configs applied to the given target, sorted in the order\n"
487 " they're specified. This includes both configs specified in the\n"
488 " \"configs\" variable, as well as configs pushed onto this target\n"
489 " via dependencies specifying \"all\" or \"direct\" dependent\n"
490 " configs.\n"
491 "\n"
492 " deps [--all | --tree]\n"
493 " Show immediate (or, when \"--all\" or \"--tree\" is specified,\n"
494 " recursive) dependencies of the given target. \"--tree\" shows them\n"
495 " in a tree format with duplicates elided (noted by \"...\").\n"
496 " \"--all\" shows them sorted alphabetically. Using both flags will\n"
497 " print a tree with no omissions. The \"deps\", \"public_deps\", and\n"
498 " \"data_deps\" will all be included.\n"
499 "\n"
500 " public_configs\n"
501 " all_dependent_configs\n"
502 " Shows the labels of configs applied to targets that depend on this\n"
503 " one (either directly or all of them).\n"
504 "\n"
505 " forward_dependent_configs_from\n"
506 " Shows the labels of dependencies for which dependent configs will\n"
507 " be pushed to targets depending on the current one.\n"
508 "\n"
509 " script\n"
510 " args\n"
511 " depfile\n"
512 " Actions only. The script and related values.\n"
513 "\n"
514 " outputs\n"
515 " Outputs for script and copy target types.\n"
516 "\n"
517 " defines [--blame]\n"
518 " include_dirs [--blame]\n"
519 " cflags [--blame]\n"
520 " cflags_cc [--blame]\n"
521 " cflags_cxx [--blame]\n"
522 " ldflags [--blame]\n"
523 " lib_dirs\n"
524 " libs\n"
525 " Shows the given values taken from the target and all configs\n"
526 " applying. See \"--blame\" below.\n"
527 "\n"
528 " --blame\n"
529 " Used with any value specified by a config, this will name\n"
530 " the config that specified the value. This doesn't currently work\n"
531 " for libs and lib_dirs because those are inherited and are more\n"
532 " complicated to figure out the blame (patches welcome).\n"
533 "\n"
534 "Note:\n"
535 " This command will show the full name of directories and source files,\n"
536 " but when directories and source paths are written to the build file,\n"
537 " they will be adjusted to be relative to the build directory. So the\n"
538 " values for paths displayed by this command won't match (but should\n"
539 " mean the same thing).\n"
540 "\n"
541 "Examples:\n"
542 " gn desc out/Debug //base:base\n"
543 " Summarizes the given target.\n"
544 "\n"
545 " gn desc out/Foo :base_unittests deps --tree\n"
546 " Shows a dependency tree of the \"base_unittests\" project in\n"
547 " the current directory.\n"
548 "\n"
549 " gn desc out/Debug //base defines --blame\n"
550 " Shows defines set for the //base:base target, annotated by where\n"
551 " each one was set from.\n";
553 #define OUTPUT_CONFIG_VALUE(name, type) \
554 OutputRecursiveTargetConfig<type>(target, #name, &ConfigValues::name);
556 int RunDesc(const std::vector<std::string>& args) {
557 if (args.size() != 2 && args.size() != 3) {
558 Err(Location(), "You're holding it wrong.",
559 "Usage: \"gn desc <out_dir> <target_name> [<what to display>]\"")
560 .PrintToStdout();
561 return 1;
564 // Deliberately leaked to avoid expensive process teardown.
565 Setup* setup = new Setup;
566 if (!setup->DoSetup(args[0], false))
567 return 1;
568 if (!setup->Run())
569 return 1;
571 const Target* target = ResolveTargetFromCommandLineString(setup, args[1]);
572 if (!target)
573 return 1;
575 #define CONFIG_VALUE_HANDLER(name, type) \
576 } else if (what == #name) { OUTPUT_CONFIG_VALUE(name, type)
578 if (args.size() == 3) {
579 // User specified one thing to display.
580 const std::string& what = args[2];
581 if (what == variables::kConfigs) {
582 PrintConfigs(target, false);
583 } else if (what == variables::kPublicConfigs) {
584 PrintPublicConfigs(target, false);
585 } else if (what == variables::kAllDependentConfigs) {
586 PrintAllDependentConfigs(target, false);
587 } else if (what == variables::kForwardDependentConfigsFrom) {
588 PrintForwardDependentConfigsFrom(target, false);
589 } else if (what == variables::kSources) {
590 PrintSources(target, false);
591 } else if (what == variables::kPublic) {
592 PrintPublic(target, false);
593 } else if (what == variables::kCheckIncludes) {
594 PrintCheckIncludes(target, false);
595 } else if (what == variables::kAllowCircularIncludesFrom) {
596 PrintAllowCircularIncludesFrom(target, false);
597 } else if (what == variables::kVisibility) {
598 PrintVisibility(target, false);
599 } else if (what == variables::kTestonly) {
600 PrintTestonly(target, false);
601 } else if (what == variables::kInputs) {
602 PrintInputs(target, false);
603 } else if (what == variables::kScript) {
604 PrintScript(target, false);
605 } else if (what == variables::kArgs) {
606 PrintArgs(target, false);
607 } else if (what == variables::kDepfile) {
608 PrintDepfile(target, false);
609 } else if (what == variables::kOutputs) {
610 PrintOutputs(target, false);
611 } else if (what == variables::kDeps) {
612 PrintDeps(target, false);
613 } else if (what == variables::kLibDirs) {
614 PrintLibDirs(target, false);
615 } else if (what == variables::kLibs) {
616 PrintLibs(target, false);
618 CONFIG_VALUE_HANDLER(defines, std::string)
619 CONFIG_VALUE_HANDLER(include_dirs, SourceDir)
620 CONFIG_VALUE_HANDLER(cflags, std::string)
621 CONFIG_VALUE_HANDLER(cflags_c, std::string)
622 CONFIG_VALUE_HANDLER(cflags_cc, std::string)
623 CONFIG_VALUE_HANDLER(cflags_objc, std::string)
624 CONFIG_VALUE_HANDLER(cflags_objcc, std::string)
625 CONFIG_VALUE_HANDLER(ldflags, std::string)
627 } else {
628 OutputString("Don't know how to display \"" + what + "\".\n");
629 return 1;
632 #undef CONFIG_VALUE_HANDLER
633 return 0;
636 // Display summary.
638 // Display this only applicable to binary targets.
639 bool is_binary_output =
640 target->output_type() != Target::GROUP &&
641 target->output_type() != Target::COPY_FILES &&
642 target->output_type() != Target::ACTION &&
643 target->output_type() != Target::ACTION_FOREACH;
645 // Generally we only want to display toolchains on labels when the toolchain
646 // is different than the default one for this target (which we always print
647 // in the header).
648 Label target_toolchain = target->label().GetToolchainLabel();
650 // Header.
651 OutputString("Target: ", DECORATION_YELLOW);
652 OutputString(target->label().GetUserVisibleName(false) + "\n");
653 OutputString("Type: ", DECORATION_YELLOW);
654 OutputString(std::string(
655 Target::GetStringForOutputType(target->output_type())) + "\n");
656 OutputString("Toolchain: ", DECORATION_YELLOW);
657 OutputString(target_toolchain.GetUserVisibleName(false) + "\n");
659 PrintSources(target, true);
660 if (is_binary_output) {
661 PrintPublic(target, true);
662 PrintCheckIncludes(target, true);
663 PrintAllowCircularIncludesFrom(target, true);
665 PrintVisibility(target, true);
666 if (is_binary_output) {
667 PrintTestonly(target, true);
668 PrintConfigs(target, true);
671 PrintPublicConfigs(target, true);
672 PrintAllDependentConfigs(target, true);
673 PrintForwardDependentConfigsFrom(target, true);
675 PrintInputs(target, true);
677 if (is_binary_output) {
678 OUTPUT_CONFIG_VALUE(defines, std::string)
679 OUTPUT_CONFIG_VALUE(include_dirs, SourceDir)
680 OUTPUT_CONFIG_VALUE(cflags, std::string)
681 OUTPUT_CONFIG_VALUE(cflags_c, std::string)
682 OUTPUT_CONFIG_VALUE(cflags_cc, std::string)
683 OUTPUT_CONFIG_VALUE(cflags_objc, std::string)
684 OUTPUT_CONFIG_VALUE(cflags_objcc, std::string)
685 OUTPUT_CONFIG_VALUE(ldflags, std::string)
688 if (target->output_type() == Target::ACTION ||
689 target->output_type() == Target::ACTION_FOREACH) {
690 PrintScript(target, true);
691 PrintArgs(target, true);
692 PrintDepfile(target, true);
695 if (target->output_type() == Target::ACTION ||
696 target->output_type() == Target::ACTION_FOREACH ||
697 target->output_type() == Target::COPY_FILES) {
698 PrintOutputs(target, true);
701 // Libs can be part of any target and get recursively pushed up the chain,
702 // so always display them, even for groups and such.
703 PrintLibs(target, true);
704 PrintLibDirs(target, true);
706 PrintDeps(target, true);
708 return 0;
711 } // namespace commands