Add `root_patterns` list to build configuration.

This CL adds a new optional `root_patterns` argument in
the top-level `.gn` file, as well as corresponding
`--root-pattern=PATTERN` command-line arguments.

This can be used to restrict the list of targets that
are added to the build graph when BUILD.gn files are evaluated
in the default toolchain.

The default behavior, is to add all targets defined in
the BUILD.gn files that are evaluated in the default toolchain,
including all their transitive dependencies.

When a non-empty list of patterns is used, these will be used
to filter the targets by their label, in the default toolchain
(it is thus an error to specify a toolchain suffix in these
patterns).

For example, on a complex Fuchsia build plan, using
`gn gen out/default --root-target=//:*` changes `gn gen`
time from 18s to 6s, reducing the generated Ninja file
count from 21907 to 8376 files, which reduces the Ninja
startup time by ... seconds.

Change-Id: Ic727b64e8d899d0dd88113576c1c8ae6e035d326
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/16060
Commit-Queue: David Turner <digit@google.com>
Reviewed-by: Takuto Ikuta <tikuta@google.com>
diff --git a/docs/reference.md b/docs/reference.md
index fddf244..fb886f5 100644
--- a/docs/reference.md
+++ b/docs/reference.md
@@ -7060,6 +7060,18 @@
       The command-line switch --root-target will override this value (see "gn
       help --root-target").
 
+  root_patterns [optional]
+      A list of label pattern strings. When not defined or empty, the GN build
+      graph will contain all targets from any BUILD.gn evaluated in the default
+      toolchain context, and their transitive dependencies.
+
+      When set to a non empty list, only the targets in the default toolchain
+      matching these patterns, and their transitive dependencies, will be defined
+      instead.
+
+      The command-line switch --root-pattern will override this value (see
+      "gn help --root-pattern")
+
   script_executable [optional]
       By default, GN runs the scripts used in action targets and exec_script
       calls using the Python interpreter found in PATH. This value specifies the
diff --git a/src/gn/build_settings.cc b/src/gn/build_settings.cc
index 22c1145..b364ea2 100644
--- a/src/gn/build_settings.cc
+++ b/src/gn/build_settings.cc
@@ -27,6 +27,10 @@
   root_target_label_ = r;
 }
 
