Add "filter_{exclude,include}" intrinsic functions to GN

Some templates in chromium abuses set_sources_assignment_filter to
filter elements out of a list. As set_sources_assignment_filter is
considered a mis-feature (and a consensus to remove it), there is
a need for an alternative way to filter lists of strings.

Add two intrinsics functions "function_exclude" & "filter_include"
that take a list of string and a list of pattern and return all
the elements from the first list that match respectively none or
at least one of the patterns.

Bug: 35
Change-Id: I94546e15fa35122e66c3a0bbadb5410b18e8be65
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/6400
Commit-Queue: Sylvain Defresne <sdefresne@chromium.org>
Reviewed-by: Brett Wilson <brettw@chromium.org>
diff --git a/build/gen.py b/build/gen.py
index 758c51a..bcd9f15 100755
--- a/build/gen.py
+++ b/build/gen.py
@@ -485,6 +485,7 @@
         'src/gn/filesystem_utils.cc',
         'src/gn/frameworks_utils.cc',
         'src/gn/function_exec_script.cc',
+        'src/gn/function_filter.cc',
         'src/gn/function_foreach.cc',
         'src/gn/function_forward_variables_from.cc',
         'src/gn/function_get_label_info.cc',
@@ -607,6 +608,7 @@
         'src/gn/exec_process_unittest.cc',
         'src/gn/filesystem_utils_unittest.cc',
         'src/gn/frameworks_utils_unittest.cc',
+        'src/gn/function_filter_unittest.cc',
         'src/gn/function_foreach_unittest.cc',
         'src/gn/function_forward_variables_from_unittest.cc',
         'src/gn/function_get_label_info_unittest.cc',
