[apple] Implements support of transparent create_bundle

A transparent create_bundle target is one that allows bundle_data
deps to propagate through the dependency chain. This can be used
by create_bundle that generates a framework bundle that needs to
have the resources put in the main application bundle.

This will be used by Chrome on iOS to initialize PartitionAlloc
before starting the executing any static initializer (by moving
the initialisation in a shell application then loading code as
a private framework).

Bug: 337
Change-Id: I38a68c7ebcd715b06e79417b7a33e4a84f8fa655
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/15460
Commit-Queue: Sylvain Defresne <sdefresne@chromium.org>
Reviewed-by: Takuto Ikuta <tikuta@google.com>
diff --git a/docs/reference.md b/docs/reference.md
index d04912a..310d7e8 100644
--- a/docs/reference.md
+++ b/docs/reference.md
@@ -145,7 +145,7 @@
     *   [precompiled_header: [string] Header file to precompile.](#var_precompiled_header)
     *   [precompiled_header_type: [string] "gcc" or "msvc".](#var_precompiled_header_type)
     *   [precompiled_source: [file name] Source file to precompile.](#var_precompiled_source)
-    *   [product_type: [string] Product type for Xcode projects.](#var_product_type)
+    *   [product_type: [string] Product type for the bundle.](#var_product_type)
     *   [public: [file list] Declare public header files for a target.](#var_public)
     *   [public_configs: [label list] Configs applied to dependents.](#var_public_configs)
     *   [public_deps: [label list] Declare public dependencies.](#var_public_deps)
@@ -155,6 +155,7 @@
     *   [sources: [file list] Source files for a target.](#var_sources)
     *   [swiftflags: [string list] Flags passed to the swift compiler.](#var_swiftflags)
     *   [testonly: [boolean] Declares a target must only be used for testing.](#var_testonly)
+    *   [transparent: [bool] True if the bundle is transparent.](#var_transparent)
     *   [visibility: [label list] A list of labels that can depend on a target.](#var_visibility)
     *   [walk_keys: [string list] Key(s) for managing the metadata collection walk.](#var_walk_keys)
     *   [weak_frameworks: [name list] Name of frameworks that must be weak linked.](#var_weak_frameworks)
@@ -6354,14 +6355,17 @@
   "msvc"-style precompiled headers. It will be implicitly added to the sources
   of the target. See "gn help precompiled_header".
 ```
-### <a name="var_product_type"></a>**product_type**: Product type for Xcode projects.
+### <a name="var_product_type"></a>**product_type**: [string] Product type for the bundle.
 
 ```
-  Correspond to the type of the product of a create_bundle target. Only
-  meaningful to Xcode (used as part of the Xcode project generation).
+  Valid for "create_bundle" and "bundle_data" targets.
 
-  When generating Xcode project files, only create_bundle target with a
-  non-empty product_type will have a corresponding target in Xcode project.
+  Correspond to the type of the bundle. Used by transparent "create_bundle"
+  target to control whether a "bundle_data" needs to be propagated or not.
+
+  When generating Xcode project, the product_type is propagated and only
+  "create_bundle" with a non-empty product_type will have a corresponding
+  target in the project.
 ```
 ### <a name="var_public"></a>**public**: Declare public header files for a target.
 
@@ -6708,6 +6712,16 @@
     ...
   }
 ```
+### <a name="var_transparent"></a>**transparent**: [bool] True if the bundle is transparent.
+
+```
+  A boolean.
+
+  Valid for "create_bundle" target. If true, the "create_bundle" target will
+  not package the "bundle_data" deps but will forward them to all targets that
+  depends on it (unless the "bundle_data" target sets "product_type" to the
+  same value as the "create_bundle" target).
+```
 ### <a name="var_visibility"></a>**visibility**: A list of labels that can depend on a target.
 
 ```
diff --git a/src/gn/bundle_data.cc b/src/gn/bundle_data.cc
index 6b7fe77..feabf8d 100644
--- a/src/gn/bundle_data.cc
+++ b/src/gn/bundle_data.cc
@@ -62,13 +62,25 @@
 
 BundleData::~BundleData() = default;
 
-void BundleData::AddBundleData(const Target* target) {
+void BundleData::AddBundleData(const Target* target, bool is_create_bundle) {
   DCHECK_EQ(target->output_type(), Target::BUNDLE_DATA);
   for (const auto& pattern : bundle_deps_filter_) {
     if (pattern.Matches(target->label()))
       return;
   }
-  bundle_deps_.push_back(target);
+  if (transparent_) {
+    DCHECK(is_create_bundle);
+    if (target->bundle_data().product_type() == product_type_) {
+      bundle_deps_.push_back(target);
+    } else {
+      forwarded_bundle_deps_.push_back(target);
+    }
+    return;
+  }
+  if (is_create_bundle) {
+    bundle_deps_.push_back(target);
+  }
+  forwarded_bundle_deps_.push_back(target);
 }
 
 void BundleData::OnTargetResolved(Target* owning_target) {
diff --git a/src/gn/bundle_data.h b/src/gn/bundle_data.h
index 5b3bca6..f168264 100644
--- a/src/gn/bundle_data.h
+++ b/src/gn/bundle_data.h
@@ -34,7 +34,7 @@
 
   // Adds a bundle_data target to the recursive collection of all bundle_data
   // that the target depends on.
-  void AddBundleData(const Target* target);
+  void AddBundleData(const Target* target, bool is_create_bundle);
 
   // Called upon resolution of the target owning this instance of BundleData.
   // |owning_target| is the owning target.
@@ -110,6 +110,9 @@
     return xcode_extra_attributes_;
   }
 
+  bool transparent() const { return transparent_; }
+  void set_transparent(bool value) { transparent_ = value; }
+
   std::string& product_type() { return product_type_; }
   const std::string& product_type() const { return product_type_; }
 
@@ -162,6 +165,12 @@
   // Recursive collection of all bundle_data that the target depends on.
   const UniqueTargets& bundle_deps() const { return bundle_deps_; }
 
+  // Recursive collection of all forwarded bundle_data that the target
+  // depends on (but does not use, see `transparent` in `create_bundle`).
+  const UniqueTargets& forwarded_bundle_deps() const {
+    return forwarded_bundle_deps_;
+  }
+
   // Returns whether the bundle is an application bundle.
   bool is_application() const {
     return product_type_ == "com.apple.product-type.application";
@@ -177,6 +186,7 @@
   std::vector<const Target*> assets_catalog_deps_;
   BundleFileRules file_rules_;
   UniqueTargets bundle_deps_;
+  UniqueTargets forwarded_bundle_deps_;
   std::vector<LabelPattern> bundle_deps_filter_;
 
   // All those values are subdirectories relative to root_build_dir, and apart
@@ -194,6 +204,11 @@
   // the Xcode project file when using --ide=xcode.
   std::string product_type_;
 
+  // Whether the bundle should be considered "transparent". A transparent
+  // `create_bundle` target includes only the `bundle_data` with the same
+  // `product_type` as itself.
+  bool transparent_ = false;
+
   // Each Xcode unit test or ui test target must have a test application target,
   // and this value corresponds to the target name. This is only used to
   // generate the Xcode project when using --ide=xcode.
diff --git a/src/gn/bundle_data_target_generator.cc b/src/gn/bundle_data_target_generator.cc
index 0246cff..18af1ab 100644
--- a/src/gn/bundle_data_target_generator.cc
+++ b/src/gn/bundle_data_target_generator.cc
@@ -27,6 +27,8 @@
     return;
   if (!FillOutputs())
     return;
+  if (!FillProductType())
+    return;
 
   if (target_->sources().empty()) {
     *err_ = Err(function_call_,
@@ -76,6 +78,18 @@
   return true;
 }
 
+bool BundleDataTargetGenerator::FillProductType() {
+  const Value* value = scope_->GetValue(variables::kProductType, true);
+  if (!value)
+    return true;
+
+  if (!value->VerifyTypeIs(Value::STRING, err_))
+    return false;
+
+  target_->bundle_data().product_type().assign(value->string_value());
+  return true;
+}
+
 bool BundleDataTargetGenerator::EnsureSubstitutionIsInBundleDir(
     const SubstitutionPattern& pattern,
     const Value& original_value) {
diff --git a/src/gn/bundle_data_target_generator.h b/src/gn/bundle_data_target_generator.h
index f5b60f1..3fede2c 100644
--- a/src/gn/bundle_data_target_generator.h
+++ b/src/gn/bundle_data_target_generator.h
@@ -21,6 +21,7 @@
 
  private:
   bool FillOutputs();
+  bool FillProductType();
 
   bool EnsureSubstitutionIsInBundleDir(const SubstitutionPattern& pattern,
                                        const Value& original_value);
diff --git a/src/gn/create_bundle_target_generator.cc b/src/gn/create_bundle_target_generator.cc
index 0a3d7cf..5569e75 100644
--- a/src/gn/create_bundle_target_generator.cc
+++ b/src/gn/create_bundle_target_generator.cc
@@ -72,6 +72,9 @@
 
   if (!FillXcassetCompilerFlags())
     return;
+
+  if (!FillTransparent())
+    return;
 }
 
 bool CreateBundleTargetGenerator::FillBundleDir(
@@ -316,3 +319,15 @@
 
   return target_->bundle_data().xcasset_compiler_flags().Parse(*value, err_);
 }
+
+bool CreateBundleTargetGenerator::FillTransparent() {
+  const Value* value = scope_->GetValue(variables::kTransparent, true);
+  if (!value)
+    return true;
+
+  if (!value->VerifyTypeIs(Value::BOOLEAN, err_))
+    return false;
+
+  target_->bundle_data().set_transparent(value->boolean_value());
+  return true;
+}
diff --git a/src/gn/create_bundle_target_generator.h b/src/gn/create_bundle_target_generator.h
index cb18ebf..0e7a535 100644
--- a/src/gn/create_bundle_target_generator.h
+++ b/src/gn/create_bundle_target_generator.h
@@ -40,6 +40,7 @@
   bool FillCodeSigningArgs();
   bool FillBundleDepsFilter();
   bool FillXcassetCompilerFlags();
+  bool FillTransparent();
 
   CreateBundleTargetGenerator(const CreateBundleTargetGenerator&) = delete;
   CreateBundleTargetGenerator& operator=(const CreateBundleTargetGenerator&) =
diff --git a/src/gn/target.cc b/src/gn/target.cc
index f73cd3a..da82b95 100644
--- a/src/gn/target.cc
+++ b/src/gn/target.cc
@@ -132,7 +132,7 @@
         return true;  // Found a path.
     }
     if (target->output_type() == Target::CREATE_BUNDLE) {
-      for (auto* dep : target->bundle_data().bundle_deps()) {
+      for (const auto* dep : target->bundle_data().bundle_deps()) {
         if (EnsureFileIsGeneratedByDependency(dep, file, false,
                                               consider_object_files,
                                               check_data_deps, seen_targets))
@@ -738,24 +738,27 @@
 }
 
 void Target::PullRecursiveBundleData() {
+  const bool is_create_bundle = output_type_ == CREATE_BUNDLE;
   for (const auto& pair : GetDeps(DEPS_LINKED)) {
-    // Don't propagate bundle_data once they are added to a bundle.
-    if (pair.ptr->output_type() == CREATE_BUNDLE)
-      continue;
-
     // Don't propagate across toolchain.
     if (pair.ptr->toolchain() != toolchain())
       continue;
 
+    // Don't propagete through create_bundle, unless it is transparent.
+    if (pair.ptr->output_type() == CREATE_BUNDLE &&
+        !pair.ptr->bundle_data().transparent()) {
+      continue;
+    }
+
     // Direct dependency on a bundle_data target.
     if (pair.ptr->output_type() == BUNDLE_DATA) {
-      bundle_data().AddBundleData(pair.ptr);
+      bundle_data().AddBundleData(pair.ptr, is_create_bundle);
     }
 
     // Recursive bundle_data informations from all dependencies.
     if (pair.ptr->has_bundle_data()) {
-      for (auto* target : pair.ptr->bundle_data().bundle_deps())
-        bundle_data().AddBundleData(target);
+      for (const auto* target : pair.ptr->bundle_data().forwarded_bundle_deps())
+        bundle_data().AddBundleData(target, is_create_bundle);
     }
   }
 
diff --git a/src/gn/target_unittest.cc b/src/gn/target_unittest.cc
index d6554fd..db43d74 100644
--- a/src/gn/target_unittest.cc
+++ b/src/gn/target_unittest.cc
@@ -1101,18 +1101,18 @@
   ASSERT_EQ(a.bundle_data().file_rules()[0].sources().size(), 2u);
   ASSERT_EQ(a.bundle_data().file_rules()[1].sources().size(), 3u);
   ASSERT_EQ(a.bundle_data().assets_catalog_sources().size(), 1u);
-  ASSERT_EQ(a.bundle_data().bundle_deps().size(), 2u);
+  ASSERT_EQ(a.bundle_data().forwarded_bundle_deps().size(), 2u);
 
   // C gets its data from D.
   ASSERT_EQ(c.bundle_data().file_rules().size(), 1u);
   ASSERT_EQ(c.bundle_data().file_rules()[0].sources().size(), 1u);
-  ASSERT_EQ(c.bundle_data().bundle_deps().size(), 1u);
+  ASSERT_EQ(c.bundle_data().forwarded_bundle_deps().size(), 1u);
 
   // E does not have any bundle_data information but gets a list of
-  // bundle_deps to propagate them during target resolution.
+  // forwarded_bundle_deps to propagate them during target resolution.
   ASSERT_TRUE(e.bundle_data().file_rules().empty());
   ASSERT_TRUE(e.bundle_data().assets_catalog_sources().empty());
-  ASSERT_EQ(e.bundle_data().bundle_deps().size(), 2u);
+  ASSERT_EQ(e.bundle_data().forwarded_bundle_deps().size(), 2u);
 }
 
 TEST(TargetTest, CollectMetadataNoRecurse) {
diff --git a/src/gn/variables.cc b/src/gn/variables.cc
index 07ce016..5015df5 100644
--- a/src/gn/variables.cc
+++ b/src/gn/variables.cc
@@ -758,6 +758,20 @@
   in compile_xcassets tool.
 )";
 
+const char kTransparent[] = "transparent";
+const char kTransparent_HelpShort[] =
+    "transparent: [bool] True if the bundle is transparent.";
+const char kTransparent_Help[] =
+    R"(transparent: [bool] True if the bundle is transparent.
+
+  A boolean.
+
+  Valid for "create_bundle" target. If true, the "create_bundle" target will
+  not package the "bundle_data" deps but will forward them to all targets that
+  depends on it (unless the "bundle_data" target sets "product_type" to the
+  same value as the "create_bundle" target).
+)";
+
 const char kCflags[] = "cflags";
 const char kCflags_HelpShort[] =
     "cflags: [string list] Flags passed to all C compiler variants.";
@@ -1767,15 +1781,18 @@
 
 const char kProductType[] = "product_type";
 const char kProductType_HelpShort[] =
-    "product_type: [string] Product type for Xcode projects.";
+    "product_type: [string] Product type for the bundle.";
 const char kProductType_Help[] =
-    R"(product_type: Product type for Xcode projects.
+    R"(product_type: [string] Product type for the bundle.
 
-  Correspond to the type of the product of a create_bundle target. Only
-  meaningful to Xcode (used as part of the Xcode project generation).
+  Valid for "create_bundle" and "bundle_data" targets.
 
-  When generating Xcode project files, only create_bundle target with a
-  non-empty product_type will have a corresponding target in Xcode project.
+  Correspond to the type of the bundle. Used by transparent "create_bundle"
+  target to control whether a "bundle_data" needs to be propagated or not.
+
+  When generating Xcode project, the product_type is propagated and only
+  "create_bundle" with a non-empty product_type will have a corresponding
+  target in the project.
 )";
 
 const char kPublic[] = "public";
@@ -2327,6 +2344,7 @@
     INSERT_VARIABLE(BundleDepsFilter)
     INSERT_VARIABLE(BundleExecutableDir)
     INSERT_VARIABLE(XcassetCompilerFlags)
+    INSERT_VARIABLE(Transparent)
     INSERT_VARIABLE(Cflags)
     INSERT_VARIABLE(CflagsC)
     INSERT_VARIABLE(CflagsCC)
diff --git a/src/gn/variables.h b/src/gn/variables.h
index ac392d9..56d7000 100644
--- a/src/gn/variables.h
+++ b/src/gn/variables.h
@@ -130,6 +130,10 @@
 extern const char kXcassetCompilerFlags_HelpShort[];
 extern const char kXcassetCompilerFlags_Help[];
 
+extern const char kTransparent[];
+extern const char kTransparent_HelpShort[];
+extern const char kTransparent_Help[];
+
 extern const char kCflags[];
 extern const char kCflags_HelpShort[];
 extern const char* kCflags_Help;