+void BuildSettings::SetRootPatterns(std::vector<LabelPattern>&& patterns) {
+  root_patterns_ = std::move(patterns);
+}
+
 void BuildSettings::SetRootPath(const base::FilePath& r) {
   DCHECK(r.value()[r.value().size() - 1] != base::FilePath::kSeparators[0]);
   root_path_ = r.NormalizePathSeparatorsTo('/');
diff --git a/src/gn/build_settings.h b/src/gn/build_settings.h
index 6c925e9..82f11db 100644
--- a/src/gn/build_settings.h
+++ b/src/gn/build_settings.h
@@ -14,6 +14,7 @@
 #include "base/files/file_path.h"
 #include "gn/args.h"
 #include "gn/label.h"
+#include "gn/label_pattern.h"
 #include "gn/scope.h"
 #include "gn/source_dir.h"
 #include "gn/source_file.h"
@@ -35,6 +36,12 @@
   const Label& root_target_label() const { return root_target_label_; }
   void SetRootTargetLabel(const Label& r);
 
+  // Root target label patterns.
+  const std::vector<LabelPattern>& root_patterns() const {
+    return root_patterns_;
+  }
+  void SetRootPatterns(std::vector<LabelPattern>&& root_patterns);
+
   // Absolute path of the source root on the local system. Everything is
   // relative to this. Does not end in a [back]slash.
   const base::FilePath& root_path() const { return root_path_; }
@@ -135,6 +142,7 @@
 
  private:
   Label root_target_label_;
+  std::vector<LabelPattern> root_patterns_;
   base::FilePath dotfile_name_;
   base::FilePath root_path_;
   std::string root_path_utf8_;
diff --git a/src/gn/builder.cc b/src/gn/builder.cc
index 81335f8..0ba827b 100644
--- a/src/gn/builder.cc
+++ b/src/gn/builder.cc
@@ -263,7 +263,7 @@
   // check if this target was previously marked as "required" and force setting
   // the bit again so the target's dependencies (which we now know) get the
   // required bit pushed to them.
-  if (record->should_generate() || target->settings()->is_default())
+  if (record->should_generate() || target->ShouldGenerate())
     RecursiveSetShouldGenerate(record, true);
 
   return true;
diff --git a/src/gn/filesystem_utils.cc b/src/gn/filesystem_utils.cc
index d0a1ee0..d0ad23c 100644
--- a/src/gn/filesystem_utils.cc
+++ b/src/gn/filesystem_utils.cc
@@ -13,6 +13,7 @@
 #include "gn/location.h"
 #include "gn/settings.h"
 #include "gn/source_dir.h"
+#include "gn/target.h"
 #include "util/build_config.h"
 
 #if defined(OS_WIN)
diff --git a/src/gn/setup.cc b/src/gn/setup.cc
index 68acd43..b83e904 100644
--- a/src/gn/setup.cc
+++ b/src/gn/setup.cc
@@ -148,6 +148,18 @@
       The command-line switch --root-target will override this value (see "gn
       help --root-target").
 
+  root_patterns [optional]
+      A list of label pattern strings. When not defined or empty, the GN build
+      graph will contain all targets from any BUILD.gn evaluated in the default
+      toolchain context, and their transitive dependencies.
+
+      When set to a non empty list, only the targets in the default toolchain
+      matching these patterns, and their transitive dependencies, will be defined
+      instead.
+
+      The command-line switch --root-pattern will override this value (see
+      "gn help --root-pattern")
+
   script_executable [optional]
       By default, GN runs the scripts used in action targets and exec_script
       calls using the Python interpreter found in PATH. This value specifies the
@@ -878,6 +890,7 @@
 bool Setup::FillOtherConfig(const base::CommandLine& cmdline, Err* err) {
   SourceDir current_dir("//");
   Label root_target_label(current_dir, "");
+  std::vector<LabelPattern> root_patterns;
 
   // Secondary source path, read from the config file if present.
   // Read from the config file if present.
@@ -955,10 +968,63 @@
       }
     }
   }
+
+  if (cmdline.HasSwitch(switches::kRootPattern)) {
+    auto& switches = cmdline.GetSwitches();
+    for (auto it = switches.find(switches::kRootPattern);
+         it != switches.end() && it->first == switches::kRootPattern; ++it) {
+      std::string pattern = base::CommandLine::StringTypeToUTF8(it->second);
+      LabelPattern pat = LabelPattern::GetPattern(
+          SourceDir("//"), build_settings_.root_path_utf8(),
+          Value(nullptr, pattern), err);
+      if (err->has_error()) {
+        err->AppendSubErr(
+            Err(Location(),
+                "for the command-line switch --root-pattern=" + pattern));
+        return false;
+      }
+      if (!pat.toolchain().is_null()) {
+        *err = Err(Location(),
+                   "Root pattern cannot have toolchain suffix: " + pattern);
+        return false;
+      }
+      root_patterns.push_back(std::move(pat));
+    }
+    // Ensure GN does not complain about the .gn root_patterns value being
+    // ignored if it is set.
+    (void)dotfile_scope_.GetValue("root_patterns", true);
+  } else {
+    const Value* root_patterns_value =
+        dotfile_scope_.GetValue("root_patterns", true);
+    if (root_patterns_value) {
+      if (!root_patterns_value->VerifyTypeIs(Value::LIST, err)) {
+        return false;
+      }
+      for (const auto& pattern_value : root_patterns_value->list_value()) {
+        if (!pattern_value.VerifyTypeIs(Value::STRING, err))
+          return false;
+
+        LabelPattern pat = LabelPattern::GetPattern(
+            SourceDir("//"), build_settings_.root_path_utf8(), pattern_value,
+            err);
+        if (err->has_error())
+          return false;
+        if (!pat.toolchain().is_null()) {
+          *err =
+              Err(pattern_value, "Root pattern cannot have toolchain suffix: " +
+                                     pattern_value.string_value());
+          return false;
+        }
+        root_patterns.push_back(std::move(pat));
+      }
+    }
+  }
+
   // Set the root build file here in order to take into account the values of
   // "build_file_extension" and "root".
   root_build_file_ = loader_->BuildFileForLabel(root_target_label);
   build_settings_.SetRootTargetLabel(root_target_label);
