[metadata] Adding basic metadata walking

Adds walk logic to the metadata field and the target.

Change-Id: I5946bd44c1b99f94e3be4a05547a7bdd2f14245b
Reviewed-on: https://gn-review.googlesource.com/c/3340
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 e32a609..f17a648 100755
--- a/build/gen.py
+++ b/build/gen.py
@@ -469,6 +469,7 @@
         'tools/gn/lib_file.cc',
         'tools/gn/loader.cc',
         'tools/gn/location.cc',
+        'tools/gn/metadata.cc',
         'tools/gn/ninja_action_target_writer.cc',
         'tools/gn/ninja_binary_target_writer.cc',
         'tools/gn/ninja_build_writer.cc',
diff --git a/tools/gn/metadata.cc b/tools/gn/metadata.cc
new file mode 100644
index 0000000..605294b
--- /dev/null
+++ b/tools/gn/metadata.cc
@@ -0,0 +1,67 @@
+// Copyright 2018 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 "tools/gn/metadata.h"
+
+bool Metadata::WalkStep(const BuildSettings* settings,
+                        const std::vector<std::string>& keys_to_extract,
+                        const std::vector<std::string>& keys_to_walk,
+                        bool rebase_files,
+                        std::vector<Value>* next_walk_keys,
+                        std::vector<Value>* result,
+                        Err* err) const {
+  // If there's no metadata, there's nothing to find, so quick exit.
+  if (contents_.empty()) {
+    next_walk_keys->emplace_back(nullptr, "");
+    return true;
+  }
+
+  // Pull the data from each specified key.
+  for (const auto& key : keys_to_extract) {
+    auto iter = contents_.find(key);
+    if (iter == contents_.end())
+      continue;
+    assert(iter->second.type() == Value::LIST);
+
+    if (rebase_files) {
+      for (const auto& val : iter->second.list_value()) {
+        if (!val.VerifyTypeIs(Value::STRING, err))
+          return false;
+        // TODO(juliehockett): Do we want to consider absolute paths here? In
+        // which case we'd need to propagate the root_path_utf8 from
+        // build_settings as well.
+        std::string filename = source_dir_.ResolveRelativeAs(
+            /*as_file = */ true, val, err, settings->root_path_utf8());
+        if (err->has_error())
+          return false;
+        result->emplace_back(val.origin(), std::move(filename));
+      }
+    } else {
+      result->insert(result->end(), iter->second.list_value().begin(),
+                    iter->second.list_value().end());
+    }
+  }
+
+  // Get the targets to look at next. If no keys_to_walk are present, we push
+  // the empty string to the list so that the target knows to include its deps
+  // and data_deps. The values used here must be lists of strings.
+  bool found_walk_key = false;
+  for (const auto& key : keys_to_walk) {
+    auto iter = contents_.find(key);
+    if (iter != contents_.end()) {
+      found_walk_key = true;
+      assert(iter->second.type() == Value::LIST);
+      for (const auto& val : iter->second.list_value()) {
+        if (!val.VerifyTypeIs(Value::STRING, err))
+          return false;
+        next_walk_keys->emplace_back(val);
+      }
+    }
+  }
+
+  if (!found_walk_key)
+    next_walk_keys->emplace_back(nullptr, "");
+
+  return true;
+}
diff --git a/tools/gn/metadata.h b/tools/gn/metadata.h
index fe66ff6..235542b 100644
--- a/tools/gn/metadata.h
+++ b/tools/gn/metadata.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 
+#include "tools/gn/build_settings.h"
 #include "tools/gn/scope.h"
 #include "tools/gn/source_dir.h"
 
@@ -44,6 +45,19 @@
   SourceDir& source_dir() { return source_dir_; }
   void set_source_dir(const SourceDir& d) { source_dir_ = d; }
 
+  // Collect the specified metadata from this instance.
+  //
+  // Calling this will populate `next_walk_keys` with the values of targets to
+  // be walked next (with the empty string "" indicating that the target should
+  // walk all of its deps and data_deps).
+  bool WalkStep(const BuildSettings* settings,
+                const std::vector<std::string>& keys_to_extract,
+                const std::vector<std::string>& keys_to_walk,
+                bool rebase_files,
+                std::vector<Value>* next_walk_keys,
+                std::vector<Value>* result,
+                Err* err) const;
+
  private:
   const ParseNode* origin_;
   Contents contents_;
diff --git a/tools/gn/metadata_unittest.cc b/tools/gn/metadata_unittest.cc
index cb94911..2fcebfa 100644
--- a/tools/gn/metadata_unittest.cc
+++ b/tools/gn/metadata_unittest.cc
@@ -30,3 +30,175 @@
   ASSERT_EQ(a_actual->second, a_expected);
   ASSERT_EQ(b_actual->second, b_expected);
 }
