[metadata] Add metadata walk to target

Adds metadata walk logic to targets.

Change-Id: Icef7cc8d2b53413f4e8b4357a6f02cac3a52c874
Reviewed-on: https://gn-review.googlesource.com/c/3380
Commit-Queue: Julie Hockett <juliehockett@google.com>
Reviewed-by: Brett Wilson <brettw@chromium.org>
diff --git a/tools/gn/source_dir.h b/tools/gn/source_dir.h
index 5c1621b..bfc260d 100644
--- a/tools/gn/source_dir.h
+++ b/tools/gn/source_dir.h
@@ -119,6 +119,16 @@
     return base::StringPiece(&value_[1], value_.size() - 1);
   }
 
+  // Returns a path that does not end with a slash.
+  //
+  // This function simply returns the reference to the value if the path is a
+  // root, e.g. "/" or "//".
+  base::StringPiece SourceWithNoTrailingSlash() const {
+    if (value_.size() > 2)
+      return base::StringPiece(&value_[0], value_.size() - 1);
+    return base::StringPiece(value_);
+  }
+
   void SwapValue(std::string* v);
 
   bool operator==(const SourceDir& other) const {
diff --git a/tools/gn/source_dir_unittest.cc b/tools/gn/source_dir_unittest.cc
index adf865b..d5ada23 100644
--- a/tools/gn/source_dir_unittest.cc
+++ b/tools/gn/source_dir_unittest.cc
@@ -185,3 +185,24 @@
   EXPECT_FALSE(err.has_error());
 #endif
 }
+
+TEST(SourceDir, SourceWithNoTrailingSlash) {
+  Err err;
+  SourceDir base("//base/");
+  SourceDir base_no_slash("//base/");
+  EXPECT_EQ(base.SourceWithNoTrailingSlash(), "//base");
+  EXPECT_EQ(base_no_slash.SourceWithNoTrailingSlash(), "//base");
+
+  SourceDir relative_root("//");
+  EXPECT_EQ(relative_root.SourceWithNoTrailingSlash(), "//");
+
+#if defined(OS_WIN)
+  SourceDir root("C:/");
+  SourceDir root_no_slash("C:");
+  EXPECT_EQ(root.SourceWithNoTrailingSlash(), "C:");
+  EXPECT_EQ(root_no_slash.SourceWithNoTrailingSlash(), "C:");
+#else
+  SourceDir root("/");
+  EXPECT_EQ(root.SourceWithNoTrailingSlash(), "/");
+#endif
+}
diff --git a/tools/gn/target.cc b/tools/gn/target.cc
index fbbfd50..bcddf8b 100644
--- a/tools/gn/target.cc
+++ b/tools/gn/target.cc
@@ -873,3 +873,73 @@
       g_scheduler->AddUnknownGeneratedInput(this, source);
   }
 }
+
+bool Target::GetMetadata(const std::vector<std::string>& keys_to_extract,
+                         const std::vector<std::string>& keys_to_walk,
+                         bool rebase_files,
+                         std::vector<Value>* result,
+                         std::set<const Target*>* targets_walked,
+                         Err* err) const {
+  std::vector<Value> next_walk_keys;
+  if (!metadata_.WalkStep(settings()->build_settings(), keys_to_extract,
+                          keys_to_walk, rebase_files, &next_walk_keys, result,
+                          err))
+    return false;
+
+  // Gather walk keys and find the appropriate target. Targets identified in
+  // the walk key set must be deps or data_deps of the declaring target.
+  const DepsIteratorRange& all_deps = GetDeps(Target::DEPS_ALL);
+  for (const auto& next : next_walk_keys) {
+    DCHECK(next.type() == Value::STRING);
+
+    // If we hit an empty string in this list, add all deps and data_deps. The
+    // ordering in the resulting list of values as a result will be the data
+    // from each explicitly listed dep prior to this, followed by all data in
+    // walk order of the remaining deps.
+    if (next.string_value().empty()) {
+      for (const auto& dep : all_deps) {
+        // If we haven't walked this dep yet, go down into it.
+        auto pair = targets_walked->insert(dep.ptr);
+        if (pair.second) {
+          if (!dep.ptr->GetMetadata(keys_to_extract, keys_to_walk, rebase_files,
+                                    result, targets_walked, err))
+            return false;
+        }
+      }
+
+      // Any other walk keys are superfluous, as they can only be a subset of
+      // all deps.
+      break;
+    }
+
+    // Otherwise, look through the target's deps for the specified one.
+    bool found_next = false;
+    for (const auto& dep : all_deps) {
+      // Match against the label with the toolchain.
+      if (dep.label.GetUserVisibleName(true) == next.string_value()) {
+        // If we haven't walked this dep yet, go down into it.
+        auto pair = targets_walked->insert(dep.ptr);
+        if (pair.second) {
+          if (!dep.ptr->GetMetadata(keys_to_extract, keys_to_walk, rebase_files,
+                                    result, targets_walked, err))
+            return false;
+        }
+        // We found it, so we can exit this search now.
+        found_next = true;
+        break;
+      }
+    }
+    // If we didn't find the specified dep in the target, that's an error.
+    // Propagate it back to the user.
+    if (!found_next) {
+      *err = Err(next.origin(),
+                 std::string("I was expecting ") + next.string_value() +
+                     std::string(" to be a dependency of ") +
+                     label().GetUserVisibleName(true) +
+                     ". Make sure it's included in the deps or data_deps, and "
+                     "that you've specified the appropriate toolchain.");
+      return false;
+    }
+  }
+  return true;
+}
diff --git a/tools/gn/target.h b/tools/gn/target.h
index 5421cca..474f9c9 100644
--- a/tools/gn/target.h
+++ b/tools/gn/target.h
@@ -149,6 +149,15 @@
   const Metadata& metadata() const { return metadata_; }
   Metadata& metadata() { return metadata_; }
 
