GN: Do not serialize builds on targets with no public deps.

When binary target A does not have a public header, a binary target B that
depends on A does not need to wait for A's hard deps to finish. But,
in current GN, it does.

Such dependencies unnecessarily serialize the build and sometimes long compile
task significantly impact the overall build speed. This CL deserializes
the dependencies so that B does not wait on A.

This is an implementation of
https://groups.google.com/a/chromium.org/d/msg/gn-dev/1DiphV5ZnqU/c85P4ctvAQAJ

We will use this to optimize the build dependencies, especially for the
dependencies between v8 and chrome.

Bug: 578477
Change-Id: If3dffc68557a365f034e74033fa88b5b7bbdbd53
Reviewed-on: https://chromium-review.googlesource.com/1032320
Commit-Queue: Takuto Ikuta <tikuta@chromium.org>
Reviewed-by: Dirk Pranke <dpranke@chromium.org>
Reviewed-by: Brett Wilson <brettw@chromium.org>
Cr-Original-Commit-Position: refs/heads/master@{#554896}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: 94042177671bb236911fc042b651fa34eb5bd939
diff --git a/tools/gn/docs/reference.md b/tools/gn/docs/reference.md
index 47c743e..6f3682d 100644
--- a/tools/gn/docs/reference.md
+++ b/tools/gn/docs/reference.md
@@ -5143,6 +5143,17 @@
   associated code. In this case, list the test target in the "friend" list of
   the target that owns the private header to allow the inclusion. See
   "gn help friend" for more.
+
+  When a binary target has no explicit or implicit public headers (a "public"
+  list is defined but is empty), GN assumes that the target can not propagate
+  any compile-time dependencies up the dependency tree. In this case, the build
+  can be parallelized more efficiently.
+  Say there are dependencies:
+    A (shared library) -> B (shared library) -> C (action).
+  Normally C must complete before any source files in A can compile (because
+  there might be generated includes). But when B explicitly declares no public
+  headers, C can execute in parallel with A's compile steps. C must still be
+  complete before any dependents link.
 ```
 
 #### **Examples**
@@ -5152,6 +5163,7 @@
     public = [ "foo.h", "bar.h" ]
 
   No files are public (no targets may include headers from this one):
+    # This allows starting compile in dependent targets earlier.
     public = []
 ```
 ### <a name="public_configs"></a>**public_configs**: Configs to be applied on dependents.
diff --git a/tools/gn/ninja_binary_target_writer_unittest.cc b/tools/gn/ninja_binary_target_writer_unittest.cc
index e801723..15b6da9 100644
--- a/tools/gn/ninja_binary_target_writer_unittest.cc
+++ b/tools/gn/ninja_binary_target_writer_unittest.cc
@@ -313,6 +313,131 @@
   EXPECT_EQ(expected, out_str);
 }
 