+  build_settings_.SetRootPatterns(std::move(root_patterns));
 
   // Build config file.
   const Value* build_config_value =
diff --git a/src/gn/setup_unittest.cc b/src/gn/setup_unittest.cc
index b46bf94..082af40 100644
--- a/src/gn/setup_unittest.cc
+++ b/src/gn/setup_unittest.cc
@@ -8,6 +8,7 @@
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
+#include "gn/builder_record.h"
 #include "gn/filesystem_utils.h"
 #include "gn/switches.h"
 #include "gn/test_with_scheduler.h"
@@ -196,3 +197,166 @@
   EXPECT_EQ("//tools:doom_melon", export_cc[1].Describe());
   EXPECT_EQ("//src/gn:*", export_cc[2].Describe());
 }
+
+TEST_F(SetupTest, RootPatternsInGnConfig) {
+  base::CommandLine cmdline(base::CommandLine::NO_PROGRAM);
+
+  // Provide a default root pattern for all top-level targets from //BUILD.gn
+  const char kDotfileContents[] = R"(
+buildconfig = "//BUILDCONFIG.gn"
+root_patterns = [ "//:*" ]
+)";
+
+  // Create a temp directory containing the build.
+  base::ScopedTempDir in_temp_dir;
+  ASSERT_TRUE(in_temp_dir.CreateUniqueTempDir());
+  base::FilePath in_path = in_temp_dir.GetPath();
+
+  WriteFile(in_path.Append(FILE_PATH_LITERAL("BUILDCONFIG.gn")), "");
+  WriteFile(in_path.Append(FILE_PATH_LITERAL(".gn")), kDotfileContents);
+
+  cmdline.AppendSwitch(switches::kRoot, FilePathToUTF8(in_path));
+
+  // Create another temp dir for writing the generated files to.
+  base::ScopedTempDir build_temp_dir;
+  ASSERT_TRUE(build_temp_dir.CreateUniqueTempDir());
+
+  // Run setup and check that the .gn file is in the scheduler's gen deps.
+  Setup setup;
+  Err err;
+  EXPECT_TRUE(setup.DoSetupWithErr(FilePathToUTF8(build_temp_dir.GetPath()),
+                                   true, cmdline, &err));
+
+  const std::vector<LabelPattern>& root_patterns =
+      setup.build_settings().root_patterns();
+  ASSERT_EQ(1u, root_patterns.size());
+  EXPECT_EQ("//.:*", root_patterns[0].Describe());
+}
+
+TEST_F(SetupTest, RootPatternsOnCommandLineOverrideGnConfig) {
+  base::CommandLine cmdline(base::CommandLine::NO_PROGRAM);
+
+  // Provide a default root pattern for only //:foo
+  const char kDotfileContents[] = R"(
+buildconfig = "//BUILDCONFIG.gn"
+root_patterns = [ "//:foo" ]
+)";
+
+  // Create a temp directory containing the build.
+  base::ScopedTempDir in_temp_dir;
+  ASSERT_TRUE(in_temp_dir.CreateUniqueTempDir());
+  base::FilePath in_path = in_temp_dir.GetPath();
+
+  WriteFile(in_path.Append(FILE_PATH_LITERAL("BUILDCONFIG.gn")), "");
+  WriteFile(in_path.Append(FILE_PATH_LITERAL(".gn")), kDotfileContents);
+
+  cmdline.AppendSwitch(switches::kRoot, FilePathToUTF8(in_path));
+
+  // Override the default root pattern list.
+  cmdline.AppendSwitch(switches::kRootPattern, "//:bar");
+  cmdline.AppendSwitch(switches::kRootPattern, "//:qux");
+
+  // Create another temp dir for writing the generated files to.
+  base::ScopedTempDir build_temp_dir;
+  ASSERT_TRUE(build_temp_dir.CreateUniqueTempDir());
+
+  // Run setup and check that the .gn file is in the scheduler's gen deps.
+  Setup setup;
+  Err err;
+  EXPECT_TRUE(setup.DoSetupWithErr(FilePathToUTF8(build_temp_dir.GetPath()),
+                                   true, cmdline, &err));
+
+  const std::vector<LabelPattern>& root_patterns =
+      setup.build_settings().root_patterns();
+  ASSERT_EQ(2u, root_patterns.size());
+  EXPECT_EQ("//.:bar", root_patterns[0].Describe());
+  EXPECT_EQ("//.:qux", root_patterns[1].Describe());
+}
+
+TEST_F(SetupTest, RootPatternsFiltersPatterns) {
+  base::CommandLine cmdline(base::CommandLine::NO_PROGRAM);
+
+  const char kDotfileContents[] = R"(
+buildconfig = "//BUILDCONFIG.gn"
+root_patterns = [ "//:foo" ]
+)";
+
+  const char kBuildConfigContents[] = R"(
+set_default_toolchain("//:toolchain")
+)";
+
+  const char kBuildGnContents[] = R"(
+group("foo") {
+  deps = [ ":bar" ]
+}
+
+group("bar") {
+}
+
+group("zoo") {
+}
+
+group("qux") {
+}
+
+# Minimal default toolchain definition for this test. Non-functional.
+toolchain("toolchain") {
+  tool("stamp") {
+    command = "stamp"
+  }
+}
+)";
+
+  // Create a temp directory containing the build.
+  base::ScopedTempDir in_temp_dir;
+  ASSERT_TRUE(in_temp_dir.CreateUniqueTempDir());
+  base::FilePath in_path = in_temp_dir.GetPath();
+
+  WriteFile(in_path.Append(FILE_PATH_LITERAL("BUILD.gn")), kBuildGnContents);
+  WriteFile(in_path.Append(FILE_PATH_LITERAL("BUILDCONFIG.gn")),
+            kBuildConfigContents);
+  WriteFile(in_path.Append(FILE_PATH_LITERAL(".gn")), kDotfileContents);
+
+  cmdline.AppendSwitch(switches::kRoot, FilePathToUTF8(in_path));
+
+  // Create another temp dir for writing the generated files to.
+  base::ScopedTempDir build_temp_dir;
+  ASSERT_TRUE(build_temp_dir.CreateUniqueTempDir());
+
+  // Run setup and check that the .gn file is in the scheduler's gen deps.
+  Setup setup;
+  Err err;
+  EXPECT_TRUE(setup.DoSetupWithErr(FilePathToUTF8(build_temp_dir.GetPath()),
+                                   true, cmdline, &err));
+
+  const std::vector<LabelPattern>& root_patterns =
+      setup.build_settings().root_patterns();
+  ASSERT_EQ(1u, root_patterns.size());
+  EXPECT_EQ("//.:foo", root_patterns[0].Describe());
+
+  // Now build the graph, then verify it only includes //:foo and //:bar
+  ASSERT_TRUE(setup.Run(cmdline));
+
+  SourceDir top_dir("//");
+
+  const BuilderRecord* foo_record =
+      setup.builder().GetRecord(Label(top_dir, "foo", top_dir, "toolchain"));
+  const BuilderRecord* bar_record =
+      setup.builder().GetRecord(Label(top_dir, "bar", top_dir, "toolchain"));
+  const BuilderRecord* qux_record =
+      setup.builder().GetRecord(Label(top_dir, "qux", top_dir, "toolchain"));
+  const BuilderRecord* zoo_record =
+      setup.builder().GetRecord(Label(top_dir, "zoo", top_dir, "toolchain"));
+
+  // All four targets were added as build graph records.
+  ASSERT_TRUE(foo_record);
+  ASSERT_TRUE(bar_record);
+  ASSERT_TRUE(zoo_record);
+  ASSERT_TRUE(qux_record);
+
+  // But only foo and bar should be generated in the Ninja plan.
+  EXPECT_TRUE(foo_record->should_generate());
+  EXPECT_TRUE(bar_record->should_generate());
+  EXPECT_FALSE(qux_record->should_generate());
+  EXPECT_FALSE(zoo_record->should_generate());
+}
diff --git a/src/gn/switches.cc b/src/gn/switches.cc
index d86c349..95b0b14 100644
--- a/src/gn/switches.cc
+++ b/src/gn/switches.cc
@@ -182,6 +182,34 @@
   gn gen //out/Default --root-target="//third_party/grpc"
 )";
 