diff --git a/docs/reference.md b/docs/reference.md
index cbf9db1..c158fa4 100644
--- a/docs/reference.md
+++ b/docs/reference.md
@@ -39,6 +39,8 @@
     *   [declare_args: Declare build arguments.](#func_declare_args)
     *   [defined: Returns whether an identifier is defined.](#func_defined)
     *   [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)
     *   [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)
@@ -158,6 +160,7 @@
     *   [execution: Build graph and execution overview.](#execution)
     *   [grammar: Language and grammar for GN build files.](#grammar)
     *   [input_conversion: Processing input from exec_script and read_file.](#io_conversion)
+    *   [file_pattern: Matching more than one file.](#file_pattern)
     *   [label_pattern: Matching more than one label.](#label_pattern)
     *   [labels: About labels.](#labels)
     *   [metadata_collection: About metadata and its collection.](#metadata_collection)
@@ -749,6 +752,9 @@
       Override defaut workspace file name ("all"). The workspace file is
       written to the root build directory.
 
+  --ninja-executable=<string>
+      Can be used to specify the ninja executable to use when building.
+
   --ninja-extra-args=<string>
       This string is passed without any quoting to the ninja invocation
       command-line. Can be used to configure ninja flags, like "-j".
@@ -2249,6 +2255,42 @@
   # result.
   exec_script("//foo/bar/myscript.py")
 ```
+### <a name="func_filter_exclude"></a>**filter_exclude**: Remove values that match a set of patterns.
+
+```
+  filter_exclude(values, exclude_patterns)
+
+  The argument values must be a list of strings.
+
+  The argument exclude_patterns must be a list of file patterns (see
+  "gn help file_pattern"). Any elements in values matching at least one
+  of those patterns will be excluded.
+```
+
+#### **Examples**
+```
+  values = [ "foo.cc", "foo.h", "foo.proto" ]
+  result = filter_exclude(values, [ "*.proto" ])
+  # result will be [ "foo.cc", "foo.h" ]
+```
+### <a name="func_filter_include"></a>**filter_include**: Remove values that do not match a set of patterns.
+
+```
+  filter_include(values, include_patterns)
+
+  The argument values must be a list of strings.
+
+  The argument include_patterns must be a list of file patterns (see
+  "gn help file_pattern"). Only elements from values matching at least
+  one of the pattern will be included.
+```
+
+#### **Examples**
+```
+  values = [ "foo.cc", "foo.h", "foo.proto" ]
+  result = filter_include(values, [ "*.proto" ])
+  # result will be [ "foo.proto" ]
+```
 ### <a name="func_foreach"></a>**foreach**: Iterate over a list.
 
 ```
@@ -2956,42 +2998,9 @@
 
   If you want to bypass the filter and add a file even if it might be filtered
   out, call set_sources_assignment_filter([]) to clear the list of filters.
-  This will apply until the current scope exits
-```
+  This will apply until the current scope exits.
 
-#### **How to use patterns**
-
-```
-  File patterns are VERY limited regular expressions. They must match the
-  entire input string to be counted as a match. In regular expression parlance,
-  there is an implicit "^...$" surrounding your input. If you want to match a
-  substring, you need to use wildcards at the beginning and end.
-
-  There are only two special tokens understood by the pattern matcher.
-  Everything else is a literal.
-
-   - "*" Matches zero or more of any character. It does not depend on the
-     preceding character (in regular expression parlance it is equivalent to
-     ".*").
-
-   - "\b" Matches a path boundary. This will match the beginning or end of a
-     string, or a slash.
-```
-
-#### **Pattern examples**
-
-```
-  "*asdf*"
-      Matches a string containing "asdf" anywhere.
-
-  "asdf"
-      Matches only the exact string "asdf".
-
-  "*.cc"
-      Matches strings ending in the literal ".cc".
-
-  "\bwin/*"
-      Matches "win/foo" and "foo/win/bar.cc" but not "iwin/foo".
+  See "gn help file_pattern" for more information on file pattern.
 ```
 
 #### **Sources assignment example**
@@ -6958,6 +6967,40 @@
       Note that "trim value" is useless because the value parser skips
       whitespace anyway.
 ```
+### <a name="file_pattern"></a>**File patterns**
+
+```
+  File patterns are VERY limited regular expressions. They must match the
+  entire input string to be counted as a match. In regular expression parlance,
+  there is an implicit "^...$" surrounding your input. If you want to match a
+  substring, you need to use wildcards at the beginning and end.
+
+  There are only two special tokens understood by the pattern matcher.
+  Everything else is a literal.
+
+   - "*" Matches zero or more of any character. It does not depend on the
+     preceding character (in regular expression parlance it is equivalent to
+     ".*").
+
+   - "\b" Matches a path boundary. This will match the beginning or end of a
+     string, or a slash.
+```
+
+#### **Pattern examples**
+
+```
+  "*asdf*"
+      Matches a string containing "asdf" anywhere.
+
+  "asdf"
+      Matches only the exact string "asdf".
+
+  "*.cc"
+      Matches strings ending in the literal ".cc".
+
+  "\bwin/*"
+      Matches "win/foo" and "foo/win/bar.cc" but not "iwin/foo".
+```
 ### <a name="label_pattern"></a>**Label patterns**
 
 ```
diff --git a/src/gn/command_help.cc b/src/gn/command_help.cc
index fdbd679..f03a4e7 100644
--- a/src/gn/command_help.cc
+++ b/src/gn/command_help.cc
@@ -14,6 +14,7 @@
 #include "gn/ninja_build_writer.h"
 #include "gn/output_conversion.h"
 #include "gn/parser.h"
+#include "gn/pattern.h"
 #include "gn/runtime_deps.h"
 #include "gn/setup.h"
 #include "gn/standard_out.h"
@@ -78,6 +79,7 @@
   PrintShortHelp(
       "input_conversion: Processing input from exec_script and read_file.",
       "io_conversion");
+  PrintShortHelp("file_pattern: Matching more than one file.", "file_pattern");
   PrintShortHelp("label_pattern: Matching more than one label.",
                  "label_pattern");
   PrintShortHelp("labels: About labels.", "labels");
@@ -195,6 +197,7 @@
   PrintLongHelp(kExecution_Help, "execution");
   PrintLongHelp(kGrammar_Help, "grammar");
   PrintLongHelp(kInputOutputConversion_Help, "io_conversion");
+  PrintLongHelp(kFilePattern_Help, "file_pattern");
   PrintLongHelp(kLabelPattern_Help, "label_pattern");
   PrintLongHelp(kLabels_Help, "labels");
   PrintLongHelp(kMetadata_Help, "metadata_collection");
@@ -336,6 +339,7 @@
   random_topics["io_conversion"] = []() {
     PrintLongHelp(kInputOutputConversion_Help);
   };
+  random_topics["file_pattern"] = []() { PrintLongHelp(kFilePattern_Help); };
   random_topics["label_pattern"] = []() { PrintLongHelp(kLabelPattern_Help); };
   random_topics["labels"] = []() { PrintLongHelp(kLabels_Help); };
   random_topics["metadata_collection"] = []() {
diff --git a/src/gn/function_filter.cc b/src/gn/function_filter.cc
new file mode 100644
index 0000000..49eacfa
--- /dev/null
+++ b/src/gn/function_filter.cc
@@ -0,0 +1,124 @@
+// 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/err.h"
+#include "gn/filesystem_utils.h"
+#include "gn/functions.h"
+#include "gn/parse_tree.h"
+#include "gn/scope.h"
+#include "gn/value.h"
+
+namespace functions {
+
+const char kFilterExclude[] = "filter_exclude";
+const char kFilterExclude_HelpShort[] =
+    "filter_exclude: Remove values that match a set of patterns.";
+const char kFilterExclude_Help[] =
+    R"(filter_exclude: Remove values that match a set of patterns.
+
+  filter_exclude(values, exclude_patterns)
+
+  The argument values must be a list of strings.
+
+  The argument exclude_patterns must be a list of file patterns (see
+  "gn help file_pattern"). Any elements in values matching at least one
+  of those patterns will be excluded.
+
+Examples
+  values = [ "foo.cc", "foo.h", "foo.proto" ]
+  result = filter_exclude(values, [ "*.proto" ])
+  # result will be [ "foo.cc", "foo.h" ]
+)";
+
+const char kFilterInclude[] = "filter_include";
+const char kFilterInclude_HelpShort[] =
+    "filter_include: Remove values that do not match a set of patterns.";
+const char kFilterInclude_Help[] =
+    R"(filter_include: Remove values that do not match a set of patterns.
+
+  filter_include(values, include_patterns)
+
+  The argument values must be a list of strings.
+
+  The argument include_patterns must be a list of file patterns (see
+  "gn help file_pattern"). Only elements from values matching at least
+  one of the pattern will be included.
+
+Examples
+  values = [ "foo.cc", "foo.h", "foo.proto" ]
+  result = filter_include(values, [ "*.proto" ])
+  # result will be [ "foo.proto" ]
+)";
+
+namespace {
+
+enum FilterSelection {
+  kExcludeFilter,
+  kIncludeFilter,
+};
+
+Value RunFilter(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();
+  }
+
+  // Extract "values".
+  if (args[0].type() != Value::LIST) {
+    *err = Err(args[0], "First argument must be a list of strings.");
+    return Value();
+  }
+
+  // Extract "patterns".
+  PatternList patterns;
+  patterns.SetFromValue(args[1], err);
+  if (err->has_error())
+    return Value();
+
+  Value result(function, Value::LIST);
+  for (const auto& value : args[0].list_value()) {
+    if (value.type() != Value::STRING) {
+      *err = Err(args[0], "First argument must be a list of strings.");
+      return Value();
+    }
+
+    const bool matches_pattern = patterns.MatchesValue(value);
+    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;
+}
+
+}  // anonymous namespace
+
+Value RunFilterExclude(Scope* scope,
+                       const FunctionCallNode* function,
+                       const std::vector<Value>& args,
+                       Err* err) {
+  return RunFilter(scope, function, args, kExcludeFilter, err);
+}
+
+Value RunFilterInclude(Scope* scope,
+                       const FunctionCallNode* function,
+                       const std::vector<Value>& args,
+                       Err* err) {
+  return RunFilter(scope, function, args, kIncludeFilter, err);
+}
+
+}  // namespace functions
diff --git a/src/gn/function_filter_unittest.cc b/src/gn/function_filter_unittest.cc
new file mode 100644
index 0000000..4269f18
--- /dev/null
+++ b/src/gn/function_filter_unittest.cc
@@ -0,0 +1,244 @@
+// 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(FilterExcludeTest, Filter) {
+  TestWithScope setup;
+
+  Value values(nullptr, Value::LIST);
+  values.list_value().push_back(Value(nullptr, "foo.cc"));
+  values.list_value().push_back(Value(nullptr, "foo.h"));
+  values.list_value().push_back(Value(nullptr, "foo.proto"));
+
+  Value patterns(nullptr, Value::LIST);
+  patterns.list_value().push_back(Value(nullptr, "*.proto"));
+
+  FunctionCallNode function;
+  std::vector<Value> args = {values, patterns};
+
+  Err err;
+  Value result =
+      functions::RunFilterExclude(setup.scope(), &function, args, &err);
+  ASSERT_FALSE(err.has_error());
+  ASSERT_EQ(result.type(), Value::LIST);
+  ASSERT_EQ(result.list_value().size(), 2);
+  EXPECT_EQ(result.list_value()[0].type(), Value::STRING);
+  EXPECT_EQ(result.list_value()[0].string_value(), "foo.cc");
+  EXPECT_EQ(result.list_value()[1].type(), Value::STRING);
+  EXPECT_EQ(result.list_value()[1].string_value(), "foo.h");
+}
+
+TEST(FilterExcludeTest, NotEnoughArguments) {
+  TestWithScope setup;
+
+  Value values(nullptr, Value::LIST);
+  values.list_value().push_back(Value(nullptr, "foo.cc"));
+  values.list_value().push_back(Value(nullptr, "foo.h"));
+  values.list_value().push_back(Value(nullptr, "foo.proto"));
+
+  FunctionCallNode function;
+  std::vector<Value> args = {values};
+
+  Err err;
+  Value result =
+      functions::RunFilterExclude(setup.scope(), &function, args, &err);
+  EXPECT_TRUE(err.has_error());
+}
+
+TEST(FilterExcludeTest, TooManyArguments) {
+  TestWithScope setup;
+
+  Value values(nullptr, Value::LIST);
+  values.list_value().push_back(Value(nullptr, "foo.cc"));
+  values.list_value().push_back(Value(nullptr, "foo.h"));
+  values.list_value().push_back(Value(nullptr, "foo.proto"));
+
+  Value patterns(nullptr, Value::LIST);
+  patterns.list_value().push_back(Value(nullptr, "*"));
+
+  Value extra_argument(nullptr, Value::LIST);
+
+  FunctionCallNode function;
+  std::vector<Value> args = {values, patterns, extra_argument};
+
+  Err err;
+  Value result =
+      functions::RunFilterExclude(setup.scope(), &function, args, &err);
+  EXPECT_TRUE(err.has_error());
+}
+
+TEST(FilterExcludeTest, IncorrectValuesType) {
+  TestWithScope setup;
+
+  Value values(nullptr, "foo.cc");
+
+  Value patterns(nullptr, Value::LIST);
+  patterns.list_value().push_back(Value(nullptr, "*"));
+
+  FunctionCallNode function;
+  std::vector<Value> args = {values, patterns};
+
+  Err err;
+  Value result =
+      functions::RunFilterExclude(setup.scope(), &function, args, &err);
+  EXPECT_TRUE(err.has_error());
+}
+
+TEST(FilterExcludeTest, IncorrectValuesElementType) {
+  TestWithScope setup;
+
+  Value values(nullptr, Value::LIST);
+  values.list_value().push_back(Value(nullptr, Value::LIST));
+
+  Value patterns(nullptr, Value::LIST);
+  patterns.list_value().push_back(Value(nullptr, "*"));
+
+  FunctionCallNode function;
+  std::vector<Value> args = {values, patterns};
+
+  Err err;
+  Value result =
+      functions::RunFilterExclude(setup.scope(), &function, args, &err);
+  EXPECT_TRUE(err.has_error());
+}
+
+TEST(FilterExcludeTest, IncorrectPatternsType) {
+  TestWithScope setup;
+
+  Value values(nullptr, Value::LIST);
+  values.list_value().push_back(Value(nullptr, "foo.cc"));
+  values.list_value().push_back(Value(nullptr, "foo.h"));
+  values.list_value().push_back(Value(nullptr, "foo.proto"));
+
+  Value patterns(nullptr, "foo.cc");
+
+  FunctionCallNode function;
+  std::vector<Value> args = {values, patterns};
+
+  Err err;
+  Value result =
+      functions::RunFilterExclude(setup.scope(), &function, args, &err);
+  EXPECT_TRUE(err.has_error());
+}
+
+TEST(FilterIncludeTest, Filter) {
+  TestWithScope setup;
+
+  Value values(nullptr, Value::LIST);
+  values.list_value().push_back(Value(nullptr, "foo.cc"));
+  values.list_value().push_back(Value(nullptr, "foo.h"));
+  values.list_value().push_back(Value(nullptr, "foo.proto"));
+
+  Value patterns(nullptr, Value::LIST);
+  patterns.list_value().push_back(Value(nullptr, "*.proto"));
+
+  FunctionCallNode function;
+  std::vector<Value> args = {values, patterns};
+
+  Err err;
+  Value result =
+      functions::RunFilterInclude(setup.scope(), &function, args, &err);
+  ASSERT_FALSE(err.has_error());
+  ASSERT_EQ(result.type(), Value::LIST);
+  ASSERT_EQ(result.list_value().size(), 1);
+  EXPECT_EQ(result.list_value()[0].type(), Value::STRING);
+  EXPECT_EQ(result.list_value()[0].string_value(), "foo.proto");
+}
+
+TEST(FilterIncludeTest, NotEnoughArguments) {
+  TestWithScope setup;
+
+  Value values(nullptr, Value::LIST);
+  values.list_value().push_back(Value(nullptr, "foo.cc"));
+  values.list_value().push_back(Value(nullptr, "foo.h"));
+  values.list_value().push_back(Value(nullptr, "foo.proto"));
+
+  FunctionCallNode function;
+  std::vector<Value> args = {values};
+
+  Err err;
+  Value result =
+      functions::RunFilterInclude(setup.scope(), &function, args, &err);
+  EXPECT_TRUE(err.has_error());
+}
+
+TEST(FilterIncludeTest, TooManyArguments) {
+  TestWithScope setup;
+
+  Value values(nullptr, Value::LIST);
+  values.list_value().push_back(Value(nullptr, "foo.cc"));
+  values.list_value().push_back(Value(nullptr, "foo.h"));
+  values.list_value().push_back(Value(nullptr, "foo.proto"));
+
+  Value patterns(nullptr, Value::LIST);
+  patterns.list_value().push_back(Value(nullptr, "*"));
+
+  Value extra_argument(nullptr, Value::LIST);
+
+  FunctionCallNode function;
+  std::vector<Value> args = {values, patterns, extra_argument};
+
+  Err err;
+  Value result =
+      functions::RunFilterInclude(setup.scope(), &function, args, &err);
+  EXPECT_TRUE(err.has_error());
+}
+
+TEST(FilterIncludeTest, IncorrectValuesType) {
+  TestWithScope setup;
+
+  Value values(nullptr, "foo.cc");
+
+  Value patterns(nullptr, Value::LIST);
+  patterns.list_value().push_back(Value(nullptr, "*"));
+
+  FunctionCallNode function;
+  std::vector<Value> args = {values, patterns};
+
+  Err err;
+  Value result =
+      functions::RunFilterInclude(setup.scope(), &function, args, &err);
+  EXPECT_TRUE(err.has_error());
+}
+
+TEST(FilterIncludeTest, IncorrectValuesElementType) {
+  TestWithScope setup;
+
+  Value values(nullptr, Value::LIST);
+  values.list_value().push_back(Value(nullptr, Value::LIST));
+
+  Value patterns(nullptr, Value::LIST);
+  patterns.list_value().push_back(Value(nullptr, "*"));
+
+  FunctionCallNode function;
+  std::vector<Value> args = {values, patterns};
+
+  Err err;
+  Value result =
+      functions::RunFilterInclude(setup.scope(), &function, args, &err);
+  EXPECT_TRUE(err.has_error());
+}
+
+TEST(FilterIncludeTest, IncorrectPatternsType) {
+  TestWithScope setup;
+
+  Value values(nullptr, Value::LIST);
+  values.list_value().push_back(Value(nullptr, "foo.cc"));
+  values.list_value().push_back(Value(nullptr, "foo.h"));
+  values.list_value().push_back(Value(nullptr, "foo.proto"));
+
+  Value patterns(nullptr, "foo.cc");
+
+  FunctionCallNode function;
+  std::vector<Value> args = {values, patterns};
+
+  Err err;
+  Value result =
+      functions::RunFilterInclude(setup.scope(), &function, args, &err);
+  EXPECT_TRUE(err.has_error());
+}
diff --git a/src/gn/functions.cc b/src/gn/functions.cc
index abbf188..314173b 100644
--- a/src/gn/functions.cc
+++ b/src/gn/functions.cc
@@ -828,38 +828,9 @@
 
   If you want to bypass the filter and add a file even if it might be filtered
   out, call set_sources_assignment_filter([]) to clear the list of filters.
-  This will apply until the current scope exits
+  This will apply until the current scope exits.
 
-How to use patterns
-
-  File patterns are VERY limited regular expressions. They must match the
-  entire input string to be counted as a match. In regular expression parlance,
-  there is an implicit "^...$" surrounding your input. If you want to match a
-  substring, you need to use wildcards at the beginning and end.
-
-  There are only two special tokens understood by the pattern matcher.
-  Everything else is a literal.
-
-   - "*" Matches zero or more of any character. It does not depend on the
-     preceding character (in regular expression parlance it is equivalent to
-     ".*").
-
-   - "\b" Matches a path boundary. This will match the beginning or end of a
-     string, or a slash.
-
-Pattern examples
-
-  "*asdf*"
-      Matches a string containing "asdf" anywhere.
-
-  "asdf"
-      Matches only the exact string "asdf".
-
-  "*.cc"
-      Matches strings ending in the literal ".cc".
-
-  "\bwin/*"
-      Matches "win/foo" and "foo/win/bar.cc" but not "iwin/foo".
+  See "gn help file_pattern" for more information on file pattern.
 
 Sources assignment example
 
@@ -1459,6 +1430,8 @@
     INSERT_FUNCTION(DeclareArgs, false)
     INSERT_FUNCTION(Defined, false)
     INSERT_FUNCTION(ExecScript, false)
+    INSERT_FUNCTION(FilterExclude, false)
+    INSERT_FUNCTION(FilterInclude, false)
     INSERT_FUNCTION(ForEach, false)
     INSERT_FUNCTION(ForwardVariablesFrom, false)
     INSERT_FUNCTION(GetEnv, false)
diff --git a/src/gn/functions.h b/src/gn/functions.h
index 5027559..dbcce39 100644
--- a/src/gn/functions.h
+++ b/src/gn/functions.h
@@ -146,6 +146,22 @@
                     BlockNode* block,
                     Err* err);
 
+extern const char kFilterExclude[];
+extern const char kFilterExclude_HelpShort[];
+extern const char kFilterExclude_Help[];
+Value RunFilterExclude(Scope* scope,
+                       const FunctionCallNode* function,
+                       const std::vector<Value>& args,
+                       Err* err);
+
+extern const char kFilterInclude[];
+extern const char kFilterInclude_HelpShort[];
+extern const char kFilterInclude_Help[];
+Value RunFilterInclude(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[];
diff --git a/src/gn/pattern.cc b/src/gn/pattern.cc
index da3f134..cf03bab 100644
--- a/src/gn/pattern.cc
+++ b/src/gn/pattern.cc
@@ -6,6 +6,40 @@
 
 #include "gn/value.h"
 
+const char kFilePattern_Help[] =
+    R"*(File patterns
+
+  File patterns are VERY limited regular expressions. They must match the
+  entire input string to be counted as a match. In regular expression parlance,
+  there is an implicit "^...$" surrounding your input. If you want to match a
+  substring, you need to use wildcards at the beginning and end.
+
+  There are only two special tokens understood by the pattern matcher.
+  Everything else is a literal.
+
+   - "*" Matches zero or more of any character. It does not depend on the
+     preceding character (in regular expression parlance it is equivalent to
+     ".*").
+
+   - "\b" Matches a path boundary. This will match the beginning or end of a
+     string, or a slash.
+
+Pattern examples
+
+  "*asdf*"
+      Matches a string containing "asdf" anywhere.
+
+  "asdf"
+      Matches only the exact string "asdf".
+
+  "*.cc"
+      Matches strings ending in the literal ".cc".
+
+  "\bwin/*"
+      Matches "win/foo" and "foo/win/bar.cc" but not "iwin/foo".
+
+)*";
+
 namespace {
 
 void ParsePattern(const std::string& s, std::vector<Pattern::Subrange>* out) {
diff --git a/src/gn/pattern.h b/src/gn/pattern.h
index a176206..f833ca4 100644
--- a/src/gn/pattern.h
+++ b/src/gn/pattern.h
@@ -12,6 +12,8 @@
 
 #include "gn/value.h"
 
+extern const char kFilePattern_Help[];
+
 class Pattern {
  public:
   struct Subrange {