Add `--filter-with-data` flag for `gn gen --ide=json`

Change-Id: Id043d19973cdc26622faf510e571c5383a2e5b26
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/18660
Reviewed-by: David Turner <digit@google.com>
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 f8c43a3..91521b7 100644
--- a/docs/reference.md
+++ b/docs/reference.md
@@ -919,6 +919,10 @@
 
   --json-ide-script-args=<argument>
       Optional second argument that will be passed to executed script.
+
+  --filter-with-data
+      Additionally follows data deps when filtering. Without this flag, only
+      public and private linked deps will be followed. Only used with --filters.
 ```
 
 #### **Ninja Outputs**
diff --git a/src/gn/command_gen.cc b/src/gn/command_gen.cc
index d3de0ba..3edee43 100644
--- a/src/gn/command_gen.cc
+++ b/src/gn/command_gen.cc
@@ -76,6 +76,7 @@
 const char kSwitchJsonIdeScriptArgs[] = "json-ide-script-args";
 const char kSwitchExportCompileCommands[] = "export-compile-commands";
 const char kSwitchExportRustProject[] = "export-rust-project";
+const char kSwitchFilterWithData[] = "filter-with-data";
 
 // A map type used to implement --ide=ninja_outputs
 using NinjaOutputsMap = NinjaOutputsWriter::MapType;
@@ -359,10 +360,11 @@
     std::string exec_script_extra_args =
         command_line->GetSwitchValueString(kSwitchJsonIdeScriptArgs);
     std::string filters = command_line->GetSwitchValueString(kSwitchFilters);
+    bool filter_with_data = command_line->HasSwitch(kSwitchFilterWithData);
 
     bool res = JSONProjectWriter::RunAndWriteFiles(
         build_settings, builder, file_name, exec_script, exec_script_extra_args,
-        filters, quiet, err);
+        filters, filter_with_data, quiet, err);
     if (res && !quiet) {
       OutputString("Generating JSON projects took " +
                    base::Int64ToString(timer.Elapsed().InMilliseconds()) +
@@ -677,6 +679,10 @@
   --json-ide-script-args=<argument>
       Optional second argument that will be passed to executed script.
 
+  --filter-with-data
+      Additionally follows data deps when filtering. Without this flag, only
+      public and private linked deps will be followed. Only used with --filters.
+
 Ninja Outputs
 
   The --ninja-outputs-file=<FILE> option dumps a JSON file that maps GN labels
diff --git a/src/gn/json_project_writer.cc b/src/gn/json_project_writer.cc
index 1ea06ff..c69f480 100644
--- a/src/gn/json_project_writer.cc
+++ b/src/gn/json_project_writer.cc
@@ -40,21 +40,26 @@
 
 namespace {
 
-void AddTargetDependencies(const Target* target, TargetSet* deps) {
-  for (const auto& pair : target->GetDeps(Target::DEPS_LINKED)) {
+void AddTargetDependencies(const Target* target, TargetSet* deps,
+                           Target::DepsIterationType iteration_type) {
+  for (const auto& pair : target->GetDeps(iteration_type)) {
     if (deps->add(pair.ptr)) {
-      AddTargetDependencies(pair.ptr, deps);
+      AddTargetDependencies(pair.ptr, deps, iteration_type);
     }
   }
 }
 
+}  // namespace
+
 // Filters targets according to filter string; Will also recursively
 // add dependent targets.
-bool FilterTargets(const BuildSettings* build_settings,
-                   std::vector<const Target*>& all_targets,
-                   std::vector<const Target*>* targets,
-                   const std::string& dir_filter_string,
-                   Err* err) {
+bool JSONProjectWriter::FilterTargets(
+    const BuildSettings* build_settings,
+    std::vector<const Target*>& all_targets,
+    std::vector<const Target*>* targets,
+    const std::string& dir_filter_string,
+    bool filter_with_data_deps,
+    Err* err) {
   if (dir_filter_string.empty()) {
     *targets = all_targets;
   } else {
@@ -67,8 +72,10 @@
     commands::FilterTargetsByPatterns(all_targets, filters, targets);
 
     TargetSet target_set(targets->begin(), targets->end());
+    Target::DepsIterationType iteration_type =
+        filter_with_data_deps ? Target::DEPS_ALL : Target::DEPS_LINKED;
     for (const auto* target : *targets)
-      AddTargetDependencies(target, &target_set);
+      AddTargetDependencies(target, &target_set, iteration_type);
 
     targets->assign(target_set.begin(), target_set.end());
   }
@@ -83,8 +90,6 @@
   return true;
 }
 
-}  // namespace
-
 bool JSONProjectWriter::RunAndWriteFiles(
     const BuildSettings* build_settings,
     const Builder& builder,
@@ -92,6 +97,7 @@
     const std::string& exec_script,
     const std::string& exec_script_extra_args,
     const std::string& dir_filter_string,
+    bool filter_with_data_deps,
     bool quiet,
     Err* err) {
   SourceFile output_file = build_settings->build_dir().ResolveRelativeFile(
@@ -104,8 +110,8 @@
 
   std::vector<const Target*> all_targets = builder.GetAllResolvedTargets();
   std::vector<const Target*> targets;
-  if (!FilterTargets(build_settings, all_targets, &targets, dir_filter_string,
-                     err)) {
+  if (!JSONProjectWriter::FilterTargets(build_settings, all_targets, &targets, dir_filter_string,
+                                        filter_with_data_deps, err)) {
     return false;
   }
 
diff --git a/src/gn/json_project_writer.h b/src/gn/json_project_writer.h
index 74293a4..0557689 100644
--- a/src/gn/json_project_writer.h
+++ b/src/gn/json_project_writer.h
@@ -20,6 +20,7 @@
                                const std::string& exec_script,
                                const std::string& exec_script_extra_args,
                                const std::string& dir_filter_string,
+                               bool filter_with_data_deps,
                                bool quiet,
                                Err* err);
 
@@ -27,6 +28,14 @@
   FRIEND_TEST_ALL_PREFIXES(JSONWriter, ActionWithResponseFile);
   FRIEND_TEST_ALL_PREFIXES(JSONWriter, ForEachWithResponseFile);
   FRIEND_TEST_ALL_PREFIXES(JSONWriter, RustTarget);
+  FRIEND_TEST_ALL_PREFIXES(JSONWriter, FilterTargetsWithDataDeps);
+
+  static bool FilterTargets(const BuildSettings* build_settings,
+                            std::vector<const Target*>& all_targets,
+                            std::vector<const Target*>* targets,
+                            const std::string& dir_filter_string,
+                            bool filter_with_data_deps,
+                            Err* err);
 
   static StringOutputBuffer GenerateJSON(
       const BuildSettings* build_settings,
diff --git a/src/gn/json_project_writer_unittest.cc b/src/gn/json_project_writer_unittest.cc
index 0481d1a..be0d05d 100644
--- a/src/gn/json_project_writer_unittest.cc
+++ b/src/gn/json_project_writer_unittest.cc
@@ -780,3 +780,58 @@
 )_";
   EXPECT_EQ(expected_json, out) << out;
 }
+
+TEST_F(JSONWriter, FilterTargetsWithDataDeps) {
+  Err err;
+  TestWithScope setup;
+
+  // Create targets :a, :b, :c
+  Target target_a(setup.settings(), Label(SourceDir("//foo/"), "a"));
+  target_a.set_output_type(Target::STATIC_LIBRARY);
+  target_a.visibility().SetPublic();
+
+  Target target_b(setup.settings(), Label(SourceDir("//foo/"), "b"));
+  target_b.set_output_type(Target::STATIC_LIBRARY);
+  target_b.visibility().SetPublic();
+
+  Target target_c(setup.settings(), Label(SourceDir("//foo/"), "c"));
+  target_c.set_output_type(Target::STATIC_LIBRARY);
+  target_c.visibility().SetPublic();
+
+  // :a depends on :b, and data_depends on :c
+  target_a.private_deps().push_back(LabelTargetPair(&target_b));
+  target_a.data_deps().push_back(LabelTargetPair(&target_c));
+
+  target_a.SetToolchain(setup.toolchain());
+  target_b.SetToolchain(setup.toolchain());
+  target_c.SetToolchain(setup.toolchain());
+
+  ASSERT_TRUE(target_a.OnResolved(&err));
+  ASSERT_TRUE(target_b.OnResolved(&err));
+  ASSERT_TRUE(target_c.OnResolved(&err));
+
+  std::vector<const Target*> all_targets = {&target_a, &target_b, &target_c};
+  std::vector<const Target*> filtered;
+
+  // Only DEPS_LINKED (should include :a and :b, but not :c)
+  ASSERT_TRUE(JSONProjectWriter::FilterTargets(
+      setup.build_settings(), all_targets, &filtered,
+      "//foo:a", /*filter_with_data_deps=*/false, &err));
+  std::set<Label> labels_linked;
+  for (const Target* t : filtered)
+    labels_linked.insert(t->label());
+  EXPECT_GT(labels_linked.count(Label(SourceDir("//foo/"), "a")), 0u);
+  EXPECT_GT(labels_linked.count(Label(SourceDir("//foo/"), "b")), 0u);
+  EXPECT_EQ(labels_linked.count(Label(SourceDir("//foo/"), "c")), 0u);
+
+  // DEPS_ALL (should include :a, :b, :c)
+  ASSERT_TRUE(JSONProjectWriter::FilterTargets(
+      setup.build_settings(), all_targets, &filtered,
+      "//foo:a", /*filter_with_data_deps=*/true, &err));
+  std::set<Label> labels_all;
+  for (const Target* t : filtered)
+    labels_all.insert(t->label());
+  EXPECT_GT(labels_all.count(Label(SourceDir("//foo/"), "a")), 0u);
+  EXPECT_GT(labels_all.count(Label(SourceDir("//foo/"), "b")), 0u);
+  EXPECT_GT(labels_all.count(Label(SourceDir("//foo/"), "c")), 0u);
+}