+const char kRootPattern[] = "root-pattern";
+const char kRootPattern_HelpShort[] =
+    "--root-pattern: Add root pattern override.";
+const char kRootPattern_Help[] =
+    R"(--root-pattern: Add root pattern override.
+
+  The root patterns is a list of label patterns used to control which
+  targets are defined when evaluating BUILD.gn files in the default toolchain.
+
+  The list is empty by default, meaning that all targets defined in all
+  BUILD.gn files evaluated in the default toolchain will be added to the
+  final GN build graph (GN's default behavior for historical reasons).
+
+  When this list is not empty, only targets matching any of the root patterns,
+  as well as their transitive dependencies, will be defined in the default
+  toolchain instead. This is a way to restrict the size of the final build graph
+  for projects with a very large number of target definitions per BUILD.gn file.
+
+  Using --root-pattern overrides the root_patterns value specified in the .gn file.
+
+  The argument must be a GN label pattern, and each --root-pattern=<pattern>
+  on the command-line will append a pattern to the list.
+
+Example
+
+  gn gen //out/Default --root-pattern="//:*"
+)";
+
 const char kRuntimeDepsListFile[] = "runtime-deps-list-file";
 const char kRuntimeDepsListFile_HelpShort[] =
     "--runtime-deps-list-file: Save runtime dependencies for targets in file.";
