[metadata] Adding `meta` command

Change-Id: Ib2d436b647936cca989e0d14f387c1383ad5df57
Reviewed-on: https://gn-review.googlesource.com/c/3361
Commit-Queue: Julie Hockett <juliehockett@google.com>
Reviewed-by: Brett Wilson <brettw@chromium.org>
diff --git a/build/gen.py b/build/gen.py
index 82c15d1..b5c2fbe 100755
--- a/build/gen.py
+++ b/build/gen.py
@@ -421,6 +421,7 @@
         'tools/gn/command_format.cc',
         'tools/gn/command_gen.cc',
         'tools/gn/command_help.cc',
+        'tools/gn/command_meta.cc',
         'tools/gn/command_ls.cc',
         'tools/gn/command_path.cc',
         'tools/gn/command_refs.cc',
diff --git a/tools/gn/command_meta.cc b/tools/gn/command_meta.cc
new file mode 100644
index 0000000..f5f96b4
--- /dev/null
+++ b/tools/gn/command_meta.cc
@@ -0,0 +1,134 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <algorithm>
+#include <set>
+
+#include "base/command_line.h"
+#include "base/strings/string_split.h"
+#include "tools/gn/commands.h"
+#include "tools/gn/metadata_walk.h"
+#include "tools/gn/setup.h"
+#include "tools/gn/standard_out.h"
+#include "tools/gn/switches.h"
+#include "tools/gn/target.h"
+
+namespace commands {
+
+const char kMeta[] = "meta";
+const char kMeta_HelpShort[] = "meta: List target metadata collection results.";
+const char kMeta_Help[] =
+    R"(gn meta <out_dir> <target>* --data=<key>[,<key>*]* [--walk=<key>[,<key>*]*]
+       [--rebase]
+
+  Lists collected metaresults of all given targets for the given data key(s),
+  collecting metadata dependencies as specified by the given walk key(s).
+
+Examples
+
+  gn meta out/Debug "//base/foo" --data=files
+      Lists collected metaresults for the `files` key in the //base/foo:foo
+      target and all of its dependency tree.
+
+  gn meta out/Debug "//base/foo" --data=files --data=other
+      Lists collected metaresults for the `files` and `other` keys in the
+      //base/foo:foo target and all of its dependency tree.
+
+  gn meta out/Debug "//base/foo" --data=files --walk=stop
+      Lists collected metaresults for the `files` key in the //base/foo:foo
+      target and all of the dependencies listed in the `stop` key (and so on).
+
+  gn meta out/Debug "//base/foo" --data=files --rebase-files
+      Lists collected metaresults for the `files` key in the //base/foo:foo
+      target and all of its dependency tree, rebasing the strings in the `files`
+      key onto the source directory of the target's declaration.
+)";
+
+int RunMeta(const std::vector<std::string>& args) {
+  if (args.size() == 0) {
+    Err(Location(), "You're holding it wrong.",
+        "Usage: \"gn meta <out_dir> <target>* --data=<key>[,<key>*] "
+        "[--walk=<key>[,<key>*]*] [--rebase-files]\"")
+        .PrintToStdout();
+    return 1;
+  }
+
+  Setup* setup = new Setup;
+  if (!setup->DoSetup(args[0], false) || !setup->Run())
+    return 1;
+
+  const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
+  bool rebase_files = cmdline->HasSwitch(switches::kMetaRebaseFiles);
+  std::string data_keys_str =
+      cmdline->GetSwitchValueASCII(switches::kMetaDataKeys);
+  std::string walk_keys_str =
+      cmdline->GetSwitchValueASCII(switches::kMetaWalkKeys);
+
+  std::vector<std::string> inputs(args.begin() + 1, args.end());
+
+  UniqueVector<const Target*> targets;
+  for (const auto& input : inputs) {
+    const Target* target = ResolveTargetFromCommandLineString(setup, input);
+    if (!target) {
+      Err(Location(), "Unknown target " + input).PrintToStdout();
+      return 1;
+    }
+    targets.push_back(target);
+  }
+
+  std::vector<std::string> data_keys = base::SplitString(
+      data_keys_str, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+  if (data_keys.empty()) {
+    return 1;
+  }
+  std::vector<std::string> walk_keys = base::SplitString(
+      walk_keys_str, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+  Err err;
+  std::set<const Target*> targets_walked;
+  std::vector<Value> result = WalkMetadata(targets, data_keys, walk_keys,
+                                           rebase_files, &targets_walked, &err);
+  if (err.has_error()) {
+    err.PrintToStdout();
+    return 1;
+  }
+
+  OutputString("Metadata values\n", DECORATION_DIM);
+  for (const auto& value : result)
+    OutputString("\n" + value.ToString(false) + "\n");
+
+  // TODO(juliehockett): We should have better dep tracing and error support for
+  // this. Also possibly data about where different values came from.
+  OutputString("\nExtracted from:\n", DECORATION_DIM);
+  bool first = true;
+  for (const auto* target : targets_walked) {
+    if (!first) {
+      first = false;
+      OutputString(", ", DECORATION_DIM);
+    }
+    OutputString(target->label().GetUserVisibleName(true) + "\n");
+  }
+  OutputString("\nusing data keys:\n", DECORATION_DIM);
+  first = true;
+  for (const auto& key : data_keys) {
+    if (!first) {
+      first = false;
+      OutputString(", ");
+    }
+    OutputString(key + "\n");
+  }
+  if (!walk_keys.empty()) {
+    OutputString("\nand using walk keys:\n", DECORATION_DIM);
+    first = true;
+    for (const auto& key : walk_keys) {
+      if (!first) {
+        first = false;
+        OutputString(", ");
+      }
+      OutputString(key + "\n");
+    }
+  }
+  return 0;
+}
+
+}  // namespace commands
diff --git a/tools/gn/commands.cc b/tools/gn/commands.cc
index 7fb496d..b7cd871 100644
--- a/tools/gn/commands.cc
+++ b/tools/gn/commands.cc
@@ -386,6 +386,7 @@
     INSERT_COMMAND(Gen)
     INSERT_COMMAND(Format)
     INSERT_COMMAND(Help)
+    INSERT_COMMAND(Meta)
     INSERT_COMMAND(Ls)
     INSERT_COMMAND(Path)
     INSERT_COMMAND(Refs)
diff --git a/tools/gn/commands.h b/tools/gn/commands.h
index 7d6017e..71950e6 100644
--- a/tools/gn/commands.h
+++ b/tools/gn/commands.h
@@ -69,6 +69,11 @@
 extern const char kHelp_Help[];
 int RunHelp(const std::vector<std::string>& args);
 
+extern const char kMeta[];
+extern const char kMeta_HelpShort[];
+extern const char kMeta_Help[];
+int RunMeta(const std::vector<std::string>& args);
+
 extern const char kLs[];
 extern const char kLs_HelpShort[];
 extern const char kLs_Help[];
diff --git a/tools/gn/switches.cc b/tools/gn/switches.cc
index 1e0082c..f6a4704 100644
--- a/tools/gn/switches.cc
+++ b/tools/gn/switches.cc
@@ -111,6 +111,39 @@
   interpreter.
 )";
 
