[Session restore] Rename group name Enabled to Restore.
[chromium-blink-merge.git] / tools / gn / command_desc.cc
blob8b321e4a20f41307c7dfe1a071c2af4ce61266c0
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,
44 std::set<const Target*>* result);
46 void RecursiveCollectDeps(const Target* target,
47 std::set<const Target*>* result) {
48 if (result->find(target) != result->end())
49 return; // Already did this target.
50 result->insert(target);
52 RecursiveCollectChildDeps(target, result);
55 void RecursiveCollectChildDeps(const Target* target,
56 std::set<const Target*>* result) {
57 for (const auto& pair : target->GetDeps(Target::DEPS_ALL))
58 RecursiveCollectDeps(pair.ptr, result);
61 // Prints dependencies of the given target (not the target itself). If the
62 // set is non-null, new targets encountered will be added to the set, and if
63 // a dependency is in the set already, it will not be recused into. When the
64 // set is null, all dependencies will be printed.
65 void RecursivePrintDeps(const Target* target,
66 const Label& default_toolchain,
67 std::set<const Target*>* seen_targets,
68 int indent_level) {
69 // Combine all deps into one sorted list.
70 std::vector<LabelTargetPair> sorted_deps;
71 for (const auto& pair : target->GetDeps(Target::DEPS_ALL))
72 sorted_deps.push_back(pair);
73 std::sort(sorted_deps.begin(), sorted_deps.end(),
74 LabelPtrLabelLess<Target>());
76 std::string indent(indent_level * 2, ' ');
77 for (const auto& pair : sorted_deps) {
78 const Target* cur_dep = pair.ptr;
80 OutputString(indent +
81 cur_dep->label().GetUserVisibleName(default_toolchain));
82 bool print_children = true;
83 if (seen_targets) {
84 if (seen_targets->find(cur_dep) == seen_targets->end()) {
85 // New target, mark it visited.
86 seen_targets->insert(cur_dep);
87 } else {
88 // Already seen.
89 print_children = false;
90 // Only print "..." if something is actually elided, which means that
91 // the current target has children.
92 if (!cur_dep->public_deps().empty() ||
93 !cur_dep->private_deps().empty() ||
94 !cur_dep->data_deps().empty())
95 OutputString("...");
99 OutputString("\n");
100 if (print_children) {
101 RecursivePrintDeps(cur_dep, default_toolchain, seen_targets,
102 indent_level + 1);
107 void PrintDeps(const Target* target, bool display_header) {
108 const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
109 Label toolchain_label = target->label().GetToolchainLabel();
111 // Tree mode is separate.
112 if (cmdline->HasSwitch("tree")) {
113 if (display_header)
114 OutputString("\nDependency tree:\n");
116 if (cmdline->HasSwitch("all")) {
117 // Show all tree deps with no eliding.
118 RecursivePrintDeps(target, toolchain_label, nullptr, 1);
119 } else {
120 // Don't recurse into duplicates.
121 std::set<const Target*> seen_targets;
122 RecursivePrintDeps(target, toolchain_label, &seen_targets, 1);
124 return;
127 // Collect the deps to display.
128 if (cmdline->HasSwitch("all")) {
129 // Show all dependencies.
130 if (display_header)
131 OutputString("\nAll recursive dependencies:\n");
133 std::set<const Target*> all_deps;
134 RecursiveCollectChildDeps(target, &all_deps);
135 FilterAndPrintTargetSet(display_header, all_deps);
136 } else {
137 std::vector<const Target*> deps;
138 // Show direct dependencies only.
139 if (display_header) {
140 OutputString(
141 "\nDirect dependencies "
142 "(try also \"--all\", \"--tree\", or even \"--all --tree\"):\n");
144 for (const auto& pair : target->GetDeps(Target::DEPS_ALL))
145 deps.push_back(pair.ptr);
146 std::sort(deps.begin(), deps.end());
147 FilterAndPrintTargets(display_header, &deps);
151 void PrintForwardDependentConfigsFrom(const Target* target,
152 bool display_header) {
153 if (target->forward_dependent_configs().empty())
154 return;
156 if (display_header)
157 OutputString("\nforward_dependent_configs_from:\n");
159 // Collect the sorted list of deps.
160 std::vector<Label> forward;
161 for (const auto& pair : target->forward_dependent_configs())
162 forward.push_back(pair.label);
163 std::sort(forward.begin(), forward.end());
165 Label toolchain_label = target->label().GetToolchainLabel();
166 for (const auto& fwd : forward)
167 OutputString(" " + fwd.GetUserVisibleName(toolchain_label) + "\n");
170 // libs and lib_dirs are special in that they're inherited. We don't currently
171 // implement a blame feature for this since the bottom-up inheritance makes
172 // this difficult.
173 void PrintLibDirs(const Target* target, bool display_header) {
174 const OrderedSet<SourceDir>& lib_dirs = target->all_lib_dirs();
175 if (lib_dirs.empty())
176 return;
178 if (display_header)
179 OutputString("\nlib_dirs\n");
181 for (size_t i = 0; i < lib_dirs.size(); i++)
182 OutputString(" " + FormatSourceDir(lib_dirs[i]) + "\n");
185 void PrintLibs(const Target* target, bool display_header) {
186 const OrderedSet<std::string>& libs = target->all_libs();
187 if (libs.empty())
188 return;
190 if (display_header)
191 OutputString("\nlibs\n");
193 for (size_t i = 0; i < libs.size(); i++)
194 OutputString(" " + libs[i] + "\n");
197 void PrintPublic(const Target* target, bool display_header) {
198 if (display_header)
199 OutputString("\npublic:\n");
201 if (target->all_headers_public()) {
202 OutputString(" [All headers listed in the sources are public.]\n");
203 return;
206 Target::FileList public_headers = target->public_headers();
207 std::sort(public_headers.begin(), public_headers.end());
208 for (const auto& hdr : public_headers)
209 OutputString(" " + hdr.value() + "\n");
212 void PrintCheckIncludes(const Target* target, bool display_header) {
213 if (display_header)
214 OutputString("\ncheck_includes:\n");
216 if (target->check_includes())
217 OutputString(" true\n");
218 else
219 OutputString(" false\n");
222 void PrintAllowCircularIncludesFrom(const Target* target, bool display_header) {
223 if (display_header)
224 OutputString("\nallow_circular_includes_from:\n");
226 Label toolchain_label = target->label().GetToolchainLabel();
227 for (const auto& cur : target->allow_circular_includes_from())
228 OutputString(" " + cur.GetUserVisibleName(toolchain_label) + "\n");
231 void PrintVisibility(const Target* target, bool display_header) {
232 if (display_header)
233 OutputString("\nvisibility:\n");
235 OutputString(target->visibility().Describe(2, false));
238 void PrintTestonly(const Target* target, bool display_header) {
239 if (display_header)
240 OutputString("\ntestonly:\n");
242 if (target->testonly())
243 OutputString(" true\n");
244 else
245 OutputString(" false\n");
248 void PrintConfigsVector(const Target* target,
249 const LabelConfigVector& configs,
250 const std::string& heading,
251 bool display_header) {
252 if (configs.empty())
253 return;
255 // Don't sort since the order determines how things are processed.
256 if (display_header)
257 OutputString("\n" + heading + " (in order applying):\n");
259 Label toolchain_label = target->label().GetToolchainLabel();
260 for (const auto& config : configs) {
261 OutputString(" " + config.label.GetUserVisibleName(toolchain_label) +
262 "\n");
266 void PrintConfigsVector(const Target* target,
267 const UniqueVector<LabelConfigPair>& configs,
268 const std::string& heading,
269 bool display_header) {
270 if (configs.empty())
271 return;
273 // Don't sort since the order determines how things are processed.
274 if (display_header)
275 OutputString("\n" + heading + " (in order applying):\n");
277 Label toolchain_label = target->label().GetToolchainLabel();
278 for (const auto& config : configs) {
279 OutputString(" " + config.label.GetUserVisibleName(toolchain_label) +
280 "\n");
284 void PrintConfigs(const Target* target, bool display_header) {
285 PrintConfigsVector(target, target->configs().vector(), "configs",
286 display_header);
289 void PrintPublicConfigs(const Target* target, bool display_header) {
290 PrintConfigsVector(target, target->public_configs(),
291 "public_configs", display_header);
294 void PrintAllDependentConfigs(const Target* target, bool display_header) {
295 PrintConfigsVector(target, target->all_dependent_configs(),
296 "all_dependent_configs", display_header);
299 void PrintFileList(const Target::FileList& files,
300 const std::string& header,
301 bool indent_extra,
302 bool display_header) {
303 if (files.empty())
304 return;
306 if (display_header)
307 OutputString("\n" + header + ":\n");
309 std::string indent = indent_extra ? " " : " ";
311 Target::FileList sorted = files;
312 std::sort(sorted.begin(), sorted.end());
313 for (const auto& elem : sorted)
314 OutputString(indent + elem.value() + "\n");
317 void PrintSources(const Target* target, bool display_header) {
318 PrintFileList(target->sources(), "sources", false, display_header);
321 void PrintInputs(const Target* target, bool display_header) {
322 PrintFileList(target->inputs(), "inputs", false, display_header);
325 void PrintOutputs(const Target* target, bool display_header) {
326 if (display_header)
327 OutputString("\noutputs:\n");
329 if (target->output_type() == Target::ACTION) {
330 // Action, print out outputs, don't apply sources to it.
331 for (const auto& elem : target->action_values().outputs().list()) {
332 OutputString(" " + elem.AsString() + "\n");
334 } else {
335 const SubstitutionList& outputs = target->action_values().outputs();
336 if (!outputs.required_types().empty()) {
337 // Display the pattern and resolved pattern separately, since there are
338 // subtitutions used.
339 OutputString(" Output pattern:\n");
340 for (const auto& elem : outputs.list())
341 OutputString(" " + elem.AsString() + "\n");
343 // Now display what that resolves to given the sources.
344 OutputString("\n Resolved output file list:\n");
347 // Resolved output list.
348 std::vector<SourceFile> output_files;
349 SubstitutionWriter::ApplyListToSources(target->settings(), outputs,
350 target->sources(), &output_files);
351 PrintFileList(output_files, "", true, false);
355 void PrintScript(const Target* target, bool display_header) {
356 if (display_header)
357 OutputString("\nscript:\n");
358 OutputString(" " + target->action_values().script().value() + "\n");
361 void PrintArgs(const Target* target, bool display_header) {
362 if (display_header)
363 OutputString("\nargs:\n");
364 for (const auto& elem : target->action_values().args().list()) {
365 OutputString(" " + elem.AsString() + "\n");
369 void PrintDepfile(const Target* target, bool display_header) {
370 if (target->action_values().depfile().empty())
371 return;
372 if (display_header)
373 OutputString("\ndepfile:\n");
374 OutputString(" " + target->action_values().depfile().AsString() + "\n");
377 // Attribute the origin for attributing from where a target came from. Does
378 // nothing if the input is null or it does not have a location.
379 void OutputSourceOfDep(const ParseNode* origin, std::ostream& out) {
380 if (!origin)
381 return;
382 Location location = origin->GetRange().begin();
383 out << " (Added by " + location.file()->name().value() << ":"
384 << location.line_number() << ")\n";
387 // Templatized writer for writing out different config value types.
388 template<typename T> struct DescValueWriter {};
389 template<> struct DescValueWriter<std::string> {
390 void operator()(const std::string& str, std::ostream& out) const {
391 out << " " << str << "\n";
394 template<> struct DescValueWriter<SourceDir> {
395 void operator()(const SourceDir& dir, std::ostream& out) const {
396 out << " " << FormatSourceDir(dir) << "\n";
400 // Writes a given config value type to the string, optionally with attribution.
401 // This should match RecursiveTargetConfigToStream in the order it traverses.
402 template<typename T> void OutputRecursiveTargetConfig(
403 const Target* target,
404 const char* header_name,
405 const std::vector<T>& (ConfigValues::* getter)() const) {
406 bool display_blame =
407 base::CommandLine::ForCurrentProcess()->HasSwitch("blame");
409 DescValueWriter<T> writer;
410 std::ostringstream out;
412 for (ConfigValuesIterator iter(target); !iter.done(); iter.Next()) {
413 if ((iter.cur().*getter)().empty())
414 continue;
416 // Optional blame sub-head.
417 if (display_blame) {
418 const Config* config = iter.GetCurrentConfig();
419 if (config) {
420 // Source of this value is a config.
421 out << " From " << config->label().GetUserVisibleName(false) << "\n";
422 OutputSourceOfDep(iter.origin(), out);
423 } else {
424 // Source of this value is the target itself.
425 out << " From " << target->label().GetUserVisibleName(false) << "\n";
429 // Actual values.
430 ConfigValuesToStream(iter.cur(), getter, writer, out);
433 std::string out_str = out.str();
434 if (!out_str.empty()) {
435 OutputString("\n" + std::string(header_name) + "\n");
436 OutputString(out_str);
440 } // namespace
442 // desc ------------------------------------------------------------------------
444 const char kDesc[] = "desc";
445 const char kDesc_HelpShort[] =
446 "desc: Show lots of insightful information about a target.";
447 const char kDesc_Help[] =
448 "gn desc <out_dir> <target label> [<what to show>] [--blame]\n"
449 "\n"
450 " Displays information about a given labeled target for the given build.\n"
451 " The build parameters will be taken for the build in the given\n"
452 " <out_dir>.\n"
453 "\n"
454 "Possibilities for <what to show>\n"
455 " (If unspecified an overall summary will be displayed.)\n"
456 "\n"
457 " sources\n"
458 " Source files.\n"
459 "\n"
460 " inputs\n"
461 " Additional input dependencies.\n"
462 "\n"
463 " public\n"
464 " Public header files.\n"
465 "\n"
466 " check_includes\n"
467 " Whether \"gn check\" checks this target for include usage.\n"
468 "\n"
469 " allow_circular_includes_from\n"
470 " Permit includes from these targets.\n"
471 "\n"
472 " visibility\n"
473 " Prints which targets can depend on this one.\n"
474 "\n"
475 " testonly\n"
476 " Whether this target may only be used in tests.\n"
477 "\n"
478 " configs\n"
479 " Shows configs applied to the given target, sorted in the order\n"
480 " they're specified. This includes both configs specified in the\n"
481 " \"configs\" variable, as well as configs pushed onto this target\n"
482 " via dependencies specifying \"all\" or \"direct\" dependent\n"
483 " configs.\n"
484 "\n"
485 " deps\n"
486 " Show immediate or recursive dependencies. See below for flags that\n"
487 " control deps printing.\n"
488 "\n"
489 " public_configs\n"
490 " all_dependent_configs\n"
491 " Shows the labels of configs applied to targets that depend on this\n"
492 " one (either directly or all of them).\n"
493 "\n"
494 " forward_dependent_configs_from\n"
495 " Shows the labels of dependencies for which dependent configs will\n"
496 " be pushed to targets depending on the current one.\n"
497 "\n"
498 " script\n"
499 " args\n"
500 " depfile\n"
501 " Actions only. The script and related values.\n"
502 "\n"
503 " outputs\n"
504 " Outputs for script and copy target types.\n"
505 "\n"
506 " defines [--blame]\n"
507 " include_dirs [--blame]\n"
508 " cflags [--blame]\n"
509 " cflags_cc [--blame]\n"
510 " cflags_cxx [--blame]\n"
511 " ldflags [--blame]\n"
512 " lib_dirs\n"
513 " libs\n"
514 " Shows the given values taken from the target and all configs\n"
515 " applying. See \"--blame\" below.\n"
516 "\n"
517 " --blame\n"
518 " Used with any value specified by a config, this will name\n"
519 " the config that specified the value. This doesn't currently work\n"
520 " for libs and lib_dirs because those are inherited and are more\n"
521 " complicated to figure out the blame (patches welcome).\n"
522 "\n"
523 "Flags that control how deps are printed\n"
524 "\n"
525 " --all\n"
526 " Collects all recursive dependencies and prints a sorted flat list.\n"
527 " Also usable with --tree (see below).\n"
528 "\n"
529 TARGET_PRINTING_MODE_COMMAND_LINE_HELP
530 "\n"
531 TARGET_TESTONLY_FILTER_COMMAND_LINE_HELP
532 "\n"
533 " --tree\n"
534 " Print a dependency tree. By default, duplicates will be elided\n"
535 " with \"...\" but when --all and -tree are used together, no\n"
536 " eliding will be performed.\n"
537 "\n"
538 " The \"deps\", \"public_deps\", and \"data_deps\" will all be\n"
539 " included in the tree.\n"
540 "\n"
541 " Tree output can not be used with the filtering or output flags:\n"
542 " --as, --type, --testonly.\n"
543 "\n"
544 TARGET_TYPE_FILTER_COMMAND_LINE_HELP
545 "\n"
546 "Note\n"
547 "\n"
548 " This command will show the full name of directories and source files,\n"
549 " but when directories and source paths are written to the build file,\n"
550 " they will be adjusted to be relative to the build directory. So the\n"
551 " values for paths displayed by this command won't match (but should\n"
552 " mean the same thing).\n"
553 "\n"
554 "Examples\n"
555 "\n"
556 " gn desc out/Debug //base:base\n"
557 " Summarizes the given target.\n"
558 "\n"
559 " gn desc out/Foo :base_unittests deps --tree\n"
560 " Shows a dependency tree of the \"base_unittests\" project in\n"
561 " the current directory.\n"
562 "\n"
563 " gn desc out/Debug //base defines --blame\n"
564 " Shows defines set for the //base:base target, annotated by where\n"
565 " each one was set from.\n";
567 #define OUTPUT_CONFIG_VALUE(name, type) \
568 OutputRecursiveTargetConfig<type>(target, #name, &ConfigValues::name);
570 int RunDesc(const std::vector<std::string>& args) {
571 if (args.size() != 2 && args.size() != 3) {
572 Err(Location(), "You're holding it wrong.",
573 "Usage: \"gn desc <out_dir> <target_name> [<what to display>]\"")
574 .PrintToStdout();
575 return 1;
578 // Deliberately leaked to avoid expensive process teardown.
579 Setup* setup = new Setup;
580 if (!setup->DoSetup(args[0], false))
581 return 1;
582 if (!setup->Run())
583 return 1;
585 const Target* target = ResolveTargetFromCommandLineString(setup, args[1]);
586 if (!target)
587 return 1;
589 #define CONFIG_VALUE_HANDLER(name, type) \
590 } else if (what == #name) { OUTPUT_CONFIG_VALUE(name, type)
592 if (args.size() == 3) {
593 // User specified one thing to display.
594 const std::string& what = args[2];
595 if (what == variables::kConfigs) {
596 PrintConfigs(target, false);
597 } else if (what == variables::kPublicConfigs) {
598 PrintPublicConfigs(target, false);
599 } else if (what == variables::kAllDependentConfigs) {
600 PrintAllDependentConfigs(target, false);
601 } else if (what == variables::kForwardDependentConfigsFrom) {
602 PrintForwardDependentConfigsFrom(target, false);
603 } else if (what == variables::kSources) {
604 PrintSources(target, false);
605 } else if (what == variables::kPublic) {
606 PrintPublic(target, false);
607 } else if (what == variables::kCheckIncludes) {
608 PrintCheckIncludes(target, false);
609 } else if (what == variables::kAllowCircularIncludesFrom) {
610 PrintAllowCircularIncludesFrom(target, false);
611 } else if (what == variables::kVisibility) {
612 PrintVisibility(target, false);
613 } else if (what == variables::kTestonly) {
614 PrintTestonly(target, false);
615 } else if (what == variables::kInputs) {
616 PrintInputs(target, false);
617 } else if (what == variables::kScript) {
618 PrintScript(target, false);
619 } else if (what == variables::kArgs) {
620 PrintArgs(target, false);
621 } else if (what == variables::kDepfile) {
622 PrintDepfile(target, false);
623 } else if (what == variables::kOutputs) {
624 PrintOutputs(target, false);
625 } else if (what == variables::kDeps) {
626 PrintDeps(target, false);
627 } else if (what == variables::kLibDirs) {
628 PrintLibDirs(target, false);
629 } else if (what == variables::kLibs) {
630 PrintLibs(target, false);
632 CONFIG_VALUE_HANDLER(defines, std::string)
633 CONFIG_VALUE_HANDLER(include_dirs, SourceDir)
634 CONFIG_VALUE_HANDLER(cflags, std::string)
635 CONFIG_VALUE_HANDLER(cflags_c, std::string)
636 CONFIG_VALUE_HANDLER(cflags_cc, std::string)
637 CONFIG_VALUE_HANDLER(cflags_objc, std::string)
638 CONFIG_VALUE_HANDLER(cflags_objcc, std::string)
639 CONFIG_VALUE_HANDLER(ldflags, std::string)
641 } else {
642 OutputString("Don't know how to display \"" + what + "\".\n");
643 return 1;
646 #undef CONFIG_VALUE_HANDLER
647 return 0;
650 // Display summary.
652 // Display this only applicable to binary targets.
653 bool is_binary_output =
654 target->output_type() != Target::GROUP &&
655 target->output_type() != Target::COPY_FILES &&
656 target->output_type() != Target::ACTION &&
657 target->output_type() != Target::ACTION_FOREACH;
659 // Generally we only want to display toolchains on labels when the toolchain
660 // is different than the default one for this target (which we always print
661 // in the header).
662 Label target_toolchain = target->label().GetToolchainLabel();
664 // Header.
665 OutputString("Target: ", DECORATION_YELLOW);
666 OutputString(target->label().GetUserVisibleName(false) + "\n");
667 OutputString("Type: ", DECORATION_YELLOW);
668 OutputString(std::string(
669 Target::GetStringForOutputType(target->output_type())) + "\n");
670 OutputString("Toolchain: ", DECORATION_YELLOW);
671 OutputString(target_toolchain.GetUserVisibleName(false) + "\n");
673 PrintSources(target, true);
674 if (is_binary_output) {
675 PrintPublic(target, true);
676 PrintCheckIncludes(target, true);
677 PrintAllowCircularIncludesFrom(target, true);
679 PrintVisibility(target, true);
680 if (is_binary_output) {
681 PrintTestonly(target, true);
682 PrintConfigs(target, true);
685 PrintPublicConfigs(target, true);
686 PrintAllDependentConfigs(target, true);
687 PrintForwardDependentConfigsFrom(target, true);
689 PrintInputs(target, true);
691 if (is_binary_output) {
692 OUTPUT_CONFIG_VALUE(defines, std::string)
693 OUTPUT_CONFIG_VALUE(include_dirs, SourceDir)
694 OUTPUT_CONFIG_VALUE(cflags, std::string)
695 OUTPUT_CONFIG_VALUE(cflags_c, std::string)
696 OUTPUT_CONFIG_VALUE(cflags_cc, std::string)
697 OUTPUT_CONFIG_VALUE(cflags_objc, std::string)
698 OUTPUT_CONFIG_VALUE(cflags_objcc, std::string)
699 OUTPUT_CONFIG_VALUE(ldflags, std::string)
702 if (target->output_type() == Target::ACTION ||
703 target->output_type() == Target::ACTION_FOREACH) {
704 PrintScript(target, true);
705 PrintArgs(target, true);
706 PrintDepfile(target, true);
709 if (target->output_type() == Target::ACTION ||
710 target->output_type() == Target::ACTION_FOREACH ||
711 target->output_type() == Target::COPY_FILES) {
712 PrintOutputs(target, true);
715 // Libs can be part of any target and get recursively pushed up the chain,
716 // so always display them, even for groups and such.
717 PrintLibs(target, true);
718 PrintLibDirs(target, true);
720 PrintDeps(target, true);
722 return 0;
725 } // namespace commands