Enhance GN's "desc" command to support configs.

Reworks the flow to duplicate things a bit less in the "overview output" and
"one thing" output mode.

Supports wildcards in the output like most other introspection commands.

Consolidates help and switch value for "all-toolchains"

Removed colons from desc headings. These were inconsistent and I've been moving away from these in the help and other places where we use indenting for hierarchy anyway.

Header printing is also more consistent. Previously if you asked for one variable, sometimes you would get a header and sometimes you wouldn't, depending on the type. Now it will consistently elide headers.

Review-Url: https://codereview.chromium.org/1934003002
Cr-Original-Commit-Position: refs/heads/master@{#392700}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: eda2744e306d07f7bee6a25b3961a3bcabb9de17
diff --git a/tools/gn/command_desc.cc b/tools/gn/command_desc.cc
index 021232c..f2371e9 100644
--- a/tools/gn/command_desc.cc
+++ b/tools/gn/command_desc.cc
@@ -21,6 +21,7 @@
 #include "tools/gn/setup.h"
 #include "tools/gn/standard_out.h"
 #include "tools/gn/substitution_writer.h"
+#include "tools/gn/switches.h"
 #include "tools/gn/target.h"
 #include "tools/gn/variables.h"
 
@@ -119,7 +120,7 @@
   // Tree mode is separate.
   if (cmdline->HasSwitch(kTree)) {
     if (display_header)
-      OutputString("\nDependency tree:\n");
+      OutputString("\nDependency tree\n");
 
     if (cmdline->HasSwitch("all")) {
       // Show all tree deps with no eliding.
@@ -136,7 +137,7 @@
   if (cmdline->HasSwitch("all")) {
     // Show all dependencies.
     if (display_header)
-      OutputString("\nAll recursive dependencies:\n");
+      OutputString("\nAll recursive dependencies\n");
 
     std::set<const Target*> all_deps;
     RecursiveCollectChildDeps(target, &all_deps);
@@ -147,7 +148,7 @@
     if (display_header) {
       OutputString(
           "\nDirect dependencies "
-          "(try also \"--all\", \"--tree\", or even \"--all --tree\"):\n");
+          "(try also \"--all\", \"--tree\", or even \"--all --tree\")\n");
     }
     for (const auto& pair : target->GetDeps(Target::DEPS_ALL))
       deps.push_back(pair.ptr);
@@ -185,7 +186,7 @@
 
 void PrintPublic(const Target* target, bool display_header) {
   if (display_header)
-    OutputString("\npublic:\n");
+    OutputString("\npublic\n");
 
   if (target->all_headers_public()) {
     OutputString("  [All headers listed in the sources are public.]\n");
@@ -200,7 +201,7 @@
 
 void PrintCheckIncludes(const Target* target, bool display_header) {
   if (display_header)
-    OutputString("\ncheck_includes:\n");
+    OutputString("\ncheck_includes\n");
 
   if (target->check_includes())
     OutputString("  true\n");
@@ -210,7 +211,7 @@
 
 void PrintAllowCircularIncludesFrom(const Target* target, bool display_header) {
   if (display_header)
-    OutputString("\nallow_circular_includes_from:\n");
+    OutputString("\nallow_circular_includes_from\n");
 
   Label toolchain_label = target->label().GetToolchainLabel();
   for (const auto& cur : target->allow_circular_includes_from())
@@ -219,14 +220,14 @@
 
 void PrintVisibility(const Target* target, bool display_header) {
   if (display_header)
-    OutputString("\nvisibility:\n");
+    OutputString("\nvisibility\n");
 
   OutputString(target->visibility().Describe(2, false));
 }
 
 void PrintTestonly(const Target* target, bool display_header) {
   if (display_header)
-    OutputString("\ntestonly:\n");
+    OutputString("\ntestonly\n");
 
   if (target->testonly())
     OutputString("  true\n");
@@ -251,7 +252,7 @@
 // This allows configs stored as either std::vector<LabelConfigPair> or
 // UniqueVector<LabelConfigPair> to be printed.
 template <class VectorType>
-void PrintConfigsVector(const Target* target,
+void PrintConfigsVector(const Item* item,
                         const VectorType& configs,
                         const std::string& heading,
                         bool display_header) {
@@ -263,12 +264,12 @@
   // Don't sort since the order determines how things are processed.
   if (display_header) {
     if (tree)
-      OutputString("\n" + heading + " tree (in order applying):\n");
+      OutputString("\n" + heading + " tree (in order applying)\n");
     else
-      OutputString("\n" + heading + " (in order applying, try also --tree):\n");
+      OutputString("\n" + heading + " (in order applying, try also --tree)\n");
   }
 
-  Label toolchain_label = target->label().GetToolchainLabel();
+  Label toolchain_label = item->label().GetToolchainLabel();
   for (const auto& config : configs) {
     OutputString("  " + config.label.GetUserVisibleName(toolchain_label) +
                  "\n");
@@ -282,6 +283,11 @@
                      display_header);
 }
 
+void PrintConfigs(const Config* config, bool display_header) {
+  PrintConfigsVector(config, config->configs().vector(), "configs",
+                     display_header);
+}
+
 void PrintPublicConfigs(const Target* target, bool display_header) {
   PrintConfigsVector(target, target->public_configs(),
                      "public_configs", display_header);
@@ -300,7 +306,7 @@
     return;
 
   if (display_header)
-    OutputString("\n" + header + ":\n");
+    OutputString("\n" + header + "\n");
 
   std::string indent = indent_extra ? "    " : "  ";
 
@@ -320,7 +326,7 @@
 
 void PrintOutputs(const Target* target, bool display_header) {
   if (display_header)
-    OutputString("\noutputs:\n");
+    OutputString("\noutputs\n");
 
   if (target->output_type() == Target::ACTION) {
     // Action, print out outputs, don't apply sources to it.
@@ -337,12 +343,12 @@
     if (!outputs.required_types().empty()) {
       // Display the pattern and resolved pattern separately, since there are
       // subtitutions used.
-      OutputString("  Output pattern:\n");
+      OutputString("  Output pattern\n");
       for (const auto& elem : outputs.list())
         OutputString("    " + elem.AsString() + "\n");
 
       // Now display what that resolves to given the sources.
-      OutputString("\n  Resolved output file list:\n");
+      OutputString("\n  Resolved output file list\n");
     }
 
     // Resolved output list.
@@ -355,13 +361,13 @@
 
 void PrintScript(const Target* target, bool display_header) {
   if (display_header)
-    OutputString("\nscript:\n");
+    OutputString("\nscript\n");
   OutputString("  " + target->action_values().script().value() + "\n");
 }
 
 void PrintArgs(const Target* target, bool display_header) {
   if (display_header)
-    OutputString("\nargs:\n");
+    OutputString("\nargs\n");
   for (const auto& elem : target->action_values().args().list()) {
     OutputString("  " + elem.AsString() + "\n");
   }
@@ -371,7 +377,7 @@
   if (target->action_values().depfile().empty())
     return;
   if (display_header)
-    OutputString("\ndepfile:\n");
+    OutputString("\ndepfile\n");
   OutputString("  " + target->action_values().depfile().AsString() + "\n");
 }
 
@@ -392,12 +398,22 @@
     out << "    " << str << "\n";
   }
 };
+
 template<> struct DescValueWriter<SourceDir> {
   void operator()(const SourceDir& dir, std::ostream& out) const {
     out << "    " << FormatSourceDir(dir) << "\n";
   }
 };
 
+template<> struct DescValueWriter<LibFile> {
+  void operator()(const LibFile& lib, std::ostream& out) const {
+    if (lib.is_source_file())
+      out << "    " << lib.source_file().value() << "\n";
+    else
+      out << "    " << lib.value() << "\n";
+  }
+};
+
 // Writes a given config value type to the string, optionally with attribution.
 // This should match RecursiveTargetConfigToStream in the order it traverses.
 template<typename T> void OutputRecursiveTargetConfig(
@@ -433,7 +449,26 @@
 
   std::string out_str = out.str();
   if (!out_str.empty()) {
-    OutputString("\n" + std::string(header_name) + "\n");
+    if (header_name)
+      OutputString("\n" + std::string(header_name) + "\n");
+    OutputString(out_str);
+  }
+}
+
+template<typename T> void OutputConfigValueArray(
+    const ConfigValues& values,
+    const char* header_name,
+    const std::vector<T>& (ConfigValues::* getter)() const) {
+  std::ostringstream out;
+
+  DescValueWriter<T> writer;
+  for (const T& cur : (values.*getter)())
+    writer(cur, out);
+
+  std::string out_str = out.str();
+  if (!out_str.empty()) {
+    if (header_name)
+      OutputString("\n" + std::string(header_name) + "\n");
     OutputString(out_str);
   }
 }
@@ -462,79 +497,295 @@
   }
 }
 
+// If "what" is empty, prints all PCH info. If "what" is nonempty, prints only
+// the things that match (if any). Returns true if anything was printed.
+bool PrintPrecompiledHeaderInfo(const ConfigValues& values,
+                                const std::string& what,
+                                bool display_headers) {
+  bool found_match = false;
+  if (what == variables::kPrecompiledHeader || what.empty()) {
+    if (!values.precompiled_header().empty()) {
+      if (display_headers)
+        OutputString("\nprecompiled_header\n");
+      OutputString(values.precompiled_header() + "\n");
+    }
+    found_match = true;
+  }
+  if (what == variables::kPrecompiledSource || what.empty()) {
+    if (!values.precompiled_source().is_null()) {
+      if (display_headers)
+        OutputString("\nprecompiled_source\n");
+      OutputString(values.precompiled_source().value() + "\n");
+    }
+    found_match = true;
+  }
+  return found_match;
+}
+
+bool PrintTarget(const Target* target,
+                 const std::string& what,
+                 bool display_target_header) {
+  if (display_target_header) {
+    OutputString("Target: ", DECORATION_YELLOW);
+    OutputString(target->label().GetUserVisibleName(false) + "\n");
+    OutputString("Type: ", DECORATION_YELLOW);
+    OutputString(std::string(
+        Target::GetStringForOutputType(target->output_type())) + "\n");
+    OutputString("Toolchain: ", DECORATION_YELLOW);
+    OutputString(
+        target->label().GetToolchainLabel().GetUserVisibleName(false) + "\n");
+  }
+
+  // Display headers when outputting everything.
+  bool display_headers = what.empty();
+  bool is_binary_output = target->IsBinary();
+
+  bool found_match = false;
+
+  // General target meta variables.
+  if (what.empty() || what == variables::kVisibility) {
+    PrintVisibility(target, display_headers);
+    found_match = true;
+  }
+  if (what.empty() || what == variables::kTestonly) {
+    PrintTestonly(target, display_headers);
+    found_match  = true;
+  }
+
+  // Binary target meta variables.
+  if (is_binary_output) {
+    if (what.empty() || what == variables::kCheckIncludes) {
+      PrintCheckIncludes(target, display_headers);
+      found_match = true;
+    }
+    if (what.empty() || what == variables::kAllowCircularIncludesFrom) {
+      PrintAllowCircularIncludesFrom(target, display_headers);
+      found_match = true;
+    }
+  }
+
+  // Sources and inputs.
+  if (what.empty() || what == variables::kSources) {
+    PrintSources(target, display_headers);
+    found_match = true;
+  }
+  if (what.empty() || what == variables::kPublic) {
+    PrintPublic(target, display_headers);
+    found_match = true;
+  }
+  if (what.empty() || what == variables::kInputs) {
+    PrintInputs(target, display_headers);
+    found_match = true;
+  }
+
+  // Configs. Configs set directly on a target are only relevant for binary
+  // targets
+  if (is_binary_output && (what.empty() || what == variables::kConfigs)) {
+    PrintConfigs(target, display_headers);
+    found_match = true;
+  }
+
+  // Dependent/public configs can be applied to anything.
+  if (what.empty() || what == variables::kPublicConfigs) {
+    PrintPublicConfigs(target, display_headers);
+    found_match = true;
+  }
+  if (what.empty() || what == variables::kAllDependentConfigs) {
+    PrintAllDependentConfigs(target, display_headers);
+    found_match = true;
+  }
+
+  // Action values.
+  if (target->output_type() == Target::ACTION ||
+      target->output_type() == Target::ACTION_FOREACH) {
+    if (what.empty() || what == variables::kScript) {
+      PrintScript(target, display_headers);
+      found_match = true;
+    }
+    if (what.empty() || what == variables::kArgs) {
+      PrintArgs(target, display_headers);
+      found_match = true;
+    }
+    if (what.empty() || what == variables::kDepfile) {
+      PrintDepfile(target, display_headers);
+      found_match = true;
+    }
+  }
+
+  // Outputs.
+  if (target->output_type() == Target::ACTION ||
+      target->output_type() == Target::ACTION_FOREACH ||
+      target->output_type() == Target::COPY_FILES ||
+      target->output_type() == Target::CREATE_BUNDLE) {
+    if (what.empty() || what == variables::kOutputs) {
+      PrintOutputs(target, display_headers);
+      found_match = true;
+    }
+  }
+
+  // Values from configs only apply to binary targets.
+  if (is_binary_output) {
+    #define CONFIG_VALUE_ARRAY_HANDLER(name, type) \
+      if (what.empty() || what == #name) { \
+        OutputRecursiveTargetConfig<type>( \
+            target, display_headers ? #name : nullptr, &ConfigValues::name); \
+        found_match = true; \
+      }
+
+    CONFIG_VALUE_ARRAY_HANDLER(arflags, std::string)
+    CONFIG_VALUE_ARRAY_HANDLER(asmflags, std::string)
+    CONFIG_VALUE_ARRAY_HANDLER(cflags, std::string)
+    CONFIG_VALUE_ARRAY_HANDLER(cflags_c, std::string)
+    CONFIG_VALUE_ARRAY_HANDLER(cflags_cc, std::string)
+    CONFIG_VALUE_ARRAY_HANDLER(cflags_objc, std::string)
+    CONFIG_VALUE_ARRAY_HANDLER(cflags_objcc, std::string)
+    CONFIG_VALUE_ARRAY_HANDLER(defines, std::string)
+    CONFIG_VALUE_ARRAY_HANDLER(include_dirs, SourceDir)
+    CONFIG_VALUE_ARRAY_HANDLER(ldflags, std::string)
+    // Libs and lib_dirs are handled specially below.
+
+    #undef CONFIG_VALUE_ARRAY_HANDLER
+
+    found_match |= PrintPrecompiledHeaderInfo(target->config_values(),
+                                              what, display_headers);
+  }
+
+  // Deps
+  if (what.empty() || what == "deps") {
+    PrintDeps(target, display_headers);
+    found_match = true;
+  }
+
+  // Runtime deps are special, print only when explicitly asked for and not in
+  // overview mode.
+  if (what == "runtime_deps") {
+    PrintRuntimeDeps(target);
+    found_match = true;
+  }
+
+  // Libs can be part of any target and get recursively pushed up the chain,
+  // so display them regardless of target type.
+  if (what.empty() || what == variables::kLibs) {
+    PrintLibs(target, display_headers);
+    found_match = true;
+  }
+  if (what.empty() || what == variables::kLibDirs) {
+    PrintLibDirs(target, display_headers);
+    found_match = true;
+  }
+
+  if (!found_match) {
+    OutputString("Don't know how to display \"" + what + "\" for \"" +
+        Target::GetStringForOutputType(target->output_type()) + "\".\n");
+    return false;
+  }
+  return true;
+}
+
+bool PrintConfig(const Config* config,
+                 const std::string& what,
+                 bool display_config_header) {
+  const ConfigValues& values = config->resolved_values();
+
+  if (display_config_header) {
+    OutputString("Config: ", DECORATION_YELLOW);
+    OutputString(config->label().GetUserVisibleName(false) + "\n");
+    OutputString("Toolchain: ", DECORATION_YELLOW);
+    OutputString(
+        config->label().GetToolchainLabel().GetUserVisibleName(false) + "\n");
+    if (what.empty() && !config->configs().empty()) {
+      OutputString(
+          "(This is a composite config, the values below are after the\n"
+          "expansion of the child configs.)\n");
+    }
+  }
+
+  // Display headers when outputting everything.
+  bool display_headers = what.empty();
+
+  if (what.empty() || what == variables::kConfigs)
+    PrintConfigs(config, display_headers);
+
+#define CONFIG_VALUE_ARRAY_HANDLER(name, type) \
+  if (what.empty() || what == #name) { \
+    OutputConfigValueArray<type>(values, display_headers ? #name : nullptr, \
+                                 &ConfigValues::name); \
+    found_match = true; \
+  }
+
+  bool found_match = false;
+
+  CONFIG_VALUE_ARRAY_HANDLER(arflags, std::string)
+  CONFIG_VALUE_ARRAY_HANDLER(asmflags, std::string)
+  CONFIG_VALUE_ARRAY_HANDLER(cflags, std::string)
+  CONFIG_VALUE_ARRAY_HANDLER(cflags_c, std::string)
+  CONFIG_VALUE_ARRAY_HANDLER(cflags_cc, std::string)
+  CONFIG_VALUE_ARRAY_HANDLER(cflags_objc, std::string)
+  CONFIG_VALUE_ARRAY_HANDLER(cflags_objcc, std::string)
+  CONFIG_VALUE_ARRAY_HANDLER(defines, std::string)
+  CONFIG_VALUE_ARRAY_HANDLER(include_dirs, SourceDir)
+  CONFIG_VALUE_ARRAY_HANDLER(ldflags, std::string)
+  CONFIG_VALUE_ARRAY_HANDLER(lib_dirs, SourceDir)
+  CONFIG_VALUE_ARRAY_HANDLER(libs, LibFile)
+
+#undef CONFIG_VALUE_ARRAY_HANDLER
+
+  // Handles all PCH-related variables.
+  found_match |= PrintPrecompiledHeaderInfo(config->resolved_values(),
+                                            what, display_headers);
+
+  if (!found_match) {
+    OutputString("Don't know how to display \"" + what + "\" for a config.\n");
+    return false;
+  }
+  return true;
+}
+
 }  // namespace
 
 // desc ------------------------------------------------------------------------
 
 const char kDesc[] = "desc";
 const char kDesc_HelpShort[] =
-    "desc: Show lots of insightful information about a target.";
+    "desc: Show lots of insightful information about a target or config.";
 const char kDesc_Help[] =
-    "gn desc <out_dir> <target label> [<what to show>] [--blame]\n"
+    "gn desc <out_dir> <label or pattern> [<what to show>] [--blame]\n"
     "\n"
-    "  Displays information about a given labeled target for the given build.\n"
-    "  The build parameters will be taken for the build in the given\n"
-    "  <out_dir>.\n"
+    "  Displays information about a given target or config. The build\n"
+    "  build parameters will be taken for the build in the given <out_dir>.\n"
+    "\n"
+    "  The <label or pattern> can be a target label, a config label, or a\n"
+    "  label pattern (see \"gn help label_pattern\"). A label pattern will\n"
+    "  only match targets.\n"
     "\n"
     "Possibilities for <what to show>\n"
+    "\n"
     "  (If unspecified an overall summary will be displayed.)\n"
     "\n"
-    "  sources\n"
-    "      Source files.\n"
-    "\n"
-    "  inputs\n"
-    "      Additional input dependencies.\n"
-    "\n"
-    "  public\n"
-    "      Public header files.\n"
-    "\n"
-    "  check_includes\n"
-    "      Whether \"gn check\" checks this target for include usage.\n"
-    "\n"
-    "  allow_circular_includes_from\n"
-    "      Permit includes from these targets.\n"
-    "\n"
-    "  visibility\n"
-    "      Prints which targets can depend on this one.\n"
-    "\n"
-    "  testonly\n"
-    "      Whether this target may only be used in tests.\n"
-    "\n"
-    "  configs\n"
-    "      Shows configs applied to the given target, sorted in the order\n"
-    "      they're specified. This includes both configs specified in the\n"
-    "      \"configs\" variable, as well as configs pushed onto this target\n"
-    "      via dependencies specifying \"all\" or \"direct\" dependent\n"
-    "      configs.\n"
-    "\n"
-    "  deps\n"
-    "      Show immediate or recursive dependencies. See below for flags that\n"
-    "      control deps printing.\n"
-    "\n"
-    "  public_configs\n"
     "  all_dependent_configs\n"
-    "      Shows the labels of configs applied to targets that depend on this\n"
-    "      one (either directly or all of them).\n"
-    "\n"
-    "  script\n"
+    "  allow_circular_includes_from\n"
+    "  arflags [--blame]\n"
     "  args\n"
+    "  cflags [--blame]\n"
+    "  cflags_cc [--blame]\n"
+    "  cflags_cxx [--blame]\n"
+    "  check_includes\n"
+    "  configs [--tree] (see below)\n"
+    "  defines [--blame]\n"
     "  depfile\n"
-    "      Actions only. The script and related values.\n"
-    "\n"
-    "  outputs\n"
-    "      Outputs for script and copy target types.\n"
-    "\n"
-    "  arflags       [--blame]\n"
-    "  defines       [--blame]\n"
-    "  include_dirs  [--blame]\n"
-    "  cflags        [--blame]\n"
-    "  cflags_cc     [--blame]\n"
-    "  cflags_cxx    [--blame]\n"
-    "  ldflags       [--blame]\n"
+    "  deps [--all] [--tree] (see below)\n"
+    "  include_dirs [--blame]\n"
+    "  inputs\n"
+    "  ldflags [--blame]\n"
     "  lib_dirs\n"
     "  libs\n"
-    "      Shows the given values taken from the target and all configs\n"
-    "      applying. See \"--blame\" below.\n"
+    "  outputs\n"
+    "  public_configs\n"
+    "  public\n"
+    "  script\n"
+    "  sources\n"
+    "  testonly\n"
+    "  visibility\n"
     "\n"
     "  runtime_deps\n"
     "      Compute all runtime deps for the given target. This is a\n"
@@ -548,13 +799,32 @@
     "\n"
     "Shared flags\n"
     "\n"
-    "  --blame\n"
-    "      Used with any value specified by a config, this will name\n"
-    "      the config that specified the value. This doesn't currently work\n"
-    "      for libs and lib_dirs because those are inherited and are more\n"
-    "      complicated to figure out the blame (patches welcome).\n"
+    ALL_TOOLCHAINS_SWITCH_HELP
     "\n"
-    "Flags that control how deps are printed\n"
+    "Target flags\n"
+    "\n"
+    "  --blame\n"
+    "      Used with any value specified on a config, this will name\n"
+    "      the config that cause that target to get the flag. This doesn't\n"
+    "      currently work for libs and lib_dirs because those are inherited\n"
+    "      and are more complicated to figure out the blame (patches\n"
+    "      welcome).\n"
+    "\n"
+    "Configs\n"
+    "\n"
+    "  The \"configs\" section will list all configs that apply. For targets\n"
+    "  this will include configs specified in the \"configs\" variable of\n"
+    "  the target, and also configs pushed onto this target via public\n"
+    "  or \"all dependent\" configs.\n"
+    "\n"
+    "  Configs can have child configs. Specifying --tree will show the\n"
+    "  hierarchy.\n"
+    "\n"
+    "Printing deps\n"
+    "\n"
+    "  Deps will include all public, private, and data deps (TODO this could\n"
+    "  be clarified and enhanced) sorted in order applying. The following\n"
+    "  may be used:\n"
     "\n"
     "  --all\n"
     "      Collects all recursive dependencies and prints a sorted flat list.\n"
@@ -598,9 +868,6 @@
     "      Shows defines set for the //base:base target, annotated by where\n"
     "      each one was set from.\n";
 
-#define OUTPUT_CONFIG_VALUE(name, type) \
-    OutputRecursiveTargetConfig<type>(target, #name, &ConfigValues::name);
-
 int RunDesc(const std::vector<std::string>& args) {
   if (args.size() != 2 && args.size() != 3) {
     Err(Location(), "You're holding it wrong.",
@@ -608,6 +875,7 @@
         .PrintToStdout();
     return 1;
   }
+  const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
 
   // Deliberately leaked to avoid expensive process teardown.
   Setup* setup = new Setup;
@@ -617,152 +885,47 @@
   if (!setup->Run())
     return 1;
 
-  const Target* target = ResolveTargetFromCommandLineString(setup, args[1]);
-  if (!target)
+  // Resolve target(s) and config from inputs.
+  UniqueVector<const Target*> target_matches;
+  UniqueVector<const Config*> config_matches;
+  UniqueVector<const Toolchain*> toolchain_matches;
+  UniqueVector<SourceFile> file_matches;
+
+  std::vector<std::string> target_list;
+  target_list.push_back(args[1]);
+
+  if (!ResolveFromCommandLineInput(
+          setup, target_list, cmdline->HasSwitch(switches::kAllToolchains),
+          &target_matches, &config_matches, &toolchain_matches, &file_matches))
     return 1;
 
-#define CONFIG_VALUE_HANDLER(name, type) \
-    } else if (what == #name) { OUTPUT_CONFIG_VALUE(name, type)
+  std::string what_to_print;
+  if (args.size() == 3)
+    what_to_print = args[2];
 
-  if (args.size() == 3) {
-    // User specified one thing to display.
-    const std::string& what = args[2];
-    if (what == variables::kConfigs) {
-      PrintConfigs(target, false);
-    } else if (what == variables::kPublicConfigs) {
-      PrintPublicConfigs(target, false);
-    } else if (what == variables::kAllDependentConfigs) {
-      PrintAllDependentConfigs(target, false);
-    } else if (what == variables::kSources) {
-      PrintSources(target, false);
-    } else if (what == variables::kPublic) {
-      PrintPublic(target, false);
-    } else if (what == variables::kCheckIncludes) {
-      PrintCheckIncludes(target, false);
-    } else if (what == variables::kAllowCircularIncludesFrom) {
-      PrintAllowCircularIncludesFrom(target, false);
-    } else if (what == variables::kVisibility) {
-      PrintVisibility(target, false);
-    } else if (what == variables::kTestonly) {
-      PrintTestonly(target, false);
-    } else if (what == variables::kInputs) {
-      PrintInputs(target, false);
-    } else if (what == variables::kScript) {
-      PrintScript(target, false);
-    } else if (what == variables::kArgs) {
-      PrintArgs(target, false);
-    } else if (what == variables::kDepfile) {
-      PrintDepfile(target, false);
-    } else if (what == variables::kOutputs) {
-      PrintOutputs(target, false);
-    } else if (what == variables::kDeps) {
-      PrintDeps(target, false);
-    } else if (what == variables::kLibDirs) {
-      PrintLibDirs(target, false);
-    } else if (what == variables::kLibs) {
-      PrintLibs(target, false);
-    } else if (what == "runtime_deps") {
-      PrintRuntimeDeps(target);
-//  }  Hidden closing brace in macro below.
+  bool multiple_outputs = (target_matches.size() + config_matches.size()) > 1;
 
-    CONFIG_VALUE_HANDLER(defines, std::string)
-    CONFIG_VALUE_HANDLER(include_dirs, SourceDir)
-    CONFIG_VALUE_HANDLER(arflags, std::string)
-    CONFIG_VALUE_HANDLER(asmflags, std::string)
-    CONFIG_VALUE_HANDLER(cflags, std::string)
-    CONFIG_VALUE_HANDLER(cflags_c, std::string)
-    CONFIG_VALUE_HANDLER(cflags_cc, std::string)
-    CONFIG_VALUE_HANDLER(cflags_objc, std::string)
-    CONFIG_VALUE_HANDLER(cflags_objcc, std::string)
-    CONFIG_VALUE_HANDLER(ldflags, std::string)
+  // Display headers for each target when printing all values, or when printing
+  // multiple targets or configs.
+  bool display_item_header = multiple_outputs || what_to_print.empty();
 
-    } else {
-      OutputString("Don't know how to display \"" + what + "\".\n");
+  bool printed_output = false;
+  for (const Target* target : target_matches) {
+    if (printed_output)
+      OutputString("\n\n");
+    printed_output = true;
+
+    if (!PrintTarget(target, what_to_print, display_item_header))
       return 1;
-    }
-
-#undef CONFIG_VALUE_HANDLER
-    return 0;
   }
+  for (const Config* config : config_matches) {
+    if (printed_output)
+      OutputString("\n\n");
+    printed_output = true;
 
-  // Display summary.
-
-  // Display this only applicable to binary targets.
-  bool is_binary_output =
-    target->output_type() != Target::GROUP &&
-    target->output_type() != Target::COPY_FILES &&
-    target->output_type() != Target::ACTION &&
-    target->output_type() != Target::ACTION_FOREACH &&
-    target->output_type() != Target::BUNDLE_DATA &&
-    target->output_type() != Target::CREATE_BUNDLE;
-
-  // Generally we only want to display toolchains on labels when the toolchain
-  // is different than the default one for this target (which we always print
-  // in the header).
-  Label target_toolchain = target->label().GetToolchainLabel();
-
-  // Header.
-  OutputString("Target: ", DECORATION_YELLOW);
-  OutputString(target->label().GetUserVisibleName(false) + "\n");
-  OutputString("Type: ", DECORATION_YELLOW);
-  OutputString(std::string(
-      Target::GetStringForOutputType(target->output_type())) + "\n");
-  OutputString("Toolchain: ", DECORATION_YELLOW);
-  OutputString(target_toolchain.GetUserVisibleName(false) + "\n");
-
-  PrintSources(target, true);
-  if (is_binary_output) {
-    PrintPublic(target, true);
-    PrintCheckIncludes(target, true);
-    PrintAllowCircularIncludesFrom(target, true);
+    if (!PrintConfig(config, what_to_print, display_item_header))
+      return 1;
   }
-  PrintVisibility(target, true);
-  if (is_binary_output) {
-    PrintTestonly(target, true);
-    PrintConfigs(target, true);
-  }
-
-  PrintPublicConfigs(target, true);
-  PrintAllDependentConfigs(target, true);
-
-  PrintInputs(target, true);
-
-  if (is_binary_output) {
-    OUTPUT_CONFIG_VALUE(defines, std::string)
-    OUTPUT_CONFIG_VALUE(include_dirs, SourceDir)
-    OUTPUT_CONFIG_VALUE(asmflags, std::string)
-    OUTPUT_CONFIG_VALUE(cflags, std::string)
-    OUTPUT_CONFIG_VALUE(cflags_c, std::string)
-    OUTPUT_CONFIG_VALUE(cflags_cc, std::string)
-    OUTPUT_CONFIG_VALUE(cflags_objc, std::string)
-    OUTPUT_CONFIG_VALUE(cflags_objcc, std::string)
-
-    if (target->output_type() == Target::STATIC_LIBRARY)
-      OUTPUT_CONFIG_VALUE(arflags, std::string)
-    else if (target->output_type() != Target::SOURCE_SET)
-      OUTPUT_CONFIG_VALUE(ldflags, std::string)
-  }
-
-  if (target->output_type() == Target::ACTION ||
-      target->output_type() == Target::ACTION_FOREACH) {
-    PrintScript(target, true);
-    PrintArgs(target, true);
-    PrintDepfile(target, true);
-  }
-
-  if (target->output_type() == Target::ACTION ||
-      target->output_type() == Target::ACTION_FOREACH ||
-      target->output_type() == Target::COPY_FILES ||
-      target->output_type() == Target::CREATE_BUNDLE) {
-    PrintOutputs(target, true);
-  }
-
-  // Libs can be part of any target and get recursively pushed up the chain,
-  // so always display them, even for groups and such.
-  PrintLibs(target, true);
-  PrintLibDirs(target, true);
-
-  PrintDeps(target, true);
 
   return 0;
 }
diff --git a/tools/gn/command_ls.cc b/tools/gn/command_ls.cc
index eddb51c..c9b9672 100644
--- a/tools/gn/command_ls.cc
+++ b/tools/gn/command_ls.cc
@@ -10,6 +10,7 @@
 #include "tools/gn/label_pattern.h"
 #include "tools/gn/setup.h"
 #include "tools/gn/standard_out.h"
+#include "tools/gn/switches.h"
 #include "tools/gn/target.h"
 
 namespace commands {
@@ -34,11 +35,7 @@
     "\n"
     TARGET_PRINTING_MODE_COMMAND_LINE_HELP
     "\n"
-    "  --all-toolchains\n"
-    "      Matches all toolchains. When set, if the label pattern does not\n"
-    "      specify an explicit toolchain, labels from all toolchains will be\n"
-    "      matched. When unset, only targets in the default toolchain will\n"
-    "      be matched unless an explicit toolchain in the label is set.\n"
+    ALL_TOOLCHAINS_SWITCH_HELP
     "\n"
     TARGET_TESTONLY_FILTER_COMMAND_LINE_HELP
     "\n"
@@ -81,7 +78,7 @@
     return 1;
 
   const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
-  bool all_toolchains = cmdline->HasSwitch("all-toolchains");
+  bool all_toolchains = cmdline->HasSwitch(switches::kAllToolchains);
 
   std::vector<const Target*> matches;
   if (args.size() > 1) {
diff --git a/tools/gn/command_refs.cc b/tools/gn/command_refs.cc
index 821fd88..413a98f 100644
--- a/tools/gn/command_refs.cc
+++ b/tools/gn/command_refs.cc
@@ -17,6 +17,7 @@
 #include "tools/gn/item.h"
 #include "tools/gn/setup.h"
 #include "tools/gn/standard_out.h"
+#include "tools/gn/switches.h"
 #include "tools/gn/target.h"
 
 namespace commands {
@@ -315,16 +316,7 @@
     "\n"
     "      When used with --tree, turns off eliding to show a complete tree.\n"
     "\n"
-    "  --all-toolchains\n"
-    "      Normally only inputs in the default toolchain will be included.\n"
-    "      This switch will turn on matching all toolchains.\n"
-    "\n"
-    "      For example, a file is in a target might be compiled twice:\n"
-    "      once in the default toolchain and once in a secondary one. Without\n"
-    "      this flag, only the default toolchain one will be matched and\n"
-    "      printed (potentially with its recursive dependencies, depending on\n"
-    "      the other options). With this flag, both will be printed\n"
-    "      (potentially with both of their recursive dependencies).\n"
+    ALL_TOOLCHAINS_SWITCH_HELP
     "\n"
     TARGET_PRINTING_MODE_COMMAND_LINE_HELP
     "\n"
@@ -397,7 +389,7 @@
   const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
   bool tree = cmdline->HasSwitch("tree");
   bool all = cmdline->HasSwitch("all");
-  bool all_toolchains = cmdline->HasSwitch("all-toolchains");
+  bool all_toolchains = cmdline->HasSwitch(switches::kAllToolchains);
 
   Setup* setup = new Setup;
   setup->build_settings().set_check_for_bad_items(false);
diff --git a/tools/gn/config_values.h b/tools/gn/config_values.h
index b24ae21..af97c2b 100644
--- a/tools/gn/config_values.h
+++ b/tools/gn/config_values.h
@@ -30,9 +30,10 @@
     const std::vector<SourceDir>& name() const { return name##_; } \
     std::vector<SourceDir>& name() { return name##_; }
 
-  // ==================================================================
-  // IMPORTANT: If you add a new one, be sure to update AppendValues().
-  // ==================================================================
+  // =================================================================
+  // IMPORTANT: If you add a new one, be sure to update AppendValues()
+  //            and command_desc.cc.
+  // =================================================================
   STRING_VALUES_ACCESSOR(arflags)
   STRING_VALUES_ACCESSOR(asmflags)
   STRING_VALUES_ACCESSOR(cflags)
@@ -44,9 +45,10 @@
   DIR_VALUES_ACCESSOR   (include_dirs)
   STRING_VALUES_ACCESSOR(ldflags)
   DIR_VALUES_ACCESSOR   (lib_dirs)
-  // ==================================================================
-  // IMPORTANT: If you add a new one, be sure to update AppendValues().
-  // ==================================================================
+  // =================================================================
+  // IMPORTANT: If you add a new one, be sure to update AppendValues()
+  //            and command_desc.cc.
+  // =================================================================
 
 #undef STRING_VALUES_ACCESSOR
 #undef DIR_VALUES_ACCESSOR
diff --git a/tools/gn/docs/reference.md b/tools/gn/docs/reference.md
index 7b345d7..6216791 100644
--- a/tools/gn/docs/reference.md
+++ b/tools/gn/docs/reference.md
@@ -436,74 +436,47 @@
 
 
 ```
-## **gn desc <out_dir> <target label> [<what to show>] [\--blame]**
+## **gn desc <out_dir> <label or pattern> [<what to show>] [\--blame]**
 
 ```
-  Displays information about a given labeled target for the given build.
-  The build parameters will be taken for the build in the given
-  <out_dir>.
+  Displays information about a given target or config. The build
+  build parameters will be taken for the build in the given <out_dir>.
+
+  The <label or pattern> can be a target label, a config label, or a
+  label pattern (see "gn help label_pattern"). A label pattern will
+  only match targets.
 
 ```
 
 ### **Possibilities for <what to show>**
+
 ```
   (If unspecified an overall summary will be displayed.)
 
-  sources
-      Source files.
-
-  inputs
-      Additional input dependencies.
-
-  public
-      Public header files.
-
-  check_includes
-      Whether "gn check" checks this target for include usage.
-
-  allow_circular_includes_from
-      Permit includes from these targets.
-
-  visibility
-      Prints which targets can depend on this one.
-
-  testonly
-      Whether this target may only be used in tests.
-
-  configs
-      Shows configs applied to the given target, sorted in the order
-      they're specified. This includes both configs specified in the
-      "configs" variable, as well as configs pushed onto this target
-      via dependencies specifying "all" or "direct" dependent
-      configs.
-
-  deps
-      Show immediate or recursive dependencies. See below for flags that
-      control deps printing.
-
-  public_configs
   all_dependent_configs
-      Shows the labels of configs applied to targets that depend on this
-      one (either directly or all of them).
-
-  script
+  allow_circular_includes_from
+  arflags [--blame]
   args
+  cflags [--blame]
+  cflags_cc [--blame]
+  cflags_cxx [--blame]
+  check_includes
+  configs [--tree] (see below)
+  defines [--blame]
   depfile
-      Actions only. The script and related values.
-
-  outputs
-      Outputs for script and copy target types.
-
-  defines       [--blame]
-  include_dirs  [--blame]
-  cflags        [--blame]
-  cflags_cc     [--blame]
-  cflags_cxx    [--blame]
-  ldflags       [--blame]
+  deps [--all] [--tree] (see below)
+  include_dirs [--blame]
+  inputs
+  ldflags [--blame]
   lib_dirs
   libs
-      Shows the given values taken from the target and all configs
-      applying. See "--blame" below.
+  outputs
+  public_configs
+  public
+  script
+  sources
+  testonly
+  visibility
 
   runtime_deps
       Compute all runtime deps for the given target. This is a
@@ -520,17 +493,49 @@
 ### **Shared flags**
 
 ```
+  --all-toolchains
+      Normally only inputs in the default toolchain will be included.
+      This switch will turn on matching all toolchains.
+
+      For example, a file is in a target might be compiled twice:
+      once in the default toolchain and once in a secondary one. Without
+      this flag, only the default toolchain one will be matched by
+      wildcards. With this flag, both will be matched.
+
+```
+
+### **Target flags**
+
+```
   --blame
-      Used with any value specified by a config, this will name
-      the config that specified the value. This doesn't currently work
-      for libs and lib_dirs because those are inherited and are more
-      complicated to figure out the blame (patches welcome).
+      Used with any value specified on a config, this will name
+      the config that cause that target to get the flag. This doesn't
+      currently work for libs and lib_dirs because those are inherited
+      and are more complicated to figure out the blame (patches
+      welcome).
 
 ```
 
-### **Flags that control how deps are printed**
+### **Configs**
 
 ```
+  The "configs" section will list all configs that apply. For targets
+  this will include configs specified in the "configs" variable of
+  the target, and also configs pushed onto this target via public
+  or "all dependent" configs.
+
+  Configs can have child configs. Specifying --tree will show the
+  hierarchy.
+
+```
+
+### **Printing deps**
+
+```
+  Deps will include all public, private, and data deps (TODO this could
+  be clarified and enhanced) sorted in order applying. The following
+  may be used:
+
   --all
       Collects all recursive dependencies and prints a sorted flat list.
       Also usable with --tree (see below).
@@ -760,10 +765,13 @@
           root build directory.
 
   --all-toolchains
-      Matches all toolchains. When set, if the label pattern does not
-      specify an explicit toolchain, labels from all toolchains will be
-      matched. When unset, only targets in the default toolchain will
-      be matched unless an explicit toolchain in the label is set.
+      Normally only inputs in the default toolchain will be included.
+      This switch will turn on matching all toolchains.
+
+      For example, a file is in a target might be compiled twice:
+      once in the default toolchain and once in a secondary one. Without
+      this flag, only the default toolchain one will be matched by
+      wildcards. With this flag, both will be matched.
 
   --testonly=(true|false)
       Restrict outputs to targets with the testonly flag set
@@ -892,10 +900,8 @@
 
       For example, a file is in a target might be compiled twice:
       once in the default toolchain and once in a secondary one. Without
-      this flag, only the default toolchain one will be matched and
-      printed (potentially with its recursive dependencies, depending on
-      the other options). With this flag, both will be printed
-      (potentially with both of their recursive dependencies).
+      this flag, only the default toolchain one will be matched by
+      wildcards. With this flag, both will be matched.
 
   --as=(buildfile|label|output)
       How to print targets.
@@ -4164,9 +4170,16 @@
   In some cases the static library might be the final desired output.
   For example, you may be producing a static library for distribution to
   third parties. In this case, the static library should include code
-  for all dependencies in one complete package. Since GN does not unpack
-  static libraries to forward their contents up the dependency chain,
-  it is an error for complete static libraries to depend on other static
+  for all dependencies in one complete package. However, complete static
+  libraries themselves are never linked into other complete static
+  libraries. All complete static libraries are for distribution and
+  linking them in would cause code duplication in this case. If the
+  static library is not for distribution, it should not be complete.
+
+  GN treats non-complete static libraries as source sets when they are
+  linked into complete static libraries. This is done because some tools
+  like AR do not handle dependent static libraries properly. This makes
+  it easier to write "alink" rules.
 
   In rare cases it makes sense to list a header in more than one
   target if it could be considered conceptually a member of both.
@@ -4330,7 +4343,7 @@
   generated files both in the "outputs" list as well as the "data"
   list.
 
-  By convention, directories are be listed with a trailing slash:
+  By convention, directories are listed with a trailing slash:
     data = [ "test/data/" ]
   However, no verification is done on these so GN doesn't enforce this.
   The paths are just rebased and passed along when requested.
diff --git a/tools/gn/switches.cc b/tools/gn/switches.cc
index 45b25e6..48b191e 100644
--- a/tools/gn/switches.cc
+++ b/tools/gn/switches.cc
@@ -225,6 +225,8 @@
 // immediately if this switch is used.
 const char kVersion_Help[] = "";
 
+const char kAllToolchains[] = "all-toolchains";
+
 // -----------------------------------------------------------------------------
 
 SwitchInfo::SwitchInfo()
diff --git a/tools/gn/switches.h b/tools/gn/switches.h
index cebb19c..b86bf33 100644
--- a/tools/gn/switches.h
+++ b/tools/gn/switches.h
@@ -84,6 +84,20 @@
 extern const char kVersion_HelpShort[];
 extern const char kVersion_Help[];
 
+// This switch is used by several commands. It is here so it can be shared,
+// but it's documented in the individual commands it applies to rather than
+// globally.
+extern const char kAllToolchains[];
+#define ALL_TOOLCHAINS_SWITCH_HELP \
+  "  --all-toolchains\n" \
+  "      Normally only inputs in the default toolchain will be included.\n" \
+  "      This switch will turn on matching all toolchains.\n" \
+  "\n" \
+  "      For example, a file is in a target might be compiled twice:\n" \
+  "      once in the default toolchain and once in a secondary one. Without\n" \
+  "      this flag, only the default toolchain one will be matched by\n" \
+  "      wildcards. With this flag, both will be matched.\n"
+
 }  // namespace switches
 
 #endif  // TOOLS_GN_SWITCHES_H_