[iOS/macOS] consider bundle_data as data-like dependency

Bundle data target's output file doesn't show up as an input
dependency for actions and groups anymore. It's considered as just
order-only dependency. This removes redundant executable linking
step when changes occur in a bundle data.

Change-Id: I79cc4c00af5a4102800d58457b71c81ca6591b68
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/9760
Reviewed-by: Sylvain Defresne <sdefresne@chromium.org>
Reviewed-by: Brett Wilson <brettw@chromium.org>
Commit-Queue: Brett Wilson <brettw@chromium.org>
diff --git a/src/gn/ninja_action_target_writer.cc b/src/gn/ninja_action_target_writer.cc
index 28f28c7..c48da06 100644
--- a/src/gn/ninja_action_target_writer.cc
+++ b/src/gn/ninja_action_target_writer.cc
@@ -35,8 +35,14 @@
   // operating on the result of that previous step, so we need to be sure to
   // serialize these.
   std::vector<const Target*> extra_hard_deps;
-  for (const auto& pair : target_->GetDeps(Target::DEPS_LINKED))
-    extra_hard_deps.push_back(pair.ptr);
+  std::vector<OutputFile> data_outs;
+  for (const auto& pair : target_->GetDeps(Target::DEPS_LINKED)) {
+    if (pair.ptr->IsDataOnly()) {
+      data_outs.push_back(pair.ptr->dependency_output_file());
+    } else {
+      extra_hard_deps.push_back(pair.ptr);
+    }
+  }
 
   // For ACTIONs, the input deps appear only once in the generated ninja
   // file, so WriteInputDepsStampAndGetDep() won't create a stamp file
@@ -88,7 +94,6 @@
   // done before we run the action.
   // TODO(thakis): If the action has just a single output, make things depend
   // on that output directly without writing a stamp file.
-  std::vector<OutputFile> data_outs;
   for (const auto& dep : target_->data_deps())
     data_outs.push_back(dep.ptr->dependency_output_file());
   WriteStampForTarget(output_files, data_outs);
diff --git a/src/gn/ninja_action_target_writer_unittest.cc b/src/gn/ninja_action_target_writer_unittest.cc
index eb03df1..80b125b 100644
--- a/src/gn/ninja_action_target_writer_unittest.cc
+++ b/src/gn/ninja_action_target_writer_unittest.cc
@@ -171,6 +171,14 @@
   dep.SetToolchain(setup.toolchain());
   ASSERT_TRUE(dep.OnResolved(&err));
 
+  Target bundle_data_dep(setup.settings(),
+                         Label(SourceDir("//foo/"), "bundle_data_dep"));
+  bundle_data_dep.sources().push_back(SourceFile("//foo/some_data.txt"));
+  bundle_data_dep.set_output_type(Target::BUNDLE_DATA);
+  bundle_data_dep.visibility().SetPublic();
+  bundle_data_dep.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(bundle_data_dep.OnResolved(&err));
+
   Target datadep(setup.settings(), Label(SourceDir("//foo/"), "datadep"));
   datadep.set_output_type(Target::ACTION);
   datadep.visibility().SetPublic();
@@ -180,6 +188,7 @@
   Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
   target.set_output_type(Target::ACTION_FOREACH);
   target.private_deps().push_back(LabelTargetPair(&dep));
+  target.private_deps().push_back(LabelTargetPair(&bundle_data_dep));
   target.data_deps().push_back(LabelTargetPair(&datadep));
 
   target.sources().push_back(SourceFile("//foo/input1.txt"));
@@ -226,7 +235,8 @@
       "  source_name_part = input2\n"
       "\n"
       "build obj/foo/bar.stamp: "
-      "stamp input1.out input2.out || obj/foo/datadep.stamp\n";
+      "stamp input1.out input2.out || obj/foo/bundle_data_dep.stamp "
+      "obj/foo/datadep.stamp\n";
 
   std::string out_str = out.str();
 #if defined(OS_WIN)
