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.
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/runtime_deps.h"
18 #include "tools/gn/setup.h"
19 #include "tools/gn/standard_out.h"
20 #include "tools/gn/substitution_writer.h"
21 #include "tools/gn/target.h"
22 #include "tools/gn/variables.h"
28 // The switch for displaying blame.
29 const char kBlame
[] = "blame";
31 // Prints the given directory in a nice way for the user to view.
32 std::string
FormatSourceDir(const SourceDir
& dir
) {
34 // On Windows we fix up system absolute paths to look like native ones.
35 // Internally, they'll look like "/C:\foo\bar/"
36 if (dir
.is_system_absolute()) {
37 std::string buf
= dir
.value();
38 if (buf
.size() > 3 && buf
[2] == ':') {
39 buf
.erase(buf
.begin()); // Erase beginning slash.
47 void RecursiveCollectChildDeps(const Target
* target
,
48 std::set
<const Target
*>* result
);
50 void RecursiveCollectDeps(const Target
* target
,
51 std::set
<const Target
*>* result
) {
52 if (result
->find(target
) != result
->end())
53 return; // Already did this target.
54 result
->insert(target
);
56 RecursiveCollectChildDeps(target
, result
);
59 void RecursiveCollectChildDeps(const Target
* target
,
60 std::set
<const Target
*>* result
) {
61 for (const auto& pair
: target
->GetDeps(Target::DEPS_ALL
))
62 RecursiveCollectDeps(pair
.ptr
, result
);
65 // Prints dependencies of the given target (not the target itself). If the
66 // set is non-null, new targets encountered will be added to the set, and if
67 // a dependency is in the set already, it will not be recused into. When the
68 // set is null, all dependencies will be printed.
69 void RecursivePrintDeps(const Target
* target
,
70 const Label
& default_toolchain
,
71 std::set
<const Target
*>* seen_targets
,
73 // Combine all deps into one sorted list.
74 std::vector
<LabelTargetPair
> sorted_deps
;
75 for (const auto& pair
: target
->GetDeps(Target::DEPS_ALL
))
76 sorted_deps
.push_back(pair
);
77 std::sort(sorted_deps
.begin(), sorted_deps
.end(),
78 LabelPtrLabelLess
<Target
>());
80 std::string
indent(indent_level
* 2, ' ');
81 for (const auto& pair
: sorted_deps
) {
82 const Target
* cur_dep
= pair
.ptr
;
85 cur_dep
->label().GetUserVisibleName(default_toolchain
));
86 bool print_children
= true;
88 if (seen_targets
->find(cur_dep
) == seen_targets
->end()) {
89 // New target, mark it visited.
90 seen_targets
->insert(cur_dep
);
93 print_children
= false;
94 // Only print "..." if something is actually elided, which means that
95 // the current target has children.
96 if (!cur_dep
->public_deps().empty() ||
97 !cur_dep
->private_deps().empty() ||
98 !cur_dep
->data_deps().empty())
104 if (print_children
) {
105 RecursivePrintDeps(cur_dep
, default_toolchain
, seen_targets
,
111 void PrintDeps(const Target
* target
, bool display_header
) {
112 const base::CommandLine
* cmdline
= base::CommandLine::ForCurrentProcess();
113 Label toolchain_label
= target
->label().GetToolchainLabel();
115 // Tree mode is separate.
116 if (cmdline
->HasSwitch("tree")) {
118 OutputString("\nDependency tree:\n");
120 if (cmdline
->HasSwitch("all")) {
121 // Show all tree deps with no eliding.
122 RecursivePrintDeps(target
, toolchain_label
, nullptr, 1);
124 // Don't recurse into duplicates.
125 std::set
<const Target
*> seen_targets
;
126 RecursivePrintDeps(target
, toolchain_label
, &seen_targets
, 1);
131 // Collect the deps to display.
132 if (cmdline
->HasSwitch("all")) {
133 // Show all dependencies.
135 OutputString("\nAll recursive dependencies:\n");
137 std::set
<const Target
*> all_deps
;
138 RecursiveCollectChildDeps(target
, &all_deps
);
139 FilterAndPrintTargetSet(display_header
, all_deps
);
141 std::vector
<const Target
*> deps
;
142 // Show direct dependencies only.
143 if (display_header
) {
145 "\nDirect dependencies "
146 "(try also \"--all\", \"--tree\", or even \"--all --tree\"):\n");
148 for (const auto& pair
: target
->GetDeps(Target::DEPS_ALL
))
149 deps
.push_back(pair
.ptr
);
150 std::sort(deps
.begin(), deps
.end());
151 FilterAndPrintTargets(display_header
, &deps
);
155 void PrintForwardDependentConfigsFrom(const Target
* target
,
156 bool display_header
) {
157 if (target
->forward_dependent_configs().empty())
161 OutputString("\nforward_dependent_configs_from:\n");
163 // Collect the sorted list of deps.
164 std::vector
<Label
> forward
;
165 for (const auto& pair
: target
->forward_dependent_configs())
166 forward
.push_back(pair
.label
);
167 std::sort(forward
.begin(), forward
.end());
169 Label toolchain_label
= target
->label().GetToolchainLabel();
170 for (const auto& fwd
: forward
)
171 OutputString(" " + fwd
.GetUserVisibleName(toolchain_label
) + "\n");
174 // libs and lib_dirs are special in that they're inherited. We don't currently
175 // implement a blame feature for this since the bottom-up inheritance makes
177 void PrintLibDirs(const Target
* target
, bool display_header
) {
178 const OrderedSet
<SourceDir
>& lib_dirs
= target
->all_lib_dirs();
179 if (lib_dirs
.empty())
183 OutputString("\nlib_dirs\n");
185 for (size_t i
= 0; i
< lib_dirs
.size(); i
++)
186 OutputString(" " + FormatSourceDir(lib_dirs
[i
]) + "\n");
189 void PrintLibs(const Target
* target
, bool display_header
) {
190 const OrderedSet
<std::string
>& libs
= target
->all_libs();
195 OutputString("\nlibs\n");
197 for (size_t i
= 0; i
< libs
.size(); i
++)
198 OutputString(" " + libs
[i
] + "\n");
201 void PrintPublic(const Target
* target
, bool display_header
) {
203 OutputString("\npublic:\n");
205 if (target
->all_headers_public()) {
206 OutputString(" [All headers listed in the sources are public.]\n");
210 Target::FileList public_headers
= target
->public_headers();
211 std::sort(public_headers
.begin(), public_headers
.end());
212 for (const auto& hdr
: public_headers
)
213 OutputString(" " + hdr
.value() + "\n");
216 void PrintCheckIncludes(const Target
* target
, bool display_header
) {
218 OutputString("\ncheck_includes:\n");
220 if (target
->check_includes())
221 OutputString(" true\n");
223 OutputString(" false\n");
226 void PrintAllowCircularIncludesFrom(const Target
* target
, bool display_header
) {
228 OutputString("\nallow_circular_includes_from:\n");
230 Label toolchain_label
= target
->label().GetToolchainLabel();
231 for (const auto& cur
: target
->allow_circular_includes_from())
232 OutputString(" " + cur
.GetUserVisibleName(toolchain_label
) + "\n");
235 void PrintVisibility(const Target
* target
, bool display_header
) {
237 OutputString("\nvisibility:\n");
239 OutputString(target
->visibility().Describe(2, false));
242 void PrintTestonly(const Target
* target
, bool display_header
) {
244 OutputString("\ntestonly:\n");
246 if (target
->testonly())
247 OutputString(" true\n");
249 OutputString(" false\n");
252 void PrintConfigsVector(const Target
* target
,
253 const LabelConfigVector
& configs
,
254 const std::string
& heading
,
255 bool display_header
) {
259 // Don't sort since the order determines how things are processed.
261 OutputString("\n" + heading
+ " (in order applying):\n");
263 Label toolchain_label
= target
->label().GetToolchainLabel();
264 for (const auto& config
: configs
) {
265 OutputString(" " + config
.label
.GetUserVisibleName(toolchain_label
) +
270 void PrintConfigsVector(const Target
* target
,
271 const UniqueVector
<LabelConfigPair
>& configs
,
272 const std::string
& heading
,
273 bool display_header
) {
277 // Don't sort since the order determines how things are processed.
279 OutputString("\n" + heading
+ " (in order applying):\n");
281 Label toolchain_label
= target
->label().GetToolchainLabel();
282 for (const auto& config
: configs
) {
283 OutputString(" " + config
.label
.GetUserVisibleName(toolchain_label
) +
288 void PrintConfigs(const Target
* target
, bool display_header
) {
289 PrintConfigsVector(target
, target
->configs().vector(), "configs",
293 void PrintPublicConfigs(const Target
* target
, bool display_header
) {
294 PrintConfigsVector(target
, target
->public_configs(),
295 "public_configs", display_header
);
298 void PrintAllDependentConfigs(const Target
* target
, bool display_header
) {
299 PrintConfigsVector(target
, target
->all_dependent_configs(),
300 "all_dependent_configs", display_header
);
303 void PrintFileList(const Target::FileList
& files
,
304 const std::string
& header
,
306 bool display_header
) {
311 OutputString("\n" + header
+ ":\n");
313 std::string indent
= indent_extra
? " " : " ";
315 Target::FileList sorted
= files
;
316 std::sort(sorted
.begin(), sorted
.end());
317 for (const auto& elem
: sorted
)
318 OutputString(indent
+ elem
.value() + "\n");
321 void PrintSources(const Target
* target
, bool display_header
) {
322 PrintFileList(target
->sources(), "sources", false, display_header
);
325 void PrintInputs(const Target
* target
, bool display_header
) {
326 PrintFileList(target
->inputs(), "inputs", false, display_header
);
329 void PrintOutputs(const Target
* target
, bool display_header
) {
331 OutputString("\noutputs:\n");
333 if (target
->output_type() == Target::ACTION
) {
334 // Action, print out outputs, don't apply sources to it.
335 for (const auto& elem
: target
->action_values().outputs().list()) {
336 OutputString(" " + elem
.AsString() + "\n");
339 const SubstitutionList
& outputs
= target
->action_values().outputs();
340 if (!outputs
.required_types().empty()) {
341 // Display the pattern and resolved pattern separately, since there are
342 // subtitutions used.
343 OutputString(" Output pattern:\n");
344 for (const auto& elem
: outputs
.list())
345 OutputString(" " + elem
.AsString() + "\n");
347 // Now display what that resolves to given the sources.
348 OutputString("\n Resolved output file list:\n");
351 // Resolved output list.
352 std::vector
<SourceFile
> output_files
;
353 SubstitutionWriter::ApplyListToSources(target
->settings(), outputs
,
354 target
->sources(), &output_files
);
355 PrintFileList(output_files
, "", true, false);
359 void PrintScript(const Target
* target
, bool display_header
) {
361 OutputString("\nscript:\n");
362 OutputString(" " + target
->action_values().script().value() + "\n");
365 void PrintArgs(const Target
* target
, bool display_header
) {
367 OutputString("\nargs:\n");
368 for (const auto& elem
: target
->action_values().args().list()) {
369 OutputString(" " + elem
.AsString() + "\n");
373 void PrintDepfile(const Target
* target
, bool display_header
) {
374 if (target
->action_values().depfile().empty())
377 OutputString("\ndepfile:\n");
378 OutputString(" " + target
->action_values().depfile().AsString() + "\n");
381 // Attribute the origin for attributing from where a target came from. Does
382 // nothing if the input is null or it does not have a location.
383 void OutputSourceOfDep(const ParseNode
* origin
, std::ostream
& out
) {
386 Location location
= origin
->GetRange().begin();
387 out
<< " (Added by " + location
.file()->name().value() << ":"
388 << location
.line_number() << ")\n";
391 // Templatized writer for writing out different config value types.
392 template<typename T
> struct DescValueWriter
{};
393 template<> struct DescValueWriter
<std::string
> {
394 void operator()(const std::string
& str
, std::ostream
& out
) const {
395 out
<< " " << str
<< "\n";
398 template<> struct DescValueWriter
<SourceDir
> {
399 void operator()(const SourceDir
& dir
, std::ostream
& out
) const {
400 out
<< " " << FormatSourceDir(dir
) << "\n";
404 // Writes a given config value type to the string, optionally with attribution.
405 // This should match RecursiveTargetConfigToStream in the order it traverses.
406 template<typename T
> void OutputRecursiveTargetConfig(
407 const Target
* target
,
408 const char* header_name
,
409 const std::vector
<T
>& (ConfigValues::* getter
)() const) {
411 base::CommandLine::ForCurrentProcess()->HasSwitch(kBlame
);
413 DescValueWriter
<T
> writer
;
414 std::ostringstream out
;
416 for (ConfigValuesIterator
iter(target
); !iter
.done(); iter
.Next()) {
417 if ((iter
.cur().*getter
)().empty())
420 // Optional blame sub-head.
422 const Config
* config
= iter
.GetCurrentConfig();
424 // Source of this value is a config.
425 out
<< " From " << config
->label().GetUserVisibleName(false) << "\n";
426 OutputSourceOfDep(iter
.origin(), out
);
428 // Source of this value is the target itself.
429 out
<< " From " << target
->label().GetUserVisibleName(false) << "\n";
434 ConfigValuesToStream(iter
.cur(), getter
, writer
, out
);
437 std::string out_str
= out
.str();
438 if (!out_str
.empty()) {
439 OutputString("\n" + std::string(header_name
) + "\n");
440 OutputString(out_str
);
444 void PrintRuntimeDeps(const Target
* target
) {
446 base::CommandLine::ForCurrentProcess()->HasSwitch(kBlame
);
447 Label toolchain
= target
->label().GetToolchainLabel();
449 const Target
* previous_from
= NULL
;
450 for (const auto& pair
: ComputeRuntimeDeps(target
)) {
452 // Generally a target's runtime deps will be listed sequentially, so
453 // group them and don't duplicate the "from" label for two in a row.
454 if (previous_from
== pair
.second
) {
455 OutputString(" "); // Just indent.
457 previous_from
= pair
.second
;
458 OutputString("From ");
459 OutputString(pair
.second
->label().GetUserVisibleName(toolchain
));
460 OutputString("\n "); // Make the file name indented.
463 OutputString(pair
.first
.value());
470 // desc ------------------------------------------------------------------------
472 const char kDesc
[] = "desc";
473 const char kDesc_HelpShort
[] =
474 "desc: Show lots of insightful information about a target.";
475 const char kDesc_Help
[] =
476 "gn desc <out_dir> <target label> [<what to show>] [--blame]\n"
478 " Displays information about a given labeled target for the given build.\n"
479 " The build parameters will be taken for the build in the given\n"
482 "Possibilities for <what to show>\n"
483 " (If unspecified an overall summary will be displayed.)\n"
489 " Additional input dependencies.\n"
492 " Public header files.\n"
495 " Whether \"gn check\" checks this target for include usage.\n"
497 " allow_circular_includes_from\n"
498 " Permit includes from these targets.\n"
501 " Prints which targets can depend on this one.\n"
504 " Whether this target may only be used in tests.\n"
507 " Shows configs applied to the given target, sorted in the order\n"
508 " they're specified. This includes both configs specified in the\n"
509 " \"configs\" variable, as well as configs pushed onto this target\n"
510 " via dependencies specifying \"all\" or \"direct\" dependent\n"
514 " Show immediate or recursive dependencies. See below for flags that\n"
515 " control deps printing.\n"
518 " all_dependent_configs\n"
519 " Shows the labels of configs applied to targets that depend on this\n"
520 " one (either directly or all of them).\n"
522 " forward_dependent_configs_from\n"
523 " Shows the labels of dependencies for which dependent configs will\n"
524 " be pushed to targets depending on the current one.\n"
529 " Actions only. The script and related values.\n"
532 " Outputs for script and copy target types.\n"
534 " defines [--blame]\n"
535 " include_dirs [--blame]\n"
536 " cflags [--blame]\n"
537 " cflags_cc [--blame]\n"
538 " cflags_cxx [--blame]\n"
539 " ldflags [--blame]\n"
542 " Shows the given values taken from the target and all configs\n"
543 " applying. See \"--blame\" below.\n"
546 " Compute all runtime deps for the given target. This is a\n"
547 " computed list and does not correspond to any GN variable, unlike\n"
548 " most other values here.\n"
550 " The output is a list of file names relative to the build\n"
551 " directory. See \"gn help runtime_deps\" for how this is computed.\n"
552 " This also works with \"--blame\" to see the source of the\n"
558 " Used with any value specified by a config, this will name\n"
559 " the config that specified the value. This doesn't currently work\n"
560 " for libs and lib_dirs because those are inherited and are more\n"
561 " complicated to figure out the blame (patches welcome).\n"
563 "Flags that control how deps are printed\n"
566 " Collects all recursive dependencies and prints a sorted flat list.\n"
567 " Also usable with --tree (see below).\n"
569 TARGET_PRINTING_MODE_COMMAND_LINE_HELP
571 TARGET_TESTONLY_FILTER_COMMAND_LINE_HELP
574 " Print a dependency tree. By default, duplicates will be elided\n"
575 " with \"...\" but when --all and -tree are used together, no\n"
576 " eliding will be performed.\n"
578 " The \"deps\", \"public_deps\", and \"data_deps\" will all be\n"
579 " included in the tree.\n"
581 " Tree output can not be used with the filtering or output flags:\n"
582 " --as, --type, --testonly.\n"
584 TARGET_TYPE_FILTER_COMMAND_LINE_HELP
588 " This command will show the full name of directories and source files,\n"
589 " but when directories and source paths are written to the build file,\n"
590 " they will be adjusted to be relative to the build directory. So the\n"
591 " values for paths displayed by this command won't match (but should\n"
592 " mean the same thing).\n"
596 " gn desc out/Debug //base:base\n"
597 " Summarizes the given target.\n"
599 " gn desc out/Foo :base_unittests deps --tree\n"
600 " Shows a dependency tree of the \"base_unittests\" project in\n"
601 " the current directory.\n"
603 " gn desc out/Debug //base defines --blame\n"
604 " Shows defines set for the //base:base target, annotated by where\n"
605 " each one was set from.\n";
607 #define OUTPUT_CONFIG_VALUE(name, type) \
608 OutputRecursiveTargetConfig<type>(target, #name, &ConfigValues::name);
610 int RunDesc(const std::vector
<std::string
>& args
) {
611 if (args
.size() != 2 && args
.size() != 3) {
612 Err(Location(), "You're holding it wrong.",
613 "Usage: \"gn desc <out_dir> <target_name> [<what to display>]\"")
618 // Deliberately leaked to avoid expensive process teardown.
619 Setup
* setup
= new Setup
;
620 setup
->build_settings().set_check_for_bad_items(false);
621 if (!setup
->DoSetup(args
[0], false))
626 const Target
* target
= ResolveTargetFromCommandLineString(setup
, args
[1]);
630 #define CONFIG_VALUE_HANDLER(name, type) \
631 } else if (what == #name) { OUTPUT_CONFIG_VALUE(name, type)
633 if (args
.size() == 3) {
634 // User specified one thing to display.
635 const std::string
& what
= args
[2];
636 if (what
== variables::kConfigs
) {
637 PrintConfigs(target
, false);
638 } else if (what
== variables::kPublicConfigs
) {
639 PrintPublicConfigs(target
, false);
640 } else if (what
== variables::kAllDependentConfigs
) {
641 PrintAllDependentConfigs(target
, false);
642 } else if (what
== variables::kForwardDependentConfigsFrom
) {
643 PrintForwardDependentConfigsFrom(target
, false);
644 } else if (what
== variables::kSources
) {
645 PrintSources(target
, false);
646 } else if (what
== variables::kPublic
) {
647 PrintPublic(target
, false);
648 } else if (what
== variables::kCheckIncludes
) {
649 PrintCheckIncludes(target
, false);
650 } else if (what
== variables::kAllowCircularIncludesFrom
) {
651 PrintAllowCircularIncludesFrom(target
, false);
652 } else if (what
== variables::kVisibility
) {
653 PrintVisibility(target
, false);
654 } else if (what
== variables::kTestonly
) {
655 PrintTestonly(target
, false);
656 } else if (what
== variables::kInputs
) {
657 PrintInputs(target
, false);
658 } else if (what
== variables::kScript
) {
659 PrintScript(target
, false);
660 } else if (what
== variables::kArgs
) {
661 PrintArgs(target
, false);
662 } else if (what
== variables::kDepfile
) {
663 PrintDepfile(target
, false);
664 } else if (what
== variables::kOutputs
) {
665 PrintOutputs(target
, false);
666 } else if (what
== variables::kDeps
) {
667 PrintDeps(target
, false);
668 } else if (what
== variables::kLibDirs
) {
669 PrintLibDirs(target
, false);
670 } else if (what
== variables::kLibs
) {
671 PrintLibs(target
, false);
672 } else if (what
== "runtime_deps") {
673 PrintRuntimeDeps(target
);
675 CONFIG_VALUE_HANDLER(defines
, std::string
)
676 CONFIG_VALUE_HANDLER(include_dirs
, SourceDir
)
677 CONFIG_VALUE_HANDLER(cflags
, std::string
)
678 CONFIG_VALUE_HANDLER(cflags_c
, std::string
)
679 CONFIG_VALUE_HANDLER(cflags_cc
, std::string
)
680 CONFIG_VALUE_HANDLER(cflags_objc
, std::string
)
681 CONFIG_VALUE_HANDLER(cflags_objcc
, std::string
)
682 CONFIG_VALUE_HANDLER(ldflags
, std::string
)
685 OutputString("Don't know how to display \"" + what
+ "\".\n");
689 #undef CONFIG_VALUE_HANDLER
695 // Display this only applicable to binary targets.
696 bool is_binary_output
=
697 target
->output_type() != Target::GROUP
&&
698 target
->output_type() != Target::COPY_FILES
&&
699 target
->output_type() != Target::ACTION
&&
700 target
->output_type() != Target::ACTION_FOREACH
;
702 // Generally we only want to display toolchains on labels when the toolchain
703 // is different than the default one for this target (which we always print
705 Label target_toolchain
= target
->label().GetToolchainLabel();
708 OutputString("Target: ", DECORATION_YELLOW
);
709 OutputString(target
->label().GetUserVisibleName(false) + "\n");
710 OutputString("Type: ", DECORATION_YELLOW
);
711 OutputString(std::string(
712 Target::GetStringForOutputType(target
->output_type())) + "\n");
713 OutputString("Toolchain: ", DECORATION_YELLOW
);
714 OutputString(target_toolchain
.GetUserVisibleName(false) + "\n");
716 PrintSources(target
, true);
717 if (is_binary_output
) {
718 PrintPublic(target
, true);
719 PrintCheckIncludes(target
, true);
720 PrintAllowCircularIncludesFrom(target
, true);
722 PrintVisibility(target
, true);
723 if (is_binary_output
) {
724 PrintTestonly(target
, true);
725 PrintConfigs(target
, true);
728 PrintPublicConfigs(target
, true);
729 PrintAllDependentConfigs(target
, true);
730 PrintForwardDependentConfigsFrom(target
, true);
732 PrintInputs(target
, true);
734 if (is_binary_output
) {
735 OUTPUT_CONFIG_VALUE(defines
, std::string
)
736 OUTPUT_CONFIG_VALUE(include_dirs
, SourceDir
)
737 OUTPUT_CONFIG_VALUE(cflags
, std::string
)
738 OUTPUT_CONFIG_VALUE(cflags_c
, std::string
)
739 OUTPUT_CONFIG_VALUE(cflags_cc
, std::string
)
740 OUTPUT_CONFIG_VALUE(cflags_objc
, std::string
)
741 OUTPUT_CONFIG_VALUE(cflags_objcc
, std::string
)
742 OUTPUT_CONFIG_VALUE(ldflags
, std::string
)
745 if (target
->output_type() == Target::ACTION
||
746 target
->output_type() == Target::ACTION_FOREACH
) {
747 PrintScript(target
, true);
748 PrintArgs(target
, true);
749 PrintDepfile(target
, true);
752 if (target
->output_type() == Target::ACTION
||
753 target
->output_type() == Target::ACTION_FOREACH
||
754 target
->output_type() == Target::COPY_FILES
) {
755 PrintOutputs(target
, true);
758 // Libs can be part of any target and get recursively pushed up the chain,
759 // so always display them, even for groups and such.
760 PrintLibs(target
, true);
761 PrintLibDirs(target
, true);
763 PrintDeps(target
, true);
768 } // namespace commands