diff --git a/src/gn/switches.h b/src/gn/switches.h
index 3d1943f..57d3828 100644
--- a/src/gn/switches.h
+++ b/src/gn/switches.h
@@ -70,6 +70,10 @@
 extern const char kRootTarget_HelpShort[];
 extern const char kRootTarget_Help[];
 
+extern const char kRootPattern[];
+extern const char kRootPattern_HelpShort[];
+extern const char kRootPattern_Help[];
+
 extern const char kRuntimeDepsListFile[];
 extern const char kRuntimeDepsListFile_HelpShort[];
 extern const char kRuntimeDepsListFile_Help[];
diff --git a/src/gn/target.cc b/src/gn/target.cc
index da82b95..a6440f1 100644
--- a/src/gn/target.cc
+++ b/src/gn/target.cc
@@ -544,6 +544,15 @@
   return output_type_ == BUNDLE_DATA;
 }
 
+bool Target::ShouldGenerate() const {
+  const auto& root_patterns = settings()->build_settings()->root_patterns();
+  if (root_patterns.empty()) {
+    // By default, generate all targets that belong to the default toolchain.
+    return settings()->is_default();
+  }
+  return LabelPattern::VectorMatches(root_patterns, label());
+}
+
 DepsIteratorRange Target::GetDeps(DepsIterationType type) const {
   if (type == DEPS_LINKED) {
     return DepsIteratorRange(
diff --git a/src/gn/target.h b/src/gn/target.h
index 806d14b..b839277 100644
--- a/src/gn/target.h
+++ b/src/gn/target.h
@@ -99,6 +99,9 @@
   // increases parallelism.
   bool IsDataOnly() const;
 
+  // Return true if this target should be generated in the final build graph.
+  bool ShouldGenerate() const;
+
   // Will be the empty string to use the target label as the output name.
   // See GetComputedOutputName().
   const std::string& output_name() const { return output_name_; }