diff --git a/src/gn/ninja_generated_file_target_writer.cc b/src/gn/ninja_generated_file_target_writer.cc
index 6b0db1b..afe1e08 100644
--- a/src/gn/ninja_generated_file_target_writer.cc
+++ b/src/gn/ninja_generated_file_target_writer.cc
@@ -30,10 +30,15 @@
   // on each of the deps and data_deps in the target. The actual collection is
   // done at gen time, and so ninja doesn't need to know about it.
   std::vector<OutputFile> output_files;
-  for (const auto& pair : target_->GetDeps(Target::DEPS_LINKED))
-    output_files.push_back(pair.ptr->dependency_output_file());
-
   std::vector<OutputFile> data_output_files;
+  for (const auto& pair : target_->GetDeps(Target::DEPS_LINKED)) {
+    if (pair.ptr->IsDataOnly()) {
+      data_output_files.push_back(pair.ptr->dependency_output_file());
+    } else {
+      output_files.push_back(pair.ptr->dependency_output_file());
+    }
+  }
+
   const LabelTargetVector& data_deps = target_->data_deps();
   for (const auto& pair : data_deps)
     data_output_files.push_back(pair.ptr->dependency_output_file());
diff --git a/src/gn/ninja_generated_file_target_writer_unittest.cc b/src/gn/ninja_generated_file_target_writer_unittest.cc
index e88ad49..676ba01 100644
--- a/src/gn/ninja_generated_file_target_writer_unittest.cc
+++ b/src/gn/ninja_generated_file_target_writer_unittest.cc
@@ -36,6 +36,14 @@
   dep2.SetToolchain(setup.toolchain());
   ASSERT_TRUE(dep2.OnResolved(&err));
 
+  Target bundle_data_dep(setup.settings(),
+                         Label(SourceDir("//foo/"), "bundle_data_dep"));
+  bundle_data_dep.sources().push_back(SourceFile("//foo/some_data.txt"));
+  bundle_data_dep.set_output_type(Target::BUNDLE_DATA);
+  bundle_data_dep.visibility().SetPublic();
+  bundle_data_dep.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(bundle_data_dep.OnResolved(&err));
+
   Target datadep(setup.settings(), Label(SourceDir("//foo/"), "datadep"));
   datadep.set_output_type(Target::ACTION);
   datadep.visibility().SetPublic();
@@ -44,6 +52,7 @@
 
   target.public_deps().push_back(LabelTargetPair(&dep));
   target.public_deps().push_back(LabelTargetPair(&dep2));
+  target.public_deps().push_back(LabelTargetPair(&bundle_data_dep));
   target.data_deps().push_back(LabelTargetPair(&datadep));
 
   target.SetToolchain(setup.toolchain());
@@ -55,6 +64,6 @@
 
   const char expected[] =
       "build obj/foo/bar.stamp: stamp obj/foo/dep.stamp obj/foo/dep2.stamp || "
-      "obj/foo/datadep.stamp\n";
+      "obj/foo/bundle_data_dep.stamp obj/foo/datadep.stamp\n";
   EXPECT_EQ(expected, out.str());
 }
diff --git a/src/gn/ninja_group_target_writer.cc b/src/gn/ninja_group_target_writer.cc
index b518977..beeb54a 100644
--- a/src/gn/ninja_group_target_writer.cc
+++ b/src/gn/ninja_group_target_writer.cc
@@ -20,10 +20,15 @@
   // A group rule just generates a stamp file with dependencies on each of
   // the deps and data_deps in the group.
   std::vector<OutputFile> output_files;
-  for (const auto& pair : target_->GetDeps(Target::DEPS_LINKED))
-    output_files.push_back(pair.ptr->dependency_output_file());
-
   std::vector<OutputFile> data_output_files;
+  for (const auto& pair : target_->GetDeps(Target::DEPS_LINKED)) {
+    if (pair.ptr->IsDataOnly()) {
+      data_output_files.push_back(pair.ptr->dependency_output_file());
+    } else {
+      output_files.push_back(pair.ptr->dependency_output_file());
+    }
+  }
+
   const LabelTargetVector& data_deps = target_->data_deps();
   for (const auto& pair : data_deps)
     data_output_files.push_back(pair.ptr->dependency_output_file());