+TEST_F(NinjaBinaryTargetWriterTest, NoHardDepsToNoPublicHeaderTarget) {
+  Err err;
+  TestWithScope setup;
+
+  SourceFile generated_file("//out/Debug/generated.cc");
+
+  // An action does code generation.
+  Target action(setup.settings(), Label(SourceDir("//foo/"), "generate"));
+  action.set_output_type(Target::ACTION);
+  action.visibility().SetPublic();
+  action.SetToolchain(setup.toolchain());
+  action.set_output_dir(SourceDir("//out/Debug/foo/"));
+  action.action_values().outputs() =
+      SubstitutionList::MakeForTest("//out/Debug/generated.cc");
+  ASSERT_TRUE(action.OnResolved(&err));
+
+  // A source set compiling geneated code, this target does not publicize any
+  // headers.
+  Target gen_obj(setup.settings(), Label(SourceDir("//foo/"), "gen_obj"));
+  gen_obj.set_output_type(Target::SOURCE_SET);
+  gen_obj.set_output_dir(SourceDir("//out/Debug/foo/"));
+  gen_obj.sources().push_back(generated_file);
+  gen_obj.visibility().SetPublic();
+  gen_obj.private_deps().push_back(LabelTargetPair(&action));
+  gen_obj.set_all_headers_public(false);
+  gen_obj.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(gen_obj.OnResolved(&err));
+
+  std::ostringstream obj_out;
+  NinjaBinaryTargetWriter obj_writer(&gen_obj, obj_out);
+  obj_writer.Run();
+
+  const char obj_expected[] =
+      "defines =\n"
+      "include_dirs =\n"
+      "cflags =\n"
+      "cflags_cc =\n"
+      "root_out_dir = .\n"
+      "target_out_dir = obj/foo\n"
+      "target_output_name = gen_obj\n"
+      "\n"
+      "build obj/out/Debug/gen_obj.generated.o: cxx generated.cc"
+      " || obj/foo/generate.stamp\n"
+      "\n"
+      "build obj/foo/gen_obj.stamp: stamp obj/out/Debug/gen_obj.generated.o"
+      // The order-only dependency here is strictly unnecessary since the
+      // sources list this as an order-only dep.
+      " || obj/foo/generate.stamp\n";
+
+  std::string obj_str = obj_out.str();
+  EXPECT_EQ(obj_expected, obj_str);
+
+  // A shared library depends on gen_obj, having corresponding header for
+  // generated obj.
+  Target gen_lib(setup.settings(), Label(SourceDir("//foo/"), "gen_lib"));
+  gen_lib.set_output_type(Target::SHARED_LIBRARY);
+  gen_lib.set_output_dir(SourceDir("//out/Debug/foo/"));
+  gen_lib.sources().push_back(SourceFile("//foor/generated.h"));
+  gen_lib.visibility().SetPublic();
+  gen_lib.private_deps().push_back(LabelTargetPair(&gen_obj));
+  gen_lib.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(gen_lib.OnResolved(&err));
+
+  std::ostringstream lib_out;
+  NinjaBinaryTargetWriter lib_writer(&gen_lib, lib_out);
+  lib_writer.Run();
+
+  const char lib_expected[] =
+      "defines =\n"
+      "include_dirs =\n"
+      "root_out_dir = .\n"
+      "target_out_dir = obj/foo\n"
+      "target_output_name = libgen_lib\n"
+      "\n"
+      "\n"
+      "build ./libgen_lib.so: solink obj/out/Debug/gen_obj.generated.o"
+      // The order-only dependency here is strictly unnecessary since
+      // obj/out/Debug/gen_obj.generated.o has dependency to
+      // obj/foo/gen_obj.stamp
+      " || obj/foo/gen_obj.stamp\n"
+      "  ldflags =\n"
+      "  libs =\n"
+      "  output_extension = .so\n"
+      "  output_dir = foo\n";
+
+  std::string lib_str = lib_out.str();
+  EXPECT_EQ(lib_expected, lib_str);
+
+  // An executable depends on gen_lib.
+  Target executable(setup.settings(),
+                    Label(SourceDir("//foo/"), "final_target"));
+  executable.set_output_type(Target::EXECUTABLE);
+  executable.set_output_dir(SourceDir("//out/Debug/foo/"));
+  executable.sources().push_back(SourceFile("//foo/main.cc"));
+  executable.private_deps().push_back(LabelTargetPair(&gen_lib));
+  executable.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(executable.OnResolved(&err)) << err.message();
+
+  std::ostringstream final_out;
+  NinjaBinaryTargetWriter final_writer(&executable, final_out);
+  final_writer.Run();
+
+  // There is no order only dependency to action target.
+  const char final_expected[] =
+      "defines =\n"
+      "include_dirs =\n"
+      "cflags =\n"
+      "cflags_cc =\n"
+      "root_out_dir = .\n"
+      "target_out_dir = obj/foo\n"
+      "target_output_name = final_target\n"
+      "\n"
+      "build obj/foo/final_target.main.o: cxx ../../foo/main.cc\n"
+      "\n"
+      "build ./final_target: link obj/foo/final_target.main.o"
+      " ./libgen_lib.so\n"
+      "  ldflags =\n"
+      "  libs =\n"
+      "  output_extension = \n"
+      "  output_dir = foo\n";
+
+  std::string final_str = final_out.str();
+  EXPECT_EQ(final_expected, final_str);
+}
+
 // Tests libs are applied.
 TEST_F(NinjaBinaryTargetWriterTest, LibsAndLibDirs) {
   Err err;
diff --git a/tools/gn/target.cc b/tools/gn/target.cc
index b8939bb..15df27f 100644
--- a/tools/gn/target.cc
+++ b/tools/gn/target.cc
@@ -584,6 +584,14 @@
     if (pair.ptr->hard_dep())
       recursive_hard_deps_.insert(pair.ptr);
 
+    // If |pair.ptr| is binary target and |pair.ptr| has no public header,
+    // |this| target does not need to have |pair.ptr|'s hard_deps as its
+    // hard_deps to start compiles earlier.
+    if (pair.ptr->IsBinary() && !pair.ptr->all_headers_public() &&
+        pair.ptr->public_headers().empty()) {
+      continue;
+    }
+
     // Recursive hard dependencies of all dependencies.
     recursive_hard_deps_.insert(pair.ptr->recursive_hard_deps().begin(),
                                 pair.ptr->recursive_hard_deps().end());
diff --git a/tools/gn/variables.cc b/tools/gn/variables.cc
index 591a945..b9dec07 100644
--- a/tools/gn/variables.cc
+++ b/tools/gn/variables.cc
@@ -1665,12 +1665,24 @@
   the target that owns the private header to allow the inclusion. See
   "gn help friend" for more.
 
+  When a binary target has no explicit or implicit public headers (a "public"
+  list is defined but is empty), GN assumes that the target can not propagate
+  any compile-time dependencies up the dependency tree. In this case, the build
+  can be parallelized more efficiently.
+  Say there are dependencies:
+    A (shared library) -> B (shared library) -> C (action).
+  Normally C must complete before any source files in A can compile (because
+  there might be generated includes). But when B explicitly declares no public
+  headers, C can execute in parallel with A's compile steps. C must still be
+  complete before any dependents link.
+
 Examples
 
   These exact files are public:
     public = [ "foo.h", "bar.h" ]
 
   No files are public (no targets may include headers from this one):
+    # This allows starting compile in dependent targets earlier.
     public = []
 )";