[label_matches] Add new functions label_matches(), filter_labels_include() and filter_labels_exclude()

Add a new function 'label_matches(target_label, label_patterns)' that
can be used to test if a given GN label matches any of the given label
patterns.

Add a new pair of functions:
 - 'filter_labels_include(labels, label_patterns)'
 - 'filter_labels_exclude(labels, label_patterns)'

These returns all labels in the first list that match (or don't match,
respectively) any of the patterns in the second list (much like the
'filter_include()' and 'filter_exclude()' functions do for files.

Change-Id: I7670099dce41c3ff38219adf8fd740b6fb935e78
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/16680
Reviewed-by: Dirk Pranke <dpranke@google.com>
Commit-Queue: Aaron Wood <aaronwood@google.com>
diff --git a/build/gen.py b/build/gen.py
index e7037bb..3570746 100755
--- a/build/gen.py
+++ b/build/gen.py
@@ -664,11 +664,13 @@
         'src/gn/frameworks_utils.cc',
         'src/gn/function_exec_script.cc',
         'src/gn/function_filter.cc',
+        'src/gn/function_filter_labels.cc',
         'src/gn/function_foreach.cc',
         'src/gn/function_forward_variables_from.cc',
         'src/gn/function_get_label_info.cc',
         'src/gn/function_get_path_info.cc',
         'src/gn/function_get_target_outputs.cc',
+        'src/gn/function_label_matches.cc',
         'src/gn/function_process_file_template.cc',
         'src/gn/function_read_file.cc',
         'src/gn/function_rebase_path.cc',
@@ -799,11 +801,13 @@
         'src/gn/file_writer_unittest.cc',
         'src/gn/frameworks_utils_unittest.cc',
         'src/gn/function_filter_unittest.cc',
+        'src/gn/function_filter_labels_unittest.cc',
         'src/gn/function_foreach_unittest.cc',
         'src/gn/function_forward_variables_from_unittest.cc',
         'src/gn/function_get_label_info_unittest.cc',
         'src/gn/function_get_path_info_unittest.cc',
         'src/gn/function_get_target_outputs_unittest.cc',
+        'src/gn/function_label_matches_unittest.cc',
         'src/gn/function_process_file_template_unittest.cc',
         'src/gn/function_rebase_path_unittest.cc',
         'src/gn/function_template_unittest.cc',
diff --git a/docs/reference.md b/docs/reference.md
index 24036dc..9621765 100644
--- a/docs/reference.md
+++ b/docs/reference.md
@@ -43,6 +43,8 @@
     *   [exec_script: Synchronously run a script and return the output.](#func_exec_script)
     *   [filter_exclude: Remove values that match a set of patterns.](#func_filter_exclude)
     *   [filter_include: Remove values that do not match a set of patterns.](#func_filter_include)
+    *   [filter_labels_exclude: Remove labels that match a set of patterns.](#func_filter_labels_exclude)
+    *   [filter_labels_include: Remove labels that do not match a set of patterns.](#func_filter_labels_include)
     *   [foreach: Iterate over a list.](#func_foreach)
     *   [forward_variables_from: Copies variables from a different scope.](#func_forward_variables_from)
     *   [get_label_info: Get an attribute from a target's label.](#func_get_label_info)
@@ -50,6 +52,7 @@
     *   [get_target_outputs: [file list] Get the list of outputs from a target.](#func_get_target_outputs)
     *   [getenv: Get an environment variable.](#func_getenv)
     *   [import: Import a file into the current scope.](#func_import)
+    *   [label_matches: Returns whether a label matches any of a list of patterns.](#func_label_matches)
     *   [not_needed: Mark variables from scope as not needed.](#func_not_needed)
     *   [pool: Defines a pool object.](#func_pool)
     *   [print: Prints to the console.](#func_print)
@@ -2610,6 +2613,42 @@
   result = filter_include(values, [ "*.proto" ])
   # result will be [ "foo.proto" ]
 ```
+### <a name="func_filter_labels_exclude"></a>**filter_labels_exclude**: Remove labels that match a set of patterns.
+
+```
+  filter_labels_exclude(labels, exclude_patterns)
+
+  The argument labels must be a list of strings.
+
+  The argument exclude_patterns must be a list of label patterns (see
+  "gn help label_pattern"). Only elements from labels matching at least
+  one of the patterns will be excluded.
+```
+
+#### **Examples**
+```
+  labels = [ "//foo:baz", "//foo/bar:baz", "//bar:baz" ]
+  result = filter_labels_exclude(labels, [ "//foo:*" ])
+  # result will be [ "//foo/bar:baz", "//bar:baz" ]
+```
+### <a name="func_filter_labels_include"></a>**filter_labels_include**: Remove labels that do not match a set of patterns.
+
+```
+  filter_labels_include(labels, include_patterns)
+
+  The argument labels must be a list of strings.
+
+  The argument include_patterns must be a list of label patterns (see
+  "gn help label_pattern"). Only elements from labels matching at least
+  one of the patterns will be included.
+```
+
+#### **Examples**
+```
+  labels = [ "//foo:baz", "//foo/bar:baz", "//bar:baz" ]
+  result = filter_labels_include(labels, [ "//foo:*" ])
+  # result will be [ "//foo:baz" ]
+```
 ### <a name="func_foreach"></a>**foreach**: Iterate over a list.
 
 ```
@@ -2972,6 +3011,21 @@
   # Looks in the current directory.
   import("my_vars.gni")
 ```
+### <a name="func_label_matches"></a>**label_matches**: Returns true if the label matches any of a set of patterns.
+
+```
+  label_matches(target_label, patterns)
+
+  The argument patterns must be a list of label patterns (see
+  "gn help label_pattern"). If the target_label matches any of the patterns,
+  the function returns the value true.
+```
+
+#### **Examples**
+```
+  result = label_matches("//baz:bar", [ "//foo/bar/*", "//baz:*" ])
+  # result will be true
+```
 ### <a name="func_not_needed"></a>**not_needed**: Mark variables from scope as not needed.
 
 ```
diff --git a/src/gn/function_filter_labels.cc b/src/gn/function_filter_labels.cc
new file mode 100644
index 0000000..eb08def
--- /dev/null
+++ b/src/gn/function_filter_labels.cc
@@ -0,0 +1,145 @@
+// 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 <stddef.h>
+
+#include "gn/build_settings.h"
+#include "gn/err.h"
+#include "gn/functions.h"
+#include "gn/label_pattern.h"
+#include "gn/parse_tree.h"
+#include "gn/scope.h"
+#include "gn/settings.h"
+#include "gn/value.h"
+
+namespace functions {
+
+enum FilterSelection {
+  kExcludeFilter,
+  kIncludeFilter,
+};
+
+Value RunFilterLabels(Scope* scope,
+                      const FunctionCallNode* function,
+                      const std::vector<Value>& args,
+                      FilterSelection selection,
+                      Err* err) {
+  if (args.size() != 2) {
+    *err = Err(function, "Expecting exactly two arguments.");
+    return Value();
+  }
+
+  // Validate "labels" and "patterns" are both lists
+  if (args[0].type() != Value::LIST) {
+    *err = Err(args[0], "First argument must be a list of target labels.");
+    return Value();
+  }
+  if (args[1].type() != Value::LIST) {
+    *err = Err(args[1], "Second argument must be a list of label patterns.");
+    return Value();
+  }
+
+  // Extract "patterns"
+  std::vector<LabelPattern> patterns;
+  patterns.reserve(args[1].list_value().size());
+
+  for (const auto& value : args[1].list_value()) {
+    if (value.type() != Value::STRING) {
+      *err = Err(args[1], "Second argument must be a list of label patterns.");
+      return Value();
+    }
+    LabelPattern pattern = LabelPattern::GetPattern(
+        scope->GetSourceDir(),
+        scope->settings()->build_settings()->root_path_utf8(), value, err);
+    if (err->has_error()) {
+      return Value();
+    }
+    patterns.push_back(std::move(pattern));
+  }
+
+  // Iterate over "labels", resolving and matching against the list of patterns.
+  Value result(function, Value::LIST);
+  for (const auto& value : args[0].list_value()) {
+    Label label =
+        Label::Resolve(scope->GetSourceDir(),
+                       scope->settings()->build_settings()->root_path_utf8(),
+                       ToolchainLabelForScope(scope), value, err);
+    if (err->has_error()) {
+      // Change the error message to be more applicable than what Resolve will
+      // produce.
+      *err = Err(value, "First argument must be a list of target labels.");
+      return Value();
+    }
+
+    const bool matches_pattern = LabelPattern::VectorMatches(patterns, label);
+    switch (selection) {
+      case kIncludeFilter:
+        if (matches_pattern)
+          result.list_value().push_back(value);
+        break;
+
+      case kExcludeFilter:
+        if (!matches_pattern)
+          result.list_value().push_back(value);
+        break;
+    }
+  }
+  return result;
+}
+
+const char kFilterLabelsInclude[] = "filter_labels_include";
+const char kFilterLabelsInclude_HelpShort[] =
+    "filter_labels_include: Remove labels that do not match a set of patterns.";
+const char kFilterLabelsInclude_Help[] =
+    R"(filter_labels_include: Remove labels that do not match a set of patterns.
+
+  filter_labels_include(labels, include_patterns)
+
+  The argument labels must be a list of strings.
+
+  The argument include_patterns must be a list of label patterns (see
+  "gn help label_pattern"). Only elements from labels matching at least
+  one of the patterns will be included.
+
+Examples
+  labels = [ "//foo:baz", "//foo/bar:baz", "//bar:baz" ]
+  result = filter_labels_include(labels, [ "//foo:*" ])
+  # result will be [ "//foo:baz" ]
+)";
+
+Value RunFilterLabelsInclude(Scope* scope,
+                             const FunctionCallNode* function,
+                             const std::vector<Value>& args,
+                             Err* err) {
+  return RunFilterLabels(scope, function, args, kIncludeFilter, err);
+}
+
+const char kFilterLabelsExclude[] = "filter_labels_exclude";
+const char kFilterLabelsExclude_HelpShort[] =
+    "filter_labels_exclude: Remove labels that match a set of patterns.";
+const char kFilterLabelsExclude_Help[] =
+    R"(filter_labels_exclude: Remove labels that match a set of patterns.
+
+  filter_labels_exclude(labels, exclude_patterns)
+
+  The argument labels must be a list of strings.
+
+  The argument exclude_patterns must be a list of label patterns (see
+  "gn help label_pattern"). Only elements from labels matching at least
+  one of the patterns will be excluded.
+
+Examples
+  labels = [ "//foo:baz", "//foo/bar:baz", "//bar:baz" ]
+  result = filter_labels_exclude(labels, [ "//foo:*" ])
+  # result will be [ "//foo/bar:baz", "//bar:baz" ]
+)";
+
+Value RunFilterLabelsExclude(Scope* scope,
+                             const FunctionCallNode* function,
+                             const std::vector<Value>& args,
+                             Err* err) {
+  return RunFilterLabels(scope, function, args, kExcludeFilter, err);
+}
+
+}  // namespace functions
diff --git a/src/gn/function_filter_labels_unittest.cc b/src/gn/function_filter_labels_unittest.cc
new file mode 100644
index 0000000..4a0ebf3
--- /dev/null
+++ b/src/gn/function_filter_labels_unittest.cc
@@ -0,0 +1,255 @@
+// 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 "gn/functions.h"
+#include "gn/test_with_scope.h"
+#include "util/build_config.h"
+#include "util/test/test.h"
+
+TEST(FilterLabelsTest, OneIncluded) {
+  TestWithScope setup;
+  FunctionCallNode function;
+
+  std::vector<Value> args;
+  Value labels(nullptr, Value::LIST);
+  labels.list_value().push_back(Value(nullptr, "//foo:bar"));
+  labels.list_value().push_back(Value(nullptr, "//baz:bar"));
+
+  Value patterns(nullptr, Value::LIST);
+  patterns.list_value().push_back(Value(nullptr, "//foo/*"));
+  patterns.list_value().push_back(Value(nullptr, "//bar:*"));
+
+  args.push_back(labels);
+  args.push_back(patterns);
+
+  Err err;
+  Value result =
+      functions::RunFilterLabelsInclude(setup.scope(), &function, args, &err);
+  ASSERT_FALSE(err.has_error());
+  ASSERT_EQ(result.type(), Value::LIST);
+  ASSERT_EQ(1u, result.list_value().size());
+  ASSERT_TRUE(result.list_value()[0].type() == Value::STRING);
+  ASSERT_EQ("//foo:bar", result.list_value()[0].string_value());
+}
+
+TEST(FilterLabelsTest, TwoIncluded) {
+  TestWithScope setup;
+  FunctionCallNode function;
+
+  std::vector<Value> args;
+  Value labels(nullptr, Value::LIST);
+  labels.list_value().push_back(Value(nullptr, "//foo:bar"));
+  labels.list_value().push_back(Value(nullptr, "//bar"));
+  labels.list_value().push_back(Value(nullptr, "//baz:bar"));
+
+  Value patterns(nullptr, Value::LIST);
+  patterns.list_value().push_back(Value(nullptr, "//foo/*"));
+  patterns.list_value().push_back(Value(nullptr, "//bar:*"));
+
+  args.push_back(labels);
+  args.push_back(patterns);
+
+  Err err;
+  Value result =
+      functions::RunFilterLabelsInclude(setup.scope(), &function, args, &err);
+  ASSERT_FALSE(err.has_error());
+  ASSERT_EQ(result.type(), Value::LIST);
+  ASSERT_EQ(2u, result.list_value().size());
+  ASSERT_TRUE(result.list_value()[0].type() == Value::STRING);
+  ASSERT_EQ("//foo:bar", result.list_value()[0].string_value());
+  ASSERT_TRUE(result.list_value()[1].type() == Value::STRING);
+  ASSERT_EQ("//bar", result.list_value()[1].string_value());
+}
+
+TEST(FilterLabelsTest, NoneIncluded) {
+  TestWithScope setup;
+  FunctionCallNode function;
+
+  std::vector<Value> args;
+  Value labels(nullptr, Value::LIST);
+  labels.list_value().push_back(Value(nullptr, "//foo:bar"));
+  labels.list_value().push_back(Value(nullptr, "//baz:bar"));
+
+  Value patterns(nullptr, Value::LIST);
+  patterns.list_value().push_back(Value(nullptr, "//fooz/*"));
+  patterns.list_value().push_back(Value(nullptr, "//bar:*"));
+
+  args.push_back(labels);
+  args.push_back(patterns);
+
+  Err err;
+  Value result =
+      functions::RunFilterLabelsInclude(setup.scope(), &function, args, &err);
+  ASSERT_FALSE(err.has_error());
+  ASSERT_EQ(result.type(), Value::LIST);
+  ASSERT_EQ(0u, result.list_value().size());
+}
+
+TEST(FilterLabelsTest, OneExcluded) {
+  TestWithScope setup;
+  FunctionCallNode function;
+
+  std::vector<Value> args;
+  Value labels(nullptr, Value::LIST);
+  labels.list_value().push_back(Value(nullptr, "//foo:bar"));
+  labels.list_value().push_back(Value(nullptr, "//baz:bar"));
+
+  Value patterns(nullptr, Value::LIST);
+  patterns.list_value().push_back(Value(nullptr, "//foo/*"));
+  patterns.list_value().push_back(Value(nullptr, "//bar:*"));
+
+  args.push_back(labels);
+  args.push_back(patterns);
+
+  Err err;
+  Value result =
+      functions::RunFilterLabelsExclude(setup.scope(), &function, args, &err);
+  ASSERT_FALSE(err.has_error());
+  ASSERT_EQ(result.type(), Value::LIST);
+  ASSERT_EQ(1u, result.list_value().size());
+  ASSERT_TRUE(result.list_value()[0].type() == Value::STRING);
+  ASSERT_EQ("//baz:bar", result.list_value()[0].string_value());
+}
+
+TEST(FilterLabelsTest, TwoExcluded) {
+  TestWithScope setup;
+  FunctionCallNode function;
+
+  std::vector<Value> args;
+  Value labels(nullptr, Value::LIST);
+  labels.list_value().push_back(Value(nullptr, "//foo:bar"));
+  labels.list_value().push_back(Value(nullptr, "//bar"));
+  labels.list_value().push_back(Value(nullptr, "//baz:bar"));
+
+  Value patterns(nullptr, Value::LIST);
+  patterns.list_value().push_back(Value(nullptr, "//foo/*"));
+  patterns.list_value().push_back(Value(nullptr, "//bar:*"));
+
+  args.push_back(labels);
+  args.push_back(patterns);
+
+  Err err;
+  Value result =
+      functions::RunFilterLabelsExclude(setup.scope(), &function, args, &err);
+  ASSERT_FALSE(err.has_error());
+  ASSERT_EQ(result.type(), Value::LIST);
+  ASSERT_EQ(1u, result.list_value().size());
+  ASSERT_TRUE(result.list_value()[0].type() == Value::STRING);
+  ASSERT_EQ("//baz:bar", result.list_value()[0].string_value());
+}
+
+TEST(FilterLabelsTest, NoneExcluded) {
+  TestWithScope setup;
+  FunctionCallNode function;
+
+  std::vector<Value> args;
+  Value labels(nullptr, Value::LIST);
+  labels.list_value().push_back(Value(nullptr, "//foo:bar"));
+  labels.list_value().push_back(Value(nullptr, "//baz:bar"));
+
+  Value patterns(nullptr, Value::LIST);
+  patterns.list_value().push_back(Value(nullptr, "//fooz/*"));
+  patterns.list_value().push_back(Value(nullptr, "//bar:*"));
+
+  args.push_back(labels);
+  args.push_back(patterns);
+
+  Err err;
+  Value result =
+      functions::RunFilterLabelsExclude(setup.scope(), &function, args, &err);
+  ASSERT_FALSE(err.has_error());
+  ASSERT_EQ(result.type(), Value::LIST);
+  ASSERT_EQ(2u, result.list_value().size());
+  ASSERT_TRUE(result.list_value()[0].type() == Value::STRING);
+  ASSERT_EQ("//foo:bar", result.list_value()[0].string_value());
+  ASSERT_TRUE(result.list_value()[1].type() == Value::STRING);
+  ASSERT_EQ("//baz:bar", result.list_value()[1].string_value());
+}
+
+TEST(FilterLabelsTest, LabelsIsList) {
+  TestWithScope setup;
+  FunctionCallNode function;
+
+  std::vector<Value> args;
+  Value labels(nullptr, true);
+
+  Value patterns(nullptr, Value::LIST);
+  patterns.list_value().push_back(Value(nullptr, "//foo/*"));
+  patterns.list_value().push_back(Value(nullptr, "//bar:*"));
+
+  args.push_back(labels);
+  args.push_back(patterns);
+
+  Err err;
+  Value result =
+      functions::RunFilterLabelsInclude(setup.scope(), &function, args, &err);
+  ASSERT_TRUE(err.has_error());
+  ASSERT_EQ(err.message(), "First argument must be a list of target labels.");
+}
+
+TEST(FilterLabelsTest, PatternsIsList) {
+  TestWithScope setup;
+  FunctionCallNode function;
+
+  std::vector<Value> args;
+  Value labels(nullptr, Value::LIST);
+  labels.list_value().push_back(Value(nullptr, "//foo:bar"));
+  labels.list_value().push_back(Value(nullptr, "//baz:bar"));
+
+  Value patterns(nullptr, true);
+
+  args.push_back(labels);
+  args.push_back(patterns);
+
+  Err err;
+  Value result =
+      functions::RunFilterLabelsInclude(setup.scope(), &function, args, &err);
+  ASSERT_TRUE(err.has_error());
+  ASSERT_EQ(err.message(), "Second argument must be a list of label patterns.");
+}
+
+TEST(FilterLabelsTest, LabelsAreLabels) {
+  TestWithScope setup;
+  FunctionCallNode function;
+
+  std::vector<Value> args;
+  Value labels(nullptr, Value::LIST);
+  labels.list_value().push_back(Value(nullptr, "//foo:bar"));
+  labels.list_value().push_back(Value(nullptr, true));
+
+  Value patterns(nullptr, Value::LIST);
+  patterns.list_value().push_back(Value(nullptr, "//foo/*"));
+  patterns.list_value().push_back(Value(nullptr, "//bar:*"));
+
+  args.push_back(labels);
+  args.push_back(patterns);
+
+  Err err;
+  Value result =
+      functions::RunFilterLabelsInclude(setup.scope(), &function, args, &err);
+  ASSERT_TRUE(err.has_error());
+  ASSERT_EQ(err.message(), "First argument must be a list of target labels.");
+}
+
+TEST(FilterLabelsTest, PatternsArePatterns) {
+  TestWithScope setup;
+  FunctionCallNode function;
+
+  std::vector<Value> args;
+  Value labels(nullptr, Value::LIST);
+  labels.list_value().push_back(Value(nullptr, "//foo:bar"));
+  labels.list_value().push_back(Value(nullptr, "//bar"));
+
+  Value patterns(nullptr, Value::LIST);
+  patterns.list_value().push_back(Value(nullptr, "//foo/*:foo"));
+
+  args.push_back(labels);
+  args.push_back(patterns);
+
+  Err err;
+  Value result =
+      functions::RunFilterLabelsInclude(setup.scope(), &function, args, &err);
+  ASSERT_TRUE(err.has_error());
+  ASSERT_EQ(err.message(), "Invalid label pattern.");
+}
\ No newline at end of file
diff --git a/src/gn/function_label_matches.cc b/src/gn/function_label_matches.cc
new file mode 100644
index 0000000..45e11d4
--- /dev/null
+++ b/src/gn/function_label_matches.cc
@@ -0,0 +1,84 @@
+// 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 <stddef.h>
+
+#include "gn/build_settings.h"
+#include "gn/err.h"
+#include "gn/functions.h"
+#include "gn/label_pattern.h"
+#include "gn/parse_tree.h"
+#include "gn/scope.h"
+#include "gn/settings.h"
+#include "gn/value.h"
+
+namespace functions {
+
+const char kLabelMatches[] = "label_matches";
+const char kLabelMatches_HelpShort[] =
+    "label_matches: Returns whether a label matches any of a list of patterns.";
+const char kLabelMatches_Help[] =
+    R"(label_matches: Returns true if the label matches any of a set of patterns.
+
+  label_matches(target_label, patterns)
+
+  The argument patterns must be a list of label patterns (see
+  "gn help label_pattern"). If the target_label matches any of the patterns,
+  the function returns the value true.
+
+Examples
+  result = label_matches("//baz:bar", [ "//foo/bar/*", "//baz:*" ])
+  # result will be true
+)";
+
+Value RunLabelMatches(Scope* scope,
+                      const FunctionCallNode* function,
+                      const std::vector<Value>& args,
+                      Err* err) {
+  if (args.size() != 2) {
+    *err = Err(function, "Expecting exactly two arguments.");
+    return Value();
+  }
+
+  // Extract "label"
+  if (args[0].type() != Value::STRING) {
+    *err = Err(args[0], "First argument must be a target label.");
+    return Value();
+  }
+  Label label =
+      Label::Resolve(scope->GetSourceDir(),
+                     scope->settings()->build_settings()->root_path_utf8(),
+                     ToolchainLabelForScope(scope), args[0], err);
+  if (label.is_null()) {
+    return Value();
+  }
+
+  // Extract "patterns".
+  if (args[1].type() != Value::LIST) {
+    *err = Err(args[1], "Second argument must be a list of label patterns.");
+    return Value();
+  }
+  std::vector<LabelPattern> patterns;
+  patterns.reserve(args[1].list_value().size());
+
+  for (const auto& pattern_string : args[1].list_value()) {
+    if (pattern_string.type() != Value::STRING) {
+      *err = Err(pattern_string,
+                 "Second argument must be a list of label patterns.");
+      return Value();
+    }
+    LabelPattern pattern = LabelPattern::GetPattern(
+        scope->GetSourceDir(),
+        scope->settings()->build_settings()->root_path_utf8(), pattern_string,
+        err);
+    if (err->has_error()) {
+      return Value();
+    }
+    patterns.push_back(std::move(pattern));
+  }
+
+  return Value(function, LabelPattern::VectorMatches(patterns, label));
+}
+
+}  // namespace functions
diff --git a/src/gn/function_label_matches_unittest.cc b/src/gn/function_label_matches_unittest.cc
new file mode 100644
index 0000000..f7bda69
--- /dev/null
+++ b/src/gn/function_label_matches_unittest.cc
@@ -0,0 +1,123 @@
+// 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 "gn/functions.h"
+#include "gn/test_with_scope.h"
+#include "util/build_config.h"
+#include "util/test/test.h"
+
+TEST(LabelMatchesTest, MatchesSubTarget) {
+  TestWithScope setup;
+  FunctionCallNode function;
+
+  std::vector<Value> args;
+  args.push_back(Value(nullptr, "//foo:bar"));
+
+  Value patterns(nullptr, Value::LIST);
+  patterns.list_value().push_back(Value(nullptr, "//foo/*"));
+  patterns.list_value().push_back(Value(nullptr, "//bar:*"));
+  args.push_back(patterns);
+
+  Err err;
+  Value result =
+      functions::RunLabelMatches(setup.scope(), &function, args, &err);
+  ASSERT_FALSE(err.has_error());
+  ASSERT_EQ(result.type(), Value::BOOLEAN);
+  ASSERT_EQ(result.boolean_value(), true);
+}
+
+TEST(LabelMatchesTest, MatchesTargetInFile) {
+  TestWithScope setup;
+  FunctionCallNode function;
+
+  std::vector<Value> args;
+  args.push_back(Value(nullptr, "//bar:foo"));
+
+  Value patterns(nullptr, Value::LIST);
+  patterns.list_value().push_back(Value(nullptr, "//foo/*"));
+  patterns.list_value().push_back(Value(nullptr, "//bar:*"));
+  args.push_back(patterns);
+
+  Err err;
+  Value result =
+      functions::RunLabelMatches(setup.scope(), &function, args, &err);
+  ASSERT_FALSE(err.has_error());
+  ASSERT_EQ(result.type(), Value::BOOLEAN);
+  ASSERT_EQ(result.boolean_value(), true);
+}
+
+TEST(LabelMatchesTest, NoMatch) {
+  TestWithScope setup;
+  FunctionCallNode function;
+
+  std::vector<Value> args;
+  args.push_back(Value(nullptr, "//baz/foo:bar"));
+
+  Value patterns(nullptr, Value::LIST);
+  patterns.list_value().push_back(Value(nullptr, "//foo/*"));
+  patterns.list_value().push_back(Value(nullptr, "//bar:*"));
+  args.push_back(patterns);
+
+  Err err;
+  Value result =
+      functions::RunLabelMatches(setup.scope(), &function, args, &err);
+  ASSERT_FALSE(err.has_error());
+  ASSERT_EQ(result.type(), Value::BOOLEAN);
+  ASSERT_EQ(result.boolean_value(), false);
+}
+
+TEST(LabelMatchesTest, LabelMustBeString) {
+  TestWithScope setup;
+  FunctionCallNode function;
+
+  std::vector<Value> args;
+  args.push_back(Value(nullptr, true));
+
+  Value patterns(nullptr, Value::LIST);
+  patterns.list_value().push_back(Value(nullptr, "//foo/*"));
+  patterns.list_value().push_back(Value(nullptr, "//bar:*"));
+  args.push_back(patterns);
+
+  Err err;
+  Value result =
+      functions::RunLabelMatches(setup.scope(), &function, args, &err);
+  ASSERT_TRUE(err.has_error());
+  ASSERT_EQ(err.message(), "First argument must be a target label.");
+}
+
+TEST(LabelMatchesTest, PatternsMustBeList) {
+  TestWithScope setup;
+  FunctionCallNode function;
+
+  std::vector<Value> args;
+  args.push_back(Value(nullptr, "//baz/foo:bar"));
+
+  Value patterns(nullptr, true);
+  args.push_back(patterns);
+
+  Err err;
+  Value result =
+      functions::RunLabelMatches(setup.scope(), &function, args, &err);
+  ASSERT_TRUE(err.has_error());
+  ASSERT_EQ(err.message(), "Second argument must be a list of label patterns.");
+}
+
+TEST(LabelMatchesTest, PatternsMustBeListOfStrings) {
+  TestWithScope setup;
+  FunctionCallNode function;
+
+  std::vector<Value> args;
+  args.push_back(Value(nullptr, "//baz/foo:bar"));
+
+  Value patterns(nullptr, Value::LIST);
+  patterns.list_value().push_back(Value(nullptr, "//foo/*"));
+  patterns.list_value().push_back(Value(nullptr, true));
+  args.push_back(patterns);
+
+  Err err;
+  Value result =
+      functions::RunLabelMatches(setup.scope(), &function, args, &err);
+  ASSERT_TRUE(err.has_error());
+  ASSERT_EQ(err.message(), "Second argument must be a list of label patterns.");
+}
diff --git a/src/gn/functions.cc b/src/gn/functions.cc
index 770ab02..adc1ce3 100644
--- a/src/gn/functions.cc
+++ b/src/gn/functions.cc
@@ -1465,6 +1465,8 @@
     INSERT_FUNCTION(ExecScript, false)
     INSERT_FUNCTION(FilterExclude, false)
     INSERT_FUNCTION(FilterInclude, false)
+    INSERT_FUNCTION(FilterLabelsInclude, false)
+    INSERT_FUNCTION(FilterLabelsExclude, false)
     INSERT_FUNCTION(ForEach, false)
     INSERT_FUNCTION(ForwardVariablesFrom, false)
     INSERT_FUNCTION(GetEnv, false)
@@ -1472,6 +1474,7 @@
     INSERT_FUNCTION(GetPathInfo, false)
     INSERT_FUNCTION(GetTargetOutputs, false)
     INSERT_FUNCTION(Import, false)
+    INSERT_FUNCTION(LabelMatches, false)
     INSERT_FUNCTION(NotNeeded, false)
     INSERT_FUNCTION(Pool, false)
     INSERT_FUNCTION(Print, false)
diff --git a/src/gn/functions.h b/src/gn/functions.h
index 1230501..5cfeb82 100644
--- a/src/gn/functions.h
+++ b/src/gn/functions.h
@@ -162,6 +162,22 @@
                        const std::vector<Value>& args,
                        Err* err);
 
+extern const char kFilterLabelsInclude[];
+extern const char kFilterLabelsInclude_HelpShort[];
+extern const char kFilterLabelsInclude_Help[];
+Value RunFilterLabelsInclude(Scope* scope,
+                      const FunctionCallNode* function,
+                      const std::vector<Value>& args,
+                      Err* err);
+
+extern const char kFilterLabelsExclude[];
+extern const char kFilterLabelsExclude_HelpShort[];
+extern const char kFilterLabelsExclude_Help[];
+Value RunFilterLabelsExclude(Scope* scope,
+                      const FunctionCallNode* function,
+                      const std::vector<Value>& args,
+                      Err* err);
+
 extern const char kForEach[];
 extern const char kForEach_HelpShort[];
 extern const char kForEach_Help[];
@@ -236,6 +252,14 @@
                 const std::vector<Value>& args,
                 Err* err);
 
+extern const char kLabelMatches[];
+extern const char kLabelMatches_HelpShort[];
+extern const char kLabelMatches_Help[];
+Value RunLabelMatches(Scope* scope,
+                      const FunctionCallNode* function,
+                      const std::vector<Value>& args,
+                      Err* err);
+
 extern const char kLoadableModule[];
 extern const char kLoadableModule_HelpShort[];
 extern const char kLoadableModule_Help[];