+const char kMetaDataKeys[] = "data";
+const char kMetaDataKeys_HelpShort[] =
+    "--data: list of data keys to concatenate when collecting metadata.";
+const char kMetaDataKeys_Help[] =
+    R"(--data: list of data keys to concatenate when collecting metadata.
+
+  Data keys identify which variables in the given targets' `metadata`
+  scopes should be collected. At least one data key must be specified.
+)";
+
+const char kMetaWalkKeys[] = "walk";
+const char kMetaWalkKeys_HelpShort[] =
+    "--walk: list of walk keys to traverse when collecting metadata.";
+const char kMetaWalkKeys_Help[] =
+    R"(--walk: list of walk keys to traverse when collecting metadata.
+
+  Walk keys identify which variables in the given targets' `metadata`
+  scopes contain the list of dependencies to walk next. Absence of any
+  walk keys indicates that all deps and data_deps should be walked.
+)";
+
+const char kMetaRebaseFiles[] = "rebase-files";
+const char kMetaRebaseFiles_HelpShort[] =
+    "--rebase-files (boolean): whether to rebase the paths of the collected "
+    "metadata.";
+const char kMetaRebaseFiles_Help[] =
+    R"(--rebase-files: whether to rebase the paths of the collected metadata.
+
+  This flag indicates whether or not to rebase the collected results onto their
+  declaring source directory path. Note that this requires the data key(s) to
+  contain only lists of strings, which will be interpreted as file names.
+)";
+
 const char kQuiet[] = "q";
 const char kQuiet_HelpShort[] =
     "-q: Quiet mode. Don't print output on success.";
diff --git a/tools/gn/switches.h b/tools/gn/switches.h
index 1dc9828..d1abb1f 100644
--- a/tools/gn/switches.h
+++ b/tools/gn/switches.h
@@ -47,6 +47,18 @@
 extern const char kMarkdown_HelpShort[];
 extern const char kMarkdown_Help[];
 
+extern const char kMetaDataKeys[];
+extern const char kMetaDataKeys_HelpShort[];
+extern const char kMetaDataKeys_Help[];
+
+extern const char kMetaWalkKeys[];
+extern const char kMetaWalkKeys_HelpShort[];
+extern const char kMetaWalkKeys_Help[];
+
+extern const char kMetaRebaseFiles[];
+extern const char kMetaRebaseFiles_HelpShort[];
+extern const char kMetaRebaseFiles_Help[];
+
 extern const char kNoColor[];
 extern const char kNoColor_HelpShort[];
 extern const char kNoColor_Help[];