diff --git a/src/gn/ninja_group_target_writer_unittest.cc b/src/gn/ninja_group_target_writer_unittest.cc
index 2280bb0..1fe51ca 100644
--- a/src/gn/ninja_group_target_writer_unittest.cc
+++ b/src/gn/ninja_group_target_writer_unittest.cc
@@ -27,6 +27,14 @@
   dep2.SetToolchain(setup.toolchain());
   ASSERT_TRUE(dep2.OnResolved(&err));
 
+  Target bundle_data_dep(setup.settings(),
+                         Label(SourceDir("//foo/"), "bundle_data_dep"));
+  bundle_data_dep.sources().push_back(SourceFile("//foo/some_data.txt"));
+  bundle_data_dep.set_output_type(Target::BUNDLE_DATA);
+  bundle_data_dep.visibility().SetPublic();
+  bundle_data_dep.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(bundle_data_dep.OnResolved(&err));
+
   Target datadep(setup.settings(), Label(SourceDir("//foo/"), "datadep"));
   datadep.set_output_type(Target::ACTION);
   datadep.visibility().SetPublic();
@@ -35,6 +43,7 @@
 
   target.public_deps().push_back(LabelTargetPair(&dep));
   target.public_deps().push_back(LabelTargetPair(&dep2));
+  target.public_deps().push_back(LabelTargetPair(&bundle_data_dep));
   target.data_deps().push_back(LabelTargetPair(&datadep));
 
   target.SetToolchain(setup.toolchain());
@@ -46,6 +55,6 @@
 
   const char expected[] =
       "build obj/foo/bar.stamp: stamp obj/foo/dep.stamp obj/foo/dep2.stamp || "
-      "obj/foo/datadep.stamp\n";
+      "obj/foo/bundle_data_dep.stamp obj/foo/datadep.stamp\n";
   EXPECT_EQ(expected, out.str());
 }
diff --git a/src/gn/ninja_target_writer.cc b/src/gn/ninja_target_writer.cc
index b6bbb9f..7eefc12 100644
--- a/src/gn/ninja_target_writer.cc
+++ b/src/gn/ninja_target_writer.cc
@@ -232,8 +232,14 @@
   // Hard dependencies that are direct or indirect dependencies.
   // These are large (up to 100s), hence why we check other
   const std::set<const Target*>& hard_deps(target_->recursive_hard_deps());
-  for (const Target* target : hard_deps)
-    input_deps_targets.push_back(target);
+  for (const Target* target : hard_deps) {
+    // BUNDLE_DATA should normally be treated as a data-only dependency
+    // (see Target::IsDataOnly()). Only the CREATE_BUNDLE target, that actually
+    // consumes this data, needs to have the BUNDLE_DATA as an input dependency.
+    if (target->output_type() != Target::BUNDLE_DATA ||
+        target_->output_type() == Target::CREATE_BUNDLE)
+      input_deps_targets.push_back(target);
+  }
 
   // Extra hard dependencies passed in. These are usually empty or small, and
   // we don't want to duplicate the explicit hard deps of the target.
diff --git a/src/gn/target.cc b/src/gn/target.cc
index b8a8d05..dd71abb 100644
--- a/src/gn/target.cc
+++ b/src/gn/target.cc
@@ -438,6 +438,15 @@
          (output_type_ == STATIC_LIBRARY && complete_static_lib_);
 }
 
+bool Target::IsDataOnly() const {
+  // BUNDLE_DATA exists only to declare inputs to subsequent CREATE_BUNDLE
+  // targets. Changing only contents of the bundle data target should not cause
+  // a binary to be re-linked. It should affect only the CREATE_BUNDLE steps
+  // instead. As a result, normal targets should treat this as a data
+  // dependency.
+  return output_type_ == BUNDLE_DATA;
+}
+
 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 dbcfa69..03f456c 100644
--- a/src/gn/target.h
+++ b/src/gn/target.h
@@ -91,6 +91,13 @@
   // dependencies, because they also don't propogate libraries up.
   bool IsFinal() const;
 
+  // Set when the target should normally be treated as a data dependency. These
+  // do not need to be treated as inputs or hard dependencies for normal build
+  // steps, but have to be kept in the dependency tree to be properly
+  // propagated. Treating these as data only decreases superfluous rebuilds and
+  // increases parallelism.
+  bool IsDataOnly() 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_; }