+  // Collect metadata from this target and its dependencies. This is intended to
+  // be called after the target is resolved.
+  bool GetMetadata(const std::vector<std::string>& keys_to_extract,
+                   const std::vector<std::string>& keys_to_walk,
+                   bool rebase_files,
+                   std::vector<Value>* result,
+                   std::set<const Target*>* targets_walked,
+                   Err* err) const;
+
   bool testonly() const { return testonly_; }
   void set_testonly(bool value) { testonly_ = value; }
 
diff --git a/tools/gn/target_unittest.cc b/tools/gn/target_unittest.cc
index 62dad52..b4fa5f9 100644
--- a/tools/gn/target_unittest.cc
+++ b/tools/gn/target_unittest.cc
@@ -1092,3 +1092,159 @@
   ASSERT_TRUE(e.bundle_data().assets_catalog_sources().empty());
   ASSERT_EQ(e.bundle_data().bundle_deps().size(), 2u);
 }
+
+TEST(TargetTest, CollectMetadataNoRecurse) {
+  TestWithScope setup;
+
+  TestTarget one(setup, "//foo:one", Target::SOURCE_SET);
+  Value a_expected(nullptr, Value::LIST);
+  a_expected.list_value().push_back(Value(nullptr, "foo"));
+  one.metadata().contents().insert(
+      std::pair<base::StringPiece, Value>("a", a_expected));
+
+  Value b_expected(nullptr, Value::LIST);
+  b_expected.list_value().push_back(Value(nullptr, true));
+  one.metadata().contents().insert(
+      std::pair<base::StringPiece, Value>("b", b_expected));
+
+  one.metadata().set_source_dir(SourceDir("/usr/home/files/"));
+
+  std::vector<std::string> data_keys;
+  data_keys.push_back("a");
+  data_keys.push_back("b");
+
+  std::vector<std::string> walk_keys;
+
+  Err err;
+  std::vector<Value> result;
+  std::set<const Target*> targets;
+  one.GetMetadata(data_keys, walk_keys, false, &result, &targets, &err);
+  EXPECT_FALSE(err.has_error());
+
+  std::vector<Value> expected;
+  expected.push_back(Value(nullptr, "foo"));
+  expected.push_back(Value(nullptr, true));
+  EXPECT_EQ(result, expected);
+}
+
+TEST(TargetTest, CollectMetadataWithRecurse) {
+  TestWithScope setup;
+
+  TestTarget one(setup, "//foo:one", Target::SOURCE_SET);
+  Value a_expected(nullptr, Value::LIST);
+  a_expected.list_value().push_back(Value(nullptr, "foo"));
+  one.metadata().contents().insert(
+      std::pair<base::StringPiece, Value>("a", a_expected));
+
+  Value b_expected(nullptr, Value::LIST);
+  b_expected.list_value().push_back(Value(nullptr, true));
+  one.metadata().contents().insert(
+      std::pair<base::StringPiece, Value>("b", b_expected));
+
+  TestTarget two(setup, "//foo:two", Target::SOURCE_SET);
+  Value a_2_expected(nullptr, Value::LIST);
+  a_2_expected.list_value().push_back(Value(nullptr, "bar"));
+  two.metadata().contents().insert(
+      std::pair<base::StringPiece, Value>("a", a_2_expected));
+
+  one.public_deps().push_back(LabelTargetPair(&two));
+
+  std::vector<std::string> data_keys;
+  data_keys.push_back("a");
+  data_keys.push_back("b");
+
+  std::vector<std::string> walk_keys;
+
+  Err err;
+  std::vector<Value> result;
+  std::set<const Target*> targets;
+  one.GetMetadata(data_keys, walk_keys, false, &result, &targets, &err);
+  EXPECT_FALSE(err.has_error());
+
+  std::vector<Value> expected;
+  expected.push_back(Value(nullptr, "foo"));
+  expected.push_back(Value(nullptr, true));
+  expected.push_back(Value(nullptr, "bar"));
+  EXPECT_EQ(result, expected);
+}
+
+TEST(TargetTest, CollectMetadataWithBarrier) {
+  TestWithScope setup;
+
+  TestTarget one(setup, "//foo:one", Target::SOURCE_SET);
+  Value a_expected(nullptr, Value::LIST);
+  a_expected.list_value().push_back(Value(nullptr, "foo"));
+  one.metadata().contents().insert(
+      std::pair<base::StringPiece, Value>("a", a_expected));
+
+  Value walk_expected(nullptr, Value::LIST);
+  walk_expected.list_value().push_back(
+      Value(nullptr, "//foo:two(//toolchain:default)"));
+  one.metadata().contents().insert(
+      std::pair<base::StringPiece, Value>("walk", walk_expected));
+
+  TestTarget two(setup, "//foo:two", Target::SOURCE_SET);
+  Value a_2_expected(nullptr, Value::LIST);
+  a_2_expected.list_value().push_back(Value(nullptr, "bar"));
+  two.metadata().contents().insert(
+      std::pair<base::StringPiece, Value>("a", a_2_expected));
+
+  TestTarget three(setup, "//foo:three", Target::SOURCE_SET);
+  Value a_3_expected(nullptr, Value::LIST);
+  a_3_expected.list_value().push_back(Value(nullptr, "baz"));
+  three.metadata().contents().insert(
+      std::pair<base::StringPiece, Value>("a", a_3_expected));
+
+  one.public_deps().push_back(LabelTargetPair(&two));
+  one.public_deps().push_back(LabelTargetPair(&three));
+
+  std::vector<std::string> data_keys;
+  data_keys.push_back("a");
+
+  std::vector<std::string> walk_keys;
+  walk_keys.push_back("walk");
+
+  Err err;
+  std::vector<Value> result;
+  std::set<const Target*> targets;
+  one.GetMetadata(data_keys, walk_keys, false, &result, &targets, &err);
+  EXPECT_FALSE(err.has_error()) << err.message();
+
+  std::vector<Value> expected;
+  expected.push_back(Value(nullptr, "foo"));
+  expected.push_back(Value(nullptr, "bar"));
+  EXPECT_EQ(result, expected) << result.size();
+}
+
+TEST(TargetTest, CollectMetadataWithError) {
+  TestWithScope setup;
+
+  TestTarget one(setup, "//foo:one", Target::SOURCE_SET);
+  Value a_expected(nullptr, Value::LIST);
+  a_expected.list_value().push_back(Value(nullptr, "foo"));
+  one.metadata().contents().insert(
+      std::pair<base::StringPiece, Value>("a", a_expected));
+
+  Value walk_expected(nullptr, Value::LIST);
+  walk_expected.list_value().push_back(Value(nullptr, "//foo:missing"));
+  one.metadata().contents().insert(
+      std::pair<base::StringPiece, Value>("walk", walk_expected));
+
+  std::vector<std::string> data_keys;
+  data_keys.push_back("a");
+
+  std::vector<std::string> walk_keys;
+  walk_keys.push_back("walk");
+
+  Err err;
+  std::vector<Value> result;
+  std::set<const Target*> targets;
+  one.GetMetadata(data_keys, walk_keys, false, &result, &targets, &err);
+  EXPECT_TRUE(err.has_error());
+  EXPECT_EQ(err.message(),
+            "I was expecting //foo:missing to be a dependency of "
+            "//foo:one(//toolchain:default). "
+            "Make sure it's included in the deps or data_deps, and that you've "
+            "specified the appropriate toolchain.")
+      << err.message();
+}