+
+TEST(MetadataTest, Walk) {
+  TestWithScope setup;
+  Metadata metadata;
+  metadata.set_source_dir(SourceDir("/usr/home/files/"));
+
+  Value a_expected(nullptr, Value::LIST);
+  a_expected.list_value().push_back(Value(nullptr, "foo.cpp"));
+  a_expected.list_value().push_back(Value(nullptr, "bar.h"));
+
+  metadata.contents().insert(
+      std::pair<base::StringPiece, Value>("a", a_expected));
+
+  std::vector<std::string> data_keys;
+  data_keys.emplace_back("a");
+  std::vector<std::string> walk_keys;
+  std::vector<Value> next_walk_keys;
+  std::vector<Value> results;
+
+  std::vector<Value> expected;
+  expected.emplace_back(Value(nullptr, "foo.cpp"));
+  expected.emplace_back(Value(nullptr, "bar.h"));
+
+  std::vector<Value> expected_walk_keys;
+  expected_walk_keys.emplace_back(nullptr, "");
+
+  Err err;
+  EXPECT_TRUE(metadata.WalkStep(setup.settings()->build_settings(), data_keys,
+                                walk_keys, false, &next_walk_keys, &results,
+                                &err));
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(next_walk_keys, expected_walk_keys);
+  EXPECT_EQ(results, expected);
+}
+
+TEST(MetadataTest, WalkWithRebase) {
+  TestWithScope setup;
+  Metadata metadata;
+  metadata.set_source_dir(SourceDir("/usr/home/files/"));
+
+  Value a_expected(nullptr, Value::LIST);
+  a_expected.list_value().push_back(Value(nullptr, "foo.cpp"));
+  a_expected.list_value().push_back(Value(nullptr, "foo/bar.h"));
+
+  metadata.contents().insert(
+      std::pair<base::StringPiece, Value>("a", a_expected));
+
+  std::vector<std::string> data_keys;
+  data_keys.emplace_back("a");
+  std::vector<std::string> walk_keys;
+  std::vector<Value> next_walk_keys;
+  std::vector<Value> results;
+
+  std::vector<Value> expected;
+  expected.emplace_back(Value(nullptr, "/usr/home/files/foo.cpp"));
+  expected.emplace_back(Value(nullptr, "/usr/home/files/foo/bar.h"));
+
+  std::vector<Value> expected_walk_keys;
+  expected_walk_keys.emplace_back(nullptr, "");
+
+  Err err;
+  EXPECT_TRUE(metadata.WalkStep(setup.settings()->build_settings(), data_keys,
+                                walk_keys, true, &next_walk_keys, &results,
+                                &err));
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(next_walk_keys, expected_walk_keys);
+  EXPECT_EQ(results, expected);
+}
+
+TEST(MetadataTest, WalkWithRebaseError) {
+  TestWithScope setup;
+  Metadata metadata;
+  metadata.set_source_dir(SourceDir("/usr/home/files/"));
+
+  Value a_expected(nullptr, Value::LIST);
+  a_expected.list_value().push_back(Value(nullptr, "foo.cpp"));
+  a_expected.list_value().push_back(Value(nullptr, true));
+
+  metadata.contents().insert(
+      std::pair<base::StringPiece, Value>("a", a_expected));
+
+  std::vector<std::string> data_keys;
+  data_keys.emplace_back("a");
+  std::vector<std::string> walk_keys;
+  std::vector<Value> next_walk_keys;
+  std::vector<Value> results;
+
+  Err err;
+  EXPECT_FALSE(metadata.WalkStep(setup.settings()->build_settings(), data_keys,
+                                 walk_keys, true, &next_walk_keys, &results,
+                                 &err));
+  EXPECT_TRUE(err.has_error());
+}
+
+TEST(MetadataTest, WalkKeysToWalk) {
+  TestWithScope setup;
+  Metadata metadata;
+  metadata.set_source_dir(SourceDir("/usr/home/files/"));
+
+  Value a_expected(nullptr, Value::LIST);
+  a_expected.list_value().push_back(Value(nullptr, "//target"));
+
+  metadata.contents().insert(
+      std::pair<base::StringPiece, Value>("a", a_expected));
+
+  std::vector<std::string> data_keys;
+  std::vector<std::string> walk_keys;
+  walk_keys.emplace_back("a");
+  std::vector<Value> next_walk_keys;
+  std::vector<Value> results;
+
+  std::vector<Value> expected_walk_keys;
+  expected_walk_keys.emplace_back(nullptr, "//target");
+
+  Err err;
+  EXPECT_TRUE(metadata.WalkStep(setup.settings()->build_settings(), data_keys,
+                                walk_keys, true, &next_walk_keys, &results,
+                                &err));
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(next_walk_keys, expected_walk_keys);
+  EXPECT_TRUE(results.empty());
+}
+
+TEST(MetadataTest, WalkNoContents) {
+  TestWithScope setup;
+  Metadata metadata;
+  metadata.set_source_dir(SourceDir("/usr/home/files/"));
+
+  std::vector<std::string> data_keys;
+  std::vector<std::string> walk_keys;
+  std::vector<Value> next_walk_keys;
+  std::vector<Value> results;
+
+  std::vector<Value> expected_walk_keys;
+  expected_walk_keys.emplace_back(nullptr, "");
+
+  Err err;
+  EXPECT_TRUE(metadata.WalkStep(setup.settings()->build_settings(), data_keys,
+                                walk_keys, true, &next_walk_keys, &results,
+                                &err));
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(next_walk_keys, expected_walk_keys);
+  EXPECT_TRUE(results.empty());
+}
+
+TEST(MetadataTest, WalkNoKeysWithContents) {
+  TestWithScope setup;
+  Metadata metadata;
+  metadata.set_source_dir(SourceDir("/usr/home/files/"));
+
+  Value a_expected(nullptr, Value::LIST);
+  a_expected.list_value().push_back(Value(nullptr, "//target"));
+
+  metadata.contents().insert(
+      std::pair<base::StringPiece, Value>("a", a_expected));
+
+  std::vector<std::string> data_keys;
+  std::vector<std::string> walk_keys;
+  std::vector<Value> next_walk_keys;
+  std::vector<Value> results;
+
+  std::vector<Value> expected_walk_keys;
+  expected_walk_keys.emplace_back(nullptr, "");
+
+  Err err;
+  EXPECT_TRUE(metadata.WalkStep(setup.settings()->build_settings(), data_keys,
+                                walk_keys, true, &next_walk_keys, &results,
+                                &err));
+  EXPECT_FALSE(err.has_error());
+  EXPECT_EQ(next_walk_keys, expected_walk_keys);
+  EXPECT_TRUE(results.empty());
+}