[compdb] Optionally filter the compilation database.

After this patch, user can use gn gen $OUT --export-compile-command=\
"target1,target2" to optional filter the generated compdb json. With
this option, GN will only generate the targets that are reachable from
targets in this option. If this option is empty, GN will not perform
the filtering.

Bug: BLD-418
Change-Id: I66d4527cd10f6be0fa338b1d0fe1d019a40161b4
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/4560
Reviewed-by: Brett Wilson <brettw@google.com>
Commit-Queue: Brett Wilson <brettw@google.com>
diff --git a/tools/gn/command_gen.cc b/tools/gn/command_gen.cc
index 46d8b89..200aad8 100644
--- a/tools/gn/command_gen.cc
+++ b/tools/gn/command_gen.cc
@@ -294,9 +294,11 @@
   base::ElapsedTimer timer;
 
   std::string file_name = "compile_commands.json";
+  std::string target_filters =
+      command_line->GetSwitchValueASCII(kSwitchExportCompileCommands);
 
-  bool res = CompileCommandsWriter::RunAndWriteFiles(build_settings, builder,
-                                                     file_name, quiet, err);
+  bool res = CompileCommandsWriter::RunAndWriteFiles(
+      build_settings, builder, file_name, target_filters, quiet, err);
   if (res && !quiet) {
     OutputString("Generating compile_commands took " +
                  base::Int64ToString(timer.Elapsed().InMilliseconds()) +
@@ -420,12 +422,15 @@
 
 Compilation Database
 
-  --export-compile-commands
+  --export-compile-commands[=<target_name1,target_name2...>]
       Produces a compile_commands.json file in the root of the build directory
       containing an array of “command objects”, where each command object
-      specifies one way a translation unit is compiled in the project. This is
-      used for various Clang-based tooling, allowing for the replay of individual
-      compilations independent of the build system.
+      specifies one way a translation unit is compiled in the project. If a list
+      of target_name is supplied, only targets that are reachable from the list
+      of target_name will be used for “command objects” generation, otherwise
+      all available targets will be used. This is used for various Clang-based
+      tooling, allowing for the replay of individual compilations independent
+      of the build system.
 )";
 
 int RunGen(const std::vector<std::string>& args) {
diff --git a/tools/gn/compile_commands_writer.cc b/tools/gn/compile_commands_writer.cc
index 0a545e8..00eb00a 100644
--- a/tools/gn/compile_commands_writer.cc
+++ b/tools/gn/compile_commands_writer.cc
@@ -8,8 +8,10 @@
 
 #include "base/json/string_escape.h"
 #include "base/strings/stringprintf.h"
+#include "base/strings/string_split.h"
 #include "tools/gn/builder.h"
 #include "tools/gn/config_values_extractors.h"
+#include "tools/gn/deps_iterator.h"
 #include "tools/gn/escape.h"
 #include "tools/gn/filesystem_utils.h"
 #include "tools/gn/ninja_target_command_util.h"
@@ -267,6 +269,7 @@
     const BuildSettings* build_settings,
     const Builder& builder,
     const std::string& file_name,
+    const std::string& target_filters,
     bool quiet,
     Err* err) {
   SourceFile output_file = build_settings->build_dir().ResolveRelativeFile(
@@ -278,9 +281,56 @@
 
   std::vector<const Target*> all_targets = builder.GetAllResolvedTargets();
 
+  std::set<std::string> target_filters_set;
+  for (auto& target :
+       base::SplitString(target_filters, ",", base::TRIM_WHITESPACE,
+                         base::SPLIT_WANT_NONEMPTY)) {
+    target_filters_set.insert(target);
+  }
   std::string json;
-  RenderJSON(build_settings, all_targets, &json);
+  if (target_filters_set.empty()) {
+    RenderJSON(build_settings, all_targets, &json);
+  } else {
+    std::vector<const Target*> preserved_targets =
+        FilterTargets(all_targets, target_filters_set);
+    RenderJSON(build_settings, preserved_targets, &json);
+  }
+
   if (!WriteFileIfChanged(output_path, json, err))
     return false;
   return true;
+
 }
+
+std::vector<const Target*> CompileCommandsWriter::FilterTargets(
+    const std::vector<const Target*>& all_targets,
+    const std::set<std::string>& target_filters_set) {
+  std::vector<const Target*> preserved_targets;
+
+  std::set<const Target*> visited;
+  for (auto& target : all_targets) {
+    if (target_filters_set.count(target->label().name())) {
+      VisitDeps(target, &visited);
+    }
+  }
+
+  preserved_targets.reserve(visited.size());
+  // Preserve the original ordering of all_targets
+  // to allow easier debugging and testing.
+  for (auto& target : all_targets) {
+    if (visited.count(target)) {
+      preserved_targets.push_back(target);
+    }
+  }
+  return preserved_targets;
+}
+
+void CompileCommandsWriter::VisitDeps(const Target* target,
+                                      std::set<const Target*>* visited) {
+  if (!visited->count(target)) {
+    visited->insert(target);
+    for (const auto& pair : target->GetDeps(Target::DEPS_ALL)) {
+      VisitDeps(pair.ptr, visited);
+    }
+  }
+}
\ No newline at end of file
diff --git a/tools/gn/compile_commands_writer.h b/tools/gn/compile_commands_writer.h
index 2f1ec83..5c9f0d5 100644
--- a/tools/gn/compile_commands_writer.h
+++ b/tools/gn/compile_commands_writer.h
@@ -13,14 +13,29 @@
 
 class CompileCommandsWriter {
  public:
+  // Write compile commands into a json file located by parameter file_name.
+  //
+  // Parameter target_filters should be in "target_name1,target_name2..."
+  // format. If it is not empty, only targets that are reachable from targets
+  // in target_filters are used to generate compile commands.
+  //
+  // Parameter quiet is not used.
   static bool RunAndWriteFiles(const BuildSettings* build_setting,
                                const Builder& builder,
                                const std::string& file_name,
+                               const std::string& target_filters,
                                bool quiet,
                                Err* err);
   static void RenderJSON(const BuildSettings* build_settings,
                          std::vector<const Target*>& all_targets,
                          std::string* compile_commands);
+  static std::vector<const Target*> FilterTargets(
+      const std::vector<const Target*>& all_targets,
+      const std::set<std::string>& target_filters_set);
+
+ private:
+  // This fuction visits the deps graph of a target in a DFS fashion.
+  static void VisitDeps(const Target* target, std::set<const Target*>* visited);
 };
 
 #endif  // TOOLS_GN_COMPILE_COMMANDS_WRITER_H_
diff --git a/tools/gn/compile_commands_writer_unittest.cc b/tools/gn/compile_commands_writer_unittest.cc
index b162ba5..90429a4 100644
--- a/tools/gn/compile_commands_writer_unittest.cc
+++ b/tools/gn/compile_commands_writer_unittest.cc
@@ -588,3 +588,56 @@
 #endif
   EXPECT_EQ(expected, out);
 }
+
+TEST_F(CompileCommandsTest, CompDBFilter) {
+  Err err;
+
+  std::vector<const Target*> targets;
+  Target target1(settings(), Label(SourceDir("//foo/"), "bar1"));
+  target1.set_output_type(Target::SOURCE_SET);
+  target1.sources().push_back(SourceFile("//foo/input1.c"));
+  target1.config_values().cflags_c().push_back("-DCONFIG=\"/config\"");
+  target1.SetToolchain(toolchain());
+  ASSERT_TRUE(target1.OnResolved(&err));
+  targets.push_back(&target1);
+
+  Target target2(settings(), Label(SourceDir("//foo/"), "bar2"));
+  target2.set_output_type(Target::SOURCE_SET);
+  target2.sources().push_back(SourceFile("//foo/input2.c"));
+  target2.config_values().cflags_c().push_back("-DCONFIG=\"/config\"");
+  target2.SetToolchain(toolchain());
+  ASSERT_TRUE(target2.OnResolved(&err));
+  targets.push_back(&target2);
+
+  Target target3(settings(), Label(SourceDir("//foo/"), "bar3"));
+  target3.set_output_type(Target::SOURCE_SET);
+  target3.sources().push_back(SourceFile("//foo/input3.c"));
+  target3.config_values().cflags_c().push_back("-DCONFIG=\"/config\"");
+  target3.SetToolchain(toolchain());
+  ASSERT_TRUE(target3.OnResolved(&err));
+  targets.push_back(&target3);
+
+  target1.private_deps().push_back(LabelTargetPair(&target2));
+  target1.private_deps().push_back(LabelTargetPair(&target3));
+
+  CompileCommandsWriter writer;
+
+  std::set<std::string> filter1;
+  std::vector<const Target*> test_results1 =
+      writer.FilterTargets(targets, filter1);
+  ASSERT_TRUE(test_results1.empty());
+
+  std::set<std::string> filter2;
+  filter2.insert(target1.label().name());
+  std::vector<const Target*> test_results2 =
+      writer.FilterTargets(targets, filter2);
+  ASSERT_EQ(test_results2, targets);
+
+  std::set<std::string> filter3;
+  filter3.insert(target2.label().name());
+  std::vector<const Target*> test_result3 =
+      writer.FilterTargets(targets, filter3);
+  std::vector<const Target*> expected_results3;
+  expected_results3.push_back(&target2);
+  ASSERT_EQ(test_result3, expected_results3);
+}