Fix dependencies rules for create_bundle and bundle_data ninja steps.

Record the "bundle_data" target corresponding to a BundleFileRule object
so that the dependencies can be correctly computed when generating the
ninja steps for the "create_bundle" target.

Change the "stamp" step of "bundle_data" target in the generated ninja
to depends on the target dependencies and source files.

Change the "copy_bundle_data" steps of "create_bundle" target in the
generated ninja to depends on the "bundle_data" target that defined
the corresponding source files.

Change the "compile_xcassets" step of "create_bundle" target in the
generated ninja to depends on all the "bundle_data" targets that defined
the corresponding source files.

Change the rule used to determine whether a "bundle_data" source file
is part of an assets catalog (some file where incorrectly omitted causing
them to be incorrectly copied to the final bundle).

All those changes will allow the "copy_bundle_data" and "compile_xcassets"
steps to only depends on the targets that generate them (if any) and will
reduce the number of file copied into the final bundle during incremental
builds.

Fix unit tests with new expectations.

BUG=623501

Review-Url: https://codereview.chromium.org/2105613003
Cr-Original-Commit-Position: refs/heads/master@{#403304}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: 4c75ba4852a79bde5ce03ebff7065282c9fd8977
diff --git a/tools/gn/BUILD.gn b/tools/gn/BUILD.gn
index a0801a1..510c178 100644
--- a/tools/gn/BUILD.gn
+++ b/tools/gn/BUILD.gn
@@ -297,6 +297,7 @@
     "ninja_action_target_writer_unittest.cc",
     "ninja_binary_target_writer_unittest.cc",
     "ninja_build_writer_unittest.cc",
+    "ninja_bundle_data_target_writer_unittest.cc",
     "ninja_copy_target_writer_unittest.cc",
     "ninja_create_bundle_target_writer_unittest.cc",
     "ninja_group_target_writer_unittest.cc",
diff --git a/tools/gn/bundle_data.cc b/tools/gn/bundle_data.cc
index 4218a15..2908ddf 100644
--- a/tools/gn/bundle_data.cc
+++ b/tools/gn/bundle_data.cc
@@ -14,33 +14,38 @@
 namespace {
 
 // Return directory of |path| without the trailing directory separator.
-base::StringPiece FindDirNoTrailingSeparator(const base::StringPiece& path) {
+base::StringPiece FindDirNoTrailingSeparator(base::StringPiece path) {
   base::StringPiece::size_type pos = path.find_last_of("/\\");
   if (pos == base::StringPiece::npos)
     return base::StringPiece();
   return base::StringPiece(path.data(), pos);
 }
 
-}  // namespace
-
-bool IsSourceFileFromAssetCatalog(const SourceFile& source,
-                                  SourceFile* asset_catalog) {
-  // Check that the file matches the following pattern:
+bool IsSourceFileFromAssetsCatalog(base::StringPiece source,
+                                   SourceFile* asset_catalog) {
+  // Check whether |source| matches one of the following pattern:
+  //    .*\.xcassets/Contents.json
+  //    .*\.xcassets/[^/]*\.appiconset/[^/]*
   //    .*\.xcassets/[^/]*\.imageset/[^/]*
-  base::StringPiece dir;
-  dir = FindDirNoTrailingSeparator(source.value());
-  if (!dir.ends_with(".imageset"))
-    return false;
-  dir = FindDirNoTrailingSeparator(dir);
-  if (!dir.ends_with(".xcassets"))
-    return false;
-  if (asset_catalog) {
+  //    .*\.xcassets/[^/]*\.launchimage/[^/]*
+  bool is_file_from_asset_catalog = false;
+  base::StringPiece dir = FindDirNoTrailingSeparator(source);
+  if (source.ends_with("/Contents.json") && dir.ends_with(".xcassets")) {
+    is_file_from_asset_catalog = true;
+  } else if (dir.ends_with(".appiconset") || dir.ends_with(".imageset") ||
+             dir.ends_with(".launchimage")) {
+    dir = FindDirNoTrailingSeparator(dir);
+    is_file_from_asset_catalog = dir.ends_with(".xcassets");
+  }
+  if (is_file_from_asset_catalog && asset_catalog) {
     std::string asset_catalog_path = dir.as_string();
     *asset_catalog = SourceFile(SourceFile::SWAP_IN, &asset_catalog_path);
   }
-  return true;
+  return is_file_from_asset_catalog;
 }
 
+}  // namespace
+
 BundleData::BundleData() {}
 
 BundleData::~BundleData() {}
@@ -51,16 +56,21 @@
 }
 
 void BundleData::OnTargetResolved(Target* owning_target) {
-  // Only initialize file_rules_ and asset_catalog_sources for "create_bundle"
+  // Only initialize file_rules_ and assets_catalog_sources for "create_bundle"
   // target (properties are only used by those targets).
   if (owning_target->output_type() != Target::CREATE_BUNDLE)
     return;
 
+  UniqueVector<const Target*> assets_catalog_deps;
+  UniqueVector<SourceFile> assets_catalog_sources;
+
   for (const Target* target : bundle_deps_) {
     SourceFiles file_rule_sources;
     for (const SourceFile& source_file : target->sources()) {
-      if (IsSourceFileFromAssetCatalog(source_file, nullptr)) {
-        asset_catalog_sources_.push_back(source_file);
+      SourceFile assets_catalog;
+      if (IsSourceFileFromAssetsCatalog(source_file.value(), &assets_catalog)) {
+        assets_catalog_sources.push_back(assets_catalog);
+        assets_catalog_deps.push_back(target);
       } else {
         file_rule_sources.push_back(source_file);
       }
@@ -68,11 +78,19 @@
 
     if (!file_rule_sources.empty()) {
       DCHECK_EQ(target->action_values().outputs().list().size(), 1u);
-      file_rules_.push_back(BundleFileRule(
-          file_rule_sources, target->action_values().outputs().list()[0]));
+      file_rules_.push_back(
+          BundleFileRule(target, file_rule_sources,
+                         target->action_values().outputs().list()[0]));
     }
   }
 
+  assets_catalog_deps_.insert(assets_catalog_deps_.end(),
+                              assets_catalog_deps.begin(),
+                              assets_catalog_deps.end());
+  assets_catalog_sources_.insert(assets_catalog_sources_.end(),
+                                 assets_catalog_sources.begin(),
+                                 assets_catalog_sources.end());
+
   GetSourceFiles(&owning_target->sources());
 }
 
@@ -81,8 +99,8 @@
     sources->insert(sources->end(), file_rule.sources().begin(),
                     file_rule.sources().end());
   }
-  sources->insert(sources->end(), asset_catalog_sources_.begin(),
-                  asset_catalog_sources_.end());
+  sources->insert(sources->end(), assets_catalog_sources_.begin(),
+                  assets_catalog_sources_.end());
   if (!code_signing_script_.is_null()) {
     sources->insert(sources->end(), code_signing_sources_.begin(),
                     code_signing_sources_.end());
@@ -107,7 +125,7 @@
     }
   }
 
-  if (!asset_catalog_sources_.empty())
+  if (!assets_catalog_sources_.empty())
     outputs_as_source->push_back(GetCompiledAssetCatalogPath());
 
   if (!code_signing_script_.is_null()) {
@@ -124,7 +142,7 @@
 }
 
 SourceFile BundleData::GetCompiledAssetCatalogPath() const {
-  DCHECK(!asset_catalog_sources_.empty());
+  DCHECK(!assets_catalog_sources_.empty());
   std::string assets_car_path = resources_dir_.value() + "/Assets.car";
   return SourceFile(SourceFile::SWAP_IN, &assets_car_path);
 }
diff --git a/tools/gn/bundle_data.h b/tools/gn/bundle_data.h
index 0b040f7..e35cacb 100644
--- a/tools/gn/bundle_data.h
+++ b/tools/gn/bundle_data.h
@@ -19,19 +19,6 @@
 class Settings;
 class Target;
 
-// Returns true if |source| correspond to the path of a file in an asset
-// catalog. If defined |asset_catalog| is set to its path.
-//
-// An asset catalog is an OS X bundle with the ".xcassets" extension. It
-// contains one directory per assets each of them with the ".imageset"
-// extension.
-//
-// All asset catalogs are compiled by Xcode into single Assets.car file as
-// part of the creation of an application or framework bundle. BundleData
-// emulates this with the "compile_xcassets" tool.
-bool IsSourceFileFromAssetCatalog(const SourceFile& source,
-                                  SourceFile* asset_catalog);
-
 // BundleData holds the information required by "create_bundle" target.
 class BundleData {
  public:
@@ -64,7 +51,7 @@
       SourceFiles* outputs_as_source) const;
 
   // Returns the path to the compiled asset catalog. Only valid if
-  // asset_catalog_sources() is not empty.
+  // assets_catalog_sources() is not empty.
   SourceFile GetCompiledAssetCatalogPath() const;
 
   // Returns the path to the top-level directory of the bundle. This is
@@ -83,9 +70,14 @@
   SourceDir GetBundleRootDirOutputAsDir(const Settings* settings) const;
 
   // Returns the list of inputs for the compilation of the asset catalog.
-  SourceFiles& asset_catalog_sources() { return asset_catalog_sources_; }
-  const SourceFiles& asset_catalog_sources() const {
-    return asset_catalog_sources_;
+  SourceFiles& assets_catalog_sources() { return assets_catalog_sources_; }
+  const SourceFiles& assets_catalog_sources() const {
+    return assets_catalog_sources_;
+  }
+
+  // Returns the list of dependencies for the compilation of the asset catalog.
+  std::vector<const Target*> assets_catalog_deps() const {
+    return assets_catalog_deps_;
   }
 
   BundleFileRules& file_rules() { return file_rules_; }
@@ -132,7 +124,8 @@
   const UniqueTargets& bundle_deps() const { return bundle_deps_; }
 
  private:
-  SourceFiles asset_catalog_sources_;
+  SourceFiles assets_catalog_sources_;
+  std::vector<const Target*> assets_catalog_deps_;
   BundleFileRules file_rules_;
   UniqueTargets bundle_deps_;
 
diff --git a/tools/gn/bundle_file_rule.cc b/tools/gn/bundle_file_rule.cc
index 9f78667..e23361f 100644
--- a/tools/gn/bundle_file_rule.cc
+++ b/tools/gn/bundle_file_rule.cc
@@ -10,9 +10,13 @@
 #include "tools/gn/substitution_writer.h"
 #include "tools/gn/target.h"
 
-BundleFileRule::BundleFileRule(const std::vector<SourceFile> sources,
+BundleFileRule::BundleFileRule(const Target* bundle_data_target,
+                               const std::vector<SourceFile> sources,
                                const SubstitutionPattern& pattern)
-    : sources_(sources), pattern_(pattern) {}
+    : target_(bundle_data_target), sources_(sources), pattern_(pattern) {
+  // target_ may be null during testing.
+  DCHECK(!target_ || target_->output_type() == Target::BUNDLE_DATA);
+}
 
 BundleFileRule::BundleFileRule(const BundleFileRule& other) = default;
 
diff --git a/tools/gn/bundle_file_rule.h b/tools/gn/bundle_file_rule.h
index 9fbf012..372e628 100644
--- a/tools/gn/bundle_file_rule.h
+++ b/tools/gn/bundle_file_rule.h
@@ -13,12 +13,14 @@
 class BundleData;
 class Settings;
 class SourceFile;
+class Target;
 class OutputFile;
 
 // BundleFileRule contains the information found in a "bundle_data" target.
 class BundleFileRule {
  public:
-  BundleFileRule(const std::vector<SourceFile> sources,
+  BundleFileRule(const Target* bundle_data_target,
+                 const std::vector<SourceFile> sources,
                  const SubstitutionPattern& pattern);
   BundleFileRule(const BundleFileRule& other);
   ~BundleFileRule();
@@ -33,10 +35,15 @@
       const BundleData& bundle_data,
       const SourceFile& source_file) const;
 
+  // Returns the associated target (of type Target::BUNDLE_DATA). May be
+  // null during testing.
+  const Target* target() const { return target_; }
+
   // Returns the list of SourceFiles.
   const std::vector<SourceFile>& sources() const { return sources_; }
 
  private:
+  const Target* target_;
   std::vector<SourceFile> sources_;
   SubstitutionPattern pattern_;
 };
diff --git a/tools/gn/gn.gyp b/tools/gn/gn.gyp
index a002323..ae486bb 100644
--- a/tools/gn/gn.gyp
+++ b/tools/gn/gn.gyp
@@ -264,6 +264,7 @@
         'loader_unittest.cc',
         'ninja_action_target_writer_unittest.cc',
         'ninja_binary_target_writer_unittest.cc',
+        'ninja_bundle_data_target_writer_unittest.cc',
         'ninja_copy_target_writer_unittest.cc',
         'ninja_create_bundle_target_writer_unittest.cc',
         'ninja_group_target_writer_unittest.cc',
diff --git a/tools/gn/ninja_bundle_data_target_writer.cc b/tools/gn/ninja_bundle_data_target_writer.cc
index 67b5a10..26bfefb 100644
--- a/tools/gn/ninja_bundle_data_target_writer.cc
+++ b/tools/gn/ninja_bundle_data_target_writer.cc
@@ -5,6 +5,8 @@
 #include "tools/gn/ninja_bundle_data_target_writer.h"
 
 #include "tools/gn/output_file.h"
+#include "tools/gn/settings.h"
+#include "tools/gn/target.h"
 
 NinjaBundleDataTargetWriter::NinjaBundleDataTargetWriter(const Target* target,
                                                          std::ostream& out)
@@ -13,7 +15,20 @@
 NinjaBundleDataTargetWriter::~NinjaBundleDataTargetWriter() {}
 
 void NinjaBundleDataTargetWriter::Run() {
-  std::vector<OutputFile> files;
-  files.push_back(WriteInputDepsStampAndGetDep(std::vector<const Target*>()));
-  WriteStampForTarget(files, std::vector<OutputFile>());
+  std::vector<OutputFile> output_files;
+  for (const SourceFile& source_file : target_->sources()) {
+    output_files.push_back(
+        OutputFile(settings_->build_settings(), source_file));
+  }
+
+  std::vector<const Target*> extra_hard_deps;
+  OutputFile input_dep = WriteInputDepsStampAndGetDep(extra_hard_deps);
+  if (!input_dep.value().empty())
+    output_files.push_back(input_dep);
+
+  std::vector<OutputFile> order_only_deps;
+  for (const auto& pair : target_->data_deps())
+    order_only_deps.push_back(pair.ptr->dependency_output_file());
+
+  WriteStampForTarget(output_files, order_only_deps);
 }
diff --git a/tools/gn/ninja_bundle_data_target_writer_unittest.cc b/tools/gn/ninja_bundle_data_target_writer_unittest.cc
new file mode 100644
index 0000000..b4725c7
--- /dev/null
+++ b/tools/gn/ninja_bundle_data_target_writer_unittest.cc
@@ -0,0 +1,55 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "tools/gn/ninja_bundle_data_target_writer.h"
+
+#include <algorithm>
+#include <sstream>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "tools/gn/target.h"
+#include "tools/gn/test_with_scope.h"
+
+TEST(NinjaBundleDataTargetWriter, Run) {
+  Err err;
+
+  TestWithScope setup;
+  setup.build_settings()->SetBuildDir(SourceDir("//out/Debug/"));
+
+  Target bundle_data(setup.settings(), Label(SourceDir("//foo/"), "data"));
+  bundle_data.set_output_type(Target::BUNDLE_DATA);
+  bundle_data.sources().push_back(SourceFile("//foo/input1.txt"));
+  bundle_data.sources().push_back(SourceFile("//foo/input2.txt"));
+  bundle_data.sources().push_back(
+      SourceFile("//foo/Foo.xcassets/Contents.json"));
+  bundle_data.sources().push_back(
+      SourceFile("//foo/Foo.xcassets/foo.imageset/Contents.json"));
+  bundle_data.sources().push_back(
+      SourceFile("//foo/Foo.xcassets/foo.imageset/FooIcon-29.png"));
+  bundle_data.sources().push_back(
+      SourceFile("//foo/Foo.xcassets/foo.imageset/FooIcon-29@2x.png"));
+  bundle_data.sources().push_back(
+      SourceFile("//foo/Foo.xcassets/foo.imageset/FooIcon-29@3x.png"));
+  bundle_data.action_values().outputs() = SubstitutionList::MakeForTest(
+      "{{bundle_resources_dir}}/{{source_file_part}}");
+  bundle_data.SetToolchain(setup.toolchain());
+  bundle_data.visibility().SetPublic();
+  ASSERT_TRUE(bundle_data.OnResolved(&err));
+
+  std::ostringstream out;
+  NinjaBundleDataTargetWriter writer(&bundle_data, out);
+  writer.Run();
+
+  const char expected[] =
+      "build obj/foo/data.stamp: stamp "
+          "../../foo/input1.txt "
+          "../../foo/input2.txt "
+          "../../foo/Foo.xcassets/Contents.json "
+          "../../foo/Foo.xcassets/foo.imageset/Contents.json "
+          "../../foo/Foo.xcassets/foo.imageset/FooIcon-29.png "
+          "../../foo/Foo.xcassets/foo.imageset/FooIcon-29@2x.png "
+          "../../foo/Foo.xcassets/foo.imageset/FooIcon-29@3x.png\n";
+  std::string out_str = out.str();
+  EXPECT_EQ(expected, out_str);
+}
diff --git a/tools/gn/ninja_create_bundle_target_writer.cc b/tools/gn/ninja_create_bundle_target_writer.cc
index 26701e5..351ec0c 100644
--- a/tools/gn/ninja_create_bundle_target_writer.cc
+++ b/tools/gn/ninja_create_bundle_target_writer.cc
@@ -57,13 +57,10 @@
 
   std::string code_signing_rule_name = WriteCodeSigningRuleDefinition();
 
-  OutputFile input_dep =
-      WriteInputDepsStampAndGetDep(std::vector<const Target*>());
-
   std::vector<OutputFile> output_files;
-  WriteCopyBundleDataRules(input_dep, &output_files);
-  WriteCompileAssetsCatalogRule(input_dep, &output_files);
-  WriteCodeSigningRules(code_signing_rule_name, input_dep, &output_files);
+  WriteCopyBundleDataSteps(&output_files);
+  WriteCompileAssetsCatalogStep(&output_files);
+  WriteCodeSigningStep(code_signing_rule_name, &output_files);
 
   std::vector<OutputFile> order_only_deps;
   for (const auto& pair : target_->data_deps())
@@ -113,37 +110,41 @@
   return custom_rule_name;
 }
 
-void NinjaCreateBundleTargetWriter::WriteCopyBundleDataRules(
-    const OutputFile& input_dep,
+void NinjaCreateBundleTargetWriter::WriteCopyBundleDataSteps(
     std::vector<OutputFile>* output_files) {
-  for (const BundleFileRule& file_rule : target_->bundle_data().file_rules()) {
-    for (const SourceFile& source_file : file_rule.sources()) {
-      OutputFile output_file = file_rule.ApplyPatternToSourceAsOutputFile(
-          settings_, target_->bundle_data(), source_file);
-      output_files->push_back(output_file);
+  for (const BundleFileRule& file_rule : target_->bundle_data().file_rules())
+    WriteCopyBundleFileRuleSteps(file_rule, output_files);
+}
 
-      out_ << "build ";
-      path_output_.WriteFile(out_, output_file);
-      out_ << ": "
-           << GetNinjaRulePrefixForToolchain(settings_)
-           << Toolchain::ToolTypeToName(Toolchain::TYPE_COPY_BUNDLE_DATA)
-           << " ";
-      path_output_.WriteFile(out_, source_file);
-      if (!input_dep.value().empty()) {
-        out_ << " | ";
-        path_output_.WriteFile(out_, input_dep);
-      }
-      out_ << std::endl;
-    }
+void NinjaCreateBundleTargetWriter::WriteCopyBundleFileRuleSteps(
+    const BundleFileRule& file_rule,
+    std::vector<OutputFile>* output_files) {
+  // Note that we don't write implicit deps for copy steps. "copy_bundle_data"
+  // steps as this is most likely implemented using hardlink in the common case.
+  // See NinjaCopyTargetWriter::WriteCopyRules() for a detailed explanation.
+  for (const SourceFile& source_file : file_rule.sources()) {
+    OutputFile output_file = file_rule.ApplyPatternToSourceAsOutputFile(
+        settings_, target_->bundle_data(), source_file);
+    output_files->push_back(output_file);
+
+    out_ << "build ";
+    path_output_.WriteFile(out_, output_file);
+    out_ << ": " << GetNinjaRulePrefixForToolchain(settings_)
+         << Toolchain::ToolTypeToName(Toolchain::TYPE_COPY_BUNDLE_DATA) << " ";
+    path_output_.WriteFile(out_, source_file);
+    out_ << std::endl;
   }
 }
 
-void NinjaCreateBundleTargetWriter::WriteCompileAssetsCatalogRule(
-    const OutputFile& input_dep,
+void NinjaCreateBundleTargetWriter::WriteCompileAssetsCatalogStep(
     std::vector<OutputFile>* output_files) {
-  if (target_->bundle_data().asset_catalog_sources().empty())
+  if (target_->bundle_data().assets_catalog_sources().empty())
     return;
 
+  OutputFile input_dep = WriteCompileAssetsCatalogInputDepsStamp(
+      target_->bundle_data().assets_catalog_deps());
+  DCHECK(!input_dep.value().empty());
+
   OutputFile output_file(settings_->build_settings(),
                          target_->bundle_data().GetCompiledAssetCatalogPath());
   output_files->push_back(output_file);
@@ -154,40 +155,53 @@
        << Toolchain::ToolTypeToName(Toolchain::TYPE_COMPILE_XCASSETS);
 
   std::set<SourceFile> asset_catalog_bundles;
-  for (const auto& source : target_->bundle_data().asset_catalog_sources()) {
-    SourceFile asset_catalog_bundle;
-    CHECK(IsSourceFileFromAssetCatalog(source, &asset_catalog_bundle));
-    if (asset_catalog_bundles.find(asset_catalog_bundle) !=
-        asset_catalog_bundles.end())
-      continue;
+  for (const auto& source : target_->bundle_data().assets_catalog_sources()) {
     out_ << " ";
-    path_output_.WriteFile(out_, asset_catalog_bundle);
-    asset_catalog_bundles.insert(asset_catalog_bundle);
+    path_output_.WriteFile(out_, source);
+    asset_catalog_bundles.insert(source);
   }
 
-  out_ << " |";
-  for (const auto& source : target_->bundle_data().asset_catalog_sources()) {
-    out_ << " ";
-    path_output_.WriteFile(
-        out_, OutputFile(settings_->build_settings(), source));
-  }
-
-  if (!input_dep.value().empty()) {
-    out_ << " ";
-    path_output_.WriteFile(out_, input_dep);
-  }
+  out_ << " | ";
+  path_output_.WriteFile(out_, input_dep);
   out_ << std::endl;
 }
 
-void NinjaCreateBundleTargetWriter::WriteCodeSigningRules(
+OutputFile
+NinjaCreateBundleTargetWriter::WriteCompileAssetsCatalogInputDepsStamp(
+    const std::vector<const Target*>& dependencies) {
+  DCHECK(!dependencies.empty());
+  if (dependencies.size() == 1)
+    return dependencies[0]->dependency_output_file();
+
+  OutputFile xcassets_input_stamp_file =
+      OutputFile(RebasePath(GetTargetOutputDir(target_).value(),
+                            settings_->build_settings()->build_dir(),
+                            settings_->build_settings()->root_path_utf8()));
+  xcassets_input_stamp_file.value().append(target_->label().name());
+  xcassets_input_stamp_file.value().append(".xcassets.inputdeps.stamp");
+
+  out_ << "build ";
+  path_output_.WriteFile(out_, xcassets_input_stamp_file);
+  out_ << ": " << GetNinjaRulePrefixForToolchain(settings_)
+       << Toolchain::ToolTypeToName(Toolchain::TYPE_STAMP);
+
+  for (const Target* target : dependencies) {
+    out_ << " ";
+    path_output_.WriteFile(out_, target->dependency_output_file());
+  }
+  out_ << std::endl;
+  return xcassets_input_stamp_file;
+}
+
+void NinjaCreateBundleTargetWriter::WriteCodeSigningStep(
     const std::string& code_signing_rule_name,
-    const OutputFile& input_dep,
     std::vector<OutputFile>* output_files) {
   if (code_signing_rule_name.empty())
     return;
 
   OutputFile code_signing_input_stamp_file =
-      WriteCodeSigningInputDepsStamp(input_dep, output_files);
+      WriteCodeSigningInputDepsStamp(output_files);
+  DCHECK(!code_signing_input_stamp_file.value().empty());
 
   out_ << "build";
   std::vector<OutputFile> code_signing_output_files;
@@ -197,19 +211,17 @@
   path_output_.WriteFiles(out_, code_signing_output_files);
 
   // Since the code signature step depends on all the files from the bundle,
-  // the create_bundle stamp can just depends on the output of the signature.
+  // the create_bundle stamp can just depends on the output of the signature
+  // script (dependencies are transitive).
   output_files->swap(code_signing_output_files);
 
   out_ << ": " << code_signing_rule_name;
-  if (!code_signing_input_stamp_file.value().empty()) {
-    out_ << " | ";
-    path_output_.WriteFile(out_, code_signing_input_stamp_file);
-  }
+  out_ << " | ";
+  path_output_.WriteFile(out_, code_signing_input_stamp_file);
   out_ << std::endl;
 }
 
 OutputFile NinjaCreateBundleTargetWriter::WriteCodeSigningInputDepsStamp(
-    const OutputFile& input_dep,
     std::vector<OutputFile>* output_files) {
   std::vector<SourceFile> code_signing_input_files;
   code_signing_input_files.push_back(
@@ -222,15 +234,32 @@
     code_signing_input_files.push_back(
         output_file.AsSourceFile(settings_->build_settings()));
   }
-  if (!input_dep.value().empty()) {
-    code_signing_input_files.push_back(
-        input_dep.AsSourceFile(settings_->build_settings()));
+
+  std::vector<const Target*> dependencies;
+  for (const auto& label_target_pair : target_->private_deps()) {
+    if (label_target_pair.ptr->output_type() == Target::BUNDLE_DATA)
+      continue;
+    dependencies.push_back(label_target_pair.ptr);
+  }
+  for (const auto& label_target_pair : target_->public_deps()) {
+    if (label_target_pair.ptr->output_type() == Target::BUNDLE_DATA)
+      continue;
+    dependencies.push_back(label_target_pair.ptr);
   }
 
   DCHECK(!code_signing_input_files.empty());
-  if (code_signing_input_files.size() == 1)
+  if (code_signing_input_files.size() == 1 && dependencies.empty())
     return OutputFile(settings_->build_settings(), code_signing_input_files[0]);
 
+  // Remove possible duplicates (if a target is listed in both deps and
+  // public_deps.
+  std::sort(dependencies.begin(), dependencies.end(),
+            [](const Target* lhs, const Target* rhs) -> bool {
+              return lhs->label() < rhs->label();
+            });
+  dependencies.erase(std::unique(dependencies.begin(), dependencies.end()),
+                     dependencies.end());
+
   OutputFile code_signing_input_stamp_file =
       OutputFile(RebasePath(GetTargetOutputDir(target_).value(),
                             settings_->build_settings()->build_dir(),
@@ -247,6 +276,10 @@
     out_ << " ";
     path_output_.WriteFile(out_, source);
   }
+  for (const Target* target : dependencies) {
+    out_ << " ";
+    path_output_.WriteFile(out_, target->dependency_output_file());
+  }
   out_ << std::endl;
   return code_signing_input_stamp_file;
 }
diff --git a/tools/gn/ninja_create_bundle_target_writer.h b/tools/gn/ninja_create_bundle_target_writer.h
index 7c4239c..824e7d9 100644
--- a/tools/gn/ninja_create_bundle_target_writer.h
+++ b/tools/gn/ninja_create_bundle_target_writer.h
@@ -8,6 +8,8 @@
 #include "base/macros.h"
 #include "tools/gn/ninja_target_writer.h"
 
+class BundleFileRule;
+
 // Writes a .ninja file for a bundle_data target type.
 class NinjaCreateBundleTargetWriter : public NinjaTargetWriter {
  public:
@@ -23,33 +25,37 @@
   // defined, otherwise returns an empty string.
   std::string WriteCodeSigningRuleDefinition();
 
-  // Writes the rule to copy files into the bundle.
+  // Writes the steps to copy files into the bundle.
   //
-  // input_dep is a file expressing the shared dependencies. It will be a
-  // stamp file if there is more than one.
-  void WriteCopyBundleDataRules(const OutputFile& input_dep,
-                                std::vector<OutputFile>* output_files);
+  // The list of newly created files will be added to |output_files|.
+  void WriteCopyBundleDataSteps(std::vector<OutputFile>* output_files);
 
-  // Writes the rule to compile assets catalogs.
+  // Writes the step to copy files BundleFileRule into the bundle.
   //
-  // input_dep is a file expressing the shared dependencies. It will be a
-  // stamp file if there is more than one.
-  void WriteCompileAssetsCatalogRule(const OutputFile& input_dep,
-                                     std::vector<OutputFile>* output_files);
+  // The list of newly created files will be added to |output_files|.
+  void WriteCopyBundleFileRuleSteps(const BundleFileRule& file_rule,
+                                    std::vector<OutputFile>* output_files);
 
-  // Writes the code signing rule (if a script is defined).
+  // Writes the step to compile assets catalogs.
   //
-  // input_dep is a file expressing the shared dependencies. It will be a
-  // stamp file if there is more than one. As the code signing may include
-  // a manifest of the file, this will depends on all files in output_files
-  // too.
-  void WriteCodeSigningRules(const std::string& code_signing_rule_name,
-                             const OutputFile& input_dep,
-                             std::vector<OutputFile>* output_files);
+  // The list of newly created files will be added to |output_files|.
+  void WriteCompileAssetsCatalogStep(std::vector<OutputFile>* output_files);
+
+  // Writes the stamp file for the assets catalog compilation input
+  // dependencies.
+  OutputFile WriteCompileAssetsCatalogInputDepsStamp(
+      const std::vector<const Target*>& dependencies);
+
+  // Writes the code signing step (if a script is defined).
+  //
+  // The list of newly created files will be added to |output_files|. As the
+  // code signing may depends on the full bundle structure, this step will
+  // depends on all files generated via other rules.
+  void WriteCodeSigningStep(const std::string& code_signing_rule_name,
+                            std::vector<OutputFile>* output_files);
 
   // Writes the stamp file for the code signing input dependencies.
   OutputFile WriteCodeSigningInputDepsStamp(
-      const OutputFile& input_dep,
       std::vector<OutputFile>* output_files);
 
   DISALLOW_COPY_AND_ASSIGN(NinjaCreateBundleTargetWriter);
diff --git a/tools/gn/ninja_create_bundle_target_writer_unittest.cc b/tools/gn/ninja_create_bundle_target_writer_unittest.cc
index 1c6f8e0..876aeda 100644
--- a/tools/gn/ninja_create_bundle_target_writer_unittest.cc
+++ b/tools/gn/ninja_create_bundle_target_writer_unittest.cc
@@ -14,124 +14,44 @@
 namespace {
 
 void SetupBundleDataDir(BundleData* bundle_data, const std::string& root_dir) {
-  std::string bundle_root_dir = root_dir + "/bar.bundle";
+  std::string bundle_root_dir = root_dir + "/bar.bundle/Contents";
   bundle_data->root_dir() = SourceDir(bundle_root_dir);
   bundle_data->resources_dir() = SourceDir(bundle_root_dir + "/Resources");
-  bundle_data->executable_dir() = SourceDir(bundle_root_dir + "/Executable");
-  bundle_data->plugins_dir() = SourceDir(bundle_root_dir + "/PlugIns");
+  bundle_data->executable_dir() = SourceDir(bundle_root_dir + "/MacOS");
+  bundle_data->plugins_dir() = SourceDir(bundle_root_dir + "/Plug Ins");
 }
 
 }  // namespace
 
 // Tests multiple files with an output pattern.
 TEST(NinjaCreateBundleTargetWriter, Run) {
-  TestWithScope setup;
   Err err;
 
-  setup.build_settings()->SetBuildDir(SourceDir("//out/Debug/"));
-  Target target(setup.settings(), Label(SourceDir("//baz/"), "bar"));
-  target.set_output_type(Target::CREATE_BUNDLE);
-
-  SetupBundleDataDir(&target.bundle_data(), "//out/Debug");
-
-  std::vector<SourceFile> sources;
-  sources.push_back(SourceFile("//foo/input1.txt"));
-  sources.push_back(SourceFile("//foo/input2.txt"));
-  target.bundle_data().file_rules().push_back(BundleFileRule(
-      sources, SubstitutionPattern::MakeForTest(
-                   "{{bundle_resources_dir}}/{{source_file_part}}")));
-
-  target.SetToolchain(setup.toolchain());
-  ASSERT_TRUE(target.OnResolved(&err));
-
-  std::ostringstream out;
-  NinjaCreateBundleTargetWriter writer(&target, out);
-  writer.Run();
-
-  const char expected[] =
-      "build bar.bundle/Resources/input1.txt: copy_bundle_data "
-          "../../foo/input1.txt\n"
-      "build bar.bundle/Resources/input2.txt: copy_bundle_data "
-          "../../foo/input2.txt\n"
-      "build obj/baz/bar.stamp: stamp "
-          "bar.bundle/Resources/input1.txt "
-          "bar.bundle/Resources/input2.txt\n"
-      "build bar.bundle: phony obj/baz/bar.stamp\n";
-  std::string out_str = out.str();
-  EXPECT_EQ(expected, out_str);
-}
-
-// Tests multiple files from asset catalog.
-TEST(NinjaCreateBundleTargetWriter, AssetCatalog) {
   TestWithScope setup;
-  Err err;
-
   setup.build_settings()->SetBuildDir(SourceDir("//out/Debug/"));
-  Target target(setup.settings(), Label(SourceDir("//baz/"), "bar"));
-  target.set_output_type(Target::CREATE_BUNDLE);
 
-  SetupBundleDataDir(&target.bundle_data(), "//out/Debug");
+  Target bundle_data(setup.settings(), Label(SourceDir("//foo/"), "data"));
+  bundle_data.set_output_type(Target::BUNDLE_DATA);
+  bundle_data.sources().push_back(SourceFile("//foo/input1.txt"));
+  bundle_data.sources().push_back(SourceFile("//foo/input2.txt"));
+  bundle_data.action_values().outputs() = SubstitutionList::MakeForTest(
+      "{{bundle_resources_dir}}/{{source_file_part}}");
+  bundle_data.SetToolchain(setup.toolchain());
+  bundle_data.visibility().SetPublic();
+  ASSERT_TRUE(bundle_data.OnResolved(&err));
 
-  std::vector<SourceFile>& asset_catalog_sources =
-      target.bundle_data().asset_catalog_sources();
-  asset_catalog_sources.push_back(
-      SourceFile("//foo/Foo.xcassets/foo.imageset/Contents.json"));
-  asset_catalog_sources.push_back(
-      SourceFile("//foo/Foo.xcassets/foo.imageset/FooIcon-29.png"));
-  asset_catalog_sources.push_back(
-      SourceFile("//foo/Foo.xcassets/foo.imageset/FooIcon-29@2x.png"));
-  asset_catalog_sources.push_back(
-      SourceFile("//foo/Foo.xcassets/foo.imageset/FooIcon-29@3x.png"));
-
-  target.SetToolchain(setup.toolchain());
-  ASSERT_TRUE(target.OnResolved(&err));
+  Target create_bundle(
+      setup.settings(),
+      Label(SourceDir("//baz/"), "bar", setup.toolchain()->label().dir(),
+            setup.toolchain()->label().name()));
+  SetupBundleDataDir(&create_bundle.bundle_data(), "//out/Debug");
+  create_bundle.set_output_type(Target::CREATE_BUNDLE);
+  create_bundle.private_deps().push_back(LabelTargetPair(&bundle_data));
+  create_bundle.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(create_bundle.OnResolved(&err));
 
   std::ostringstream out;
-  NinjaCreateBundleTargetWriter writer(&target, out);
-  writer.Run();
-
-  const char expected[] =
-      "build bar.bundle/Resources/Assets.car: compile_xcassets "
-          "../../foo/Foo.xcassets | "
-          "../../foo/Foo.xcassets/foo.imageset/Contents.json "
-          "../../foo/Foo.xcassets/foo.imageset/FooIcon-29.png "
-          "../../foo/Foo.xcassets/foo.imageset/FooIcon-29@2x.png "
-          "../../foo/Foo.xcassets/foo.imageset/FooIcon-29@3x.png\n"
-      "build obj/baz/bar.stamp: stamp bar.bundle/Resources/Assets.car\n"
-      "build bar.bundle: phony obj/baz/bar.stamp\n";
-  std::string out_str = out.str();
-  EXPECT_EQ(expected, out_str);
-}
-
-// Tests that the phony target for the top-level bundle directory is generated
-// correctly.
-TEST(NinjaCreateBundleTargetWriter, BundleRootDirOutput) {
-  TestWithScope setup;
-  Err err;
-
-  setup.build_settings()->SetBuildDir(SourceDir("//out/Debug/"));
-  Target target(setup.settings(), Label(SourceDir("//baz/"), "bar"));
-  target.set_output_type(Target::CREATE_BUNDLE);
-
-  const std::string bundle_root_dir("//out/Debug/bar.bundle/Contents");
-  target.bundle_data().root_dir() = SourceDir(bundle_root_dir);
-  target.bundle_data().resources_dir() =
-      SourceDir(bundle_root_dir + "/Resources");
-  target.bundle_data().executable_dir() = SourceDir(bundle_root_dir + "/MacOS");
-  target.bundle_data().plugins_dir() = SourceDir(bundle_root_dir + "/Plug Ins");
-
-  std::vector<SourceFile> sources;
-  sources.push_back(SourceFile("//foo/input1.txt"));
-  sources.push_back(SourceFile("//foo/input2.txt"));
-  target.bundle_data().file_rules().push_back(BundleFileRule(
-      sources, SubstitutionPattern::MakeForTest(
-                   "{{bundle_resources_dir}}/{{source_file_part}}")));
-
-  target.SetToolchain(setup.toolchain());
-  ASSERT_TRUE(target.OnResolved(&err));
-
-  std::ostringstream out;
-  NinjaCreateBundleTargetWriter writer(&target, out);
+  NinjaCreateBundleTargetWriter writer(&create_bundle, out);
   writer.Run();
 
   const char expected[] =
@@ -147,150 +67,257 @@
   EXPECT_EQ(expected, out_str);
 }
 
-// Tests complex target with multiple bundle_data sources, including
-// some asset catalog.
-TEST(NinjaCreateBundleTargetWriter, ImplicitDeps) {
-  TestWithScope setup;
+// Tests multiple files from asset catalog.
+TEST(NinjaCreateBundleTargetWriter, AssetCatalog) {
   Err err;
 
+  TestWithScope setup;
   setup.build_settings()->SetBuildDir(SourceDir("//out/Debug/"));
-  Target target(setup.settings(), Label(SourceDir("//baz/"), "bar"));
-  target.set_output_type(Target::CREATE_BUNDLE);
 
-  SetupBundleDataDir(&target.bundle_data(), "//out/Debug");
-
-  std::vector<SourceFile> sources1;
-  sources1.push_back(SourceFile("//foo/input1.txt"));
-  sources1.push_back(SourceFile("//foo/input2.txt"));
-  target.bundle_data().file_rules().push_back(BundleFileRule(
-      sources1, SubstitutionPattern::MakeForTest(
-                    "{{bundle_resources_dir}}/{{source_file_part}}")));
-
-  std::vector<SourceFile> sources2;
-  sources2.push_back(SourceFile("//qux/Info.plist"));
-  target.bundle_data().file_rules().push_back(BundleFileRule(
-      sources2,
-      SubstitutionPattern::MakeForTest("{{bundle_root_dir}}/Info.plist")));
-
-  std::vector<SourceFile> empty_source;
-  target.bundle_data().file_rules().push_back(BundleFileRule(
-      empty_source, SubstitutionPattern::MakeForTest(
-                        "{{bundle_plugins_dir}}/{{source_file_part}}")));
-
-  std::vector<SourceFile>& asset_catalog_sources =
-      target.bundle_data().asset_catalog_sources();
-  asset_catalog_sources.push_back(
+  Target bundle_data(setup.settings(), Label(SourceDir("//foo/"), "data"));
+  bundle_data.set_output_type(Target::BUNDLE_DATA);
+  bundle_data.sources().push_back(
+      SourceFile("//foo/Foo.xcassets/Contents.json"));
+  bundle_data.sources().push_back(
       SourceFile("//foo/Foo.xcassets/foo.imageset/Contents.json"));
-  asset_catalog_sources.push_back(
+  bundle_data.sources().push_back(
       SourceFile("//foo/Foo.xcassets/foo.imageset/FooIcon-29.png"));
-  asset_catalog_sources.push_back(
+  bundle_data.sources().push_back(
       SourceFile("//foo/Foo.xcassets/foo.imageset/FooIcon-29@2x.png"));
-  asset_catalog_sources.push_back(
+  bundle_data.sources().push_back(
       SourceFile("//foo/Foo.xcassets/foo.imageset/FooIcon-29@3x.png"));
+  bundle_data.action_values().outputs() = SubstitutionList::MakeForTest(
+      "{{bundle_resources_dir}}/{{source_file_part}}");
+  bundle_data.SetToolchain(setup.toolchain());
+  bundle_data.visibility().SetPublic();
+  ASSERT_TRUE(bundle_data.OnResolved(&err));
 
-  target.SetToolchain(setup.toolchain());
-  ASSERT_TRUE(target.OnResolved(&err));
+  Target create_bundle(
+      setup.settings(),
+      Label(SourceDir("//baz/"), "bar", setup.toolchain()->label().dir(),
+            setup.toolchain()->label().name()));
+  SetupBundleDataDir(&create_bundle.bundle_data(), "//out/Debug");
+  create_bundle.set_output_type(Target::CREATE_BUNDLE);
+  create_bundle.private_deps().push_back(LabelTargetPair(&bundle_data));
+  create_bundle.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(create_bundle.OnResolved(&err));
 
   std::ostringstream out;
-  NinjaCreateBundleTargetWriter writer(&target, out);
+  NinjaCreateBundleTargetWriter writer(&create_bundle, out);
   writer.Run();
 
   const char expected[] =
-      "build bar.bundle/Resources/input1.txt: copy_bundle_data "
-          "../../foo/input1.txt\n"
-      "build bar.bundle/Resources/input2.txt: copy_bundle_data "
-          "../../foo/input2.txt\n"
-      "build bar.bundle/Info.plist: copy_bundle_data "
-          "../../qux/Info.plist\n"
-      "build bar.bundle/Resources/Assets.car: compile_xcassets "
-          "../../foo/Foo.xcassets | "
-          "../../foo/Foo.xcassets/foo.imageset/Contents.json "
-          "../../foo/Foo.xcassets/foo.imageset/FooIcon-29.png "
-          "../../foo/Foo.xcassets/foo.imageset/FooIcon-29@2x.png "
-          "../../foo/Foo.xcassets/foo.imageset/FooIcon-29@3x.png\n"
+      "build bar.bundle/Contents/Resources/Assets.car: compile_xcassets "
+          "../../foo/Foo.xcassets | obj/foo/data.stamp\n"
       "build obj/baz/bar.stamp: stamp "
-          "bar.bundle/Resources/input1.txt "
-          "bar.bundle/Resources/input2.txt "
-          "bar.bundle/Info.plist "
-          "bar.bundle/Resources/Assets.car\n"
+          "bar.bundle/Contents/Resources/Assets.car\n"
       "build bar.bundle: phony obj/baz/bar.stamp\n";
   std::string out_str = out.str();
   EXPECT_EQ(expected, out_str);
 }
 
-// Tests multiple files with an output pattern.
-TEST(NinjaCreateBundleTargetWriter, CodeSigning) {
-  TestWithScope setup;
+// Tests that the phony target for the top-level bundle directory is generated
+// correctly.
+TEST(NinjaCreateBundleTargetWriter, PhonyTarget) {
   Err err;
 
+  TestWithScope setup;
   setup.build_settings()->SetBuildDir(SourceDir("//out/Debug/"));
 
-  // Simulate a binary build by another target. Since no toolchain is defined
-  // use an action instead of an executable target for simplicity.
-  Target binary(setup.settings(), Label(SourceDir("//baz/"), "quz"));
-  binary.set_output_type(Target::EXECUTABLE);
-  binary.visibility().SetPublic();
-  binary.sources().push_back(SourceFile("//baz/quz.c"));
-  binary.set_output_name("obj/baz/quz/bin");
-  binary.set_output_prefix_override(true);
-  binary.SetToolchain(setup.toolchain());
-  ASSERT_TRUE(binary.OnResolved(&err));
-
-  Target target(setup.settings(),
-      Label(SourceDir("//baz/"), "bar",
-      setup.toolchain()->label().dir(),
-      setup.toolchain()->label().name()));
-  target.set_output_type(Target::CREATE_BUNDLE);
-
-  SetupBundleDataDir(&target.bundle_data(), "//out/Debug");
-
-  std::vector<SourceFile> sources;
-  sources.push_back(SourceFile("//foo/input1.txt"));
-  sources.push_back(SourceFile("//foo/input2.txt"));
-  target.bundle_data().file_rules().push_back(BundleFileRule(
-      sources, SubstitutionPattern::MakeForTest(
-                   "{{bundle_resources_dir}}/{{source_file_part}}")));
-
-  target.bundle_data().set_code_signing_script(
-      SourceFile("//build/codesign.py"));
-  target.bundle_data().code_signing_sources().push_back(
-      SourceFile("//out/Debug/obj/baz/quz/bin"));
-  target.bundle_data().code_signing_outputs() = SubstitutionList::MakeForTest(
-      "//out/Debug/bar.bundle/quz",
-      "//out/Debug/bar.bundle/_CodeSignature/CodeResources");
-  target.bundle_data().code_signing_args() = SubstitutionList::MakeForTest(
-      "-b=obj/baz/quz/bin",
-      "bar.bundle");
-
-  target.public_deps().push_back(LabelTargetPair(&binary));
-
-  target.SetToolchain(setup.toolchain());
-  ASSERT_TRUE(target.OnResolved(&err));
+  Target create_bundle(
+      setup.settings(),
+      Label(SourceDir("//baz/"), "bar", setup.toolchain()->label().dir(),
+            setup.toolchain()->label().name()));
+  SetupBundleDataDir(&create_bundle.bundle_data(), "//out/Debug");
+  create_bundle.set_output_type(Target::CREATE_BUNDLE);
+  create_bundle.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(create_bundle.OnResolved(&err));
 
   std::ostringstream out;
-  NinjaCreateBundleTargetWriter writer(&target, out);
+  NinjaCreateBundleTargetWriter writer(&create_bundle, out);
+  writer.Run();
+
+  const char expected[] =
+      "build obj/baz/bar.stamp: stamp\n"
+      "build bar.bundle: phony obj/baz/bar.stamp\n";
+  std::string out_str = out.str();
+  EXPECT_EQ(expected, out_str);
+}
+
+// Tests complex target with multiple bundle_data sources, including
+// some asset catalog.
+TEST(NinjaCreateBundleTargetWriter, Complex) {
+  Err err;
+
+  TestWithScope setup;
+  setup.build_settings()->SetBuildDir(SourceDir("//out/Debug/"));
+
+  Target bundle_data0(setup.settings(),
+                      Label(SourceDir("//qux/"), "info_plist"));
+  bundle_data0.set_output_type(Target::BUNDLE_DATA);
+  bundle_data0.sources().push_back(SourceFile("//qux/qux-Info.plist"));
+  bundle_data0.action_values().outputs() =
+      SubstitutionList::MakeForTest("{{bundle_root_dir}}/Info.plist");
+  bundle_data0.SetToolchain(setup.toolchain());
+  bundle_data0.visibility().SetPublic();
+  ASSERT_TRUE(bundle_data0.OnResolved(&err));
+
+  Target bundle_data1(setup.settings(), Label(SourceDir("//foo/"), "data"));
+  bundle_data1.set_output_type(Target::BUNDLE_DATA);
+  bundle_data1.sources().push_back(SourceFile("//foo/input1.txt"));
+  bundle_data1.sources().push_back(SourceFile("//foo/input2.txt"));
+  bundle_data1.action_values().outputs() = SubstitutionList::MakeForTest(
+      "{{bundle_resources_dir}}/{{source_file_part}}");
+  bundle_data1.SetToolchain(setup.toolchain());
+  bundle_data1.visibility().SetPublic();
+  ASSERT_TRUE(bundle_data1.OnResolved(&err));
+
+  Target bundle_data2(setup.settings(), Label(SourceDir("//foo/"), "assets"));
+  bundle_data2.set_output_type(Target::BUNDLE_DATA);
+  bundle_data2.sources().push_back(
+      SourceFile("//foo/Foo.xcassets/Contents.json"));
+  bundle_data2.sources().push_back(
+      SourceFile("//foo/Foo.xcassets/foo.imageset/Contents.json"));
+  bundle_data2.sources().push_back(
+      SourceFile("//foo/Foo.xcassets/foo.imageset/FooIcon-29.png"));
+  bundle_data2.sources().push_back(
+      SourceFile("//foo/Foo.xcassets/foo.imageset/FooIcon-29@2x.png"));
+  bundle_data2.sources().push_back(
+      SourceFile("//foo/Foo.xcassets/foo.imageset/FooIcon-29@3x.png"));
+  bundle_data2.action_values().outputs() = SubstitutionList::MakeForTest(
+      "{{bundle_resources_dir}}/{{source_file_part}}");
+  bundle_data2.SetToolchain(setup.toolchain());
+  bundle_data2.visibility().SetPublic();
+  ASSERT_TRUE(bundle_data2.OnResolved(&err));
+
+  Target bundle_data3(setup.settings(), Label(SourceDir("//quz/"), "assets"));
+  bundle_data3.set_output_type(Target::BUNDLE_DATA);
+  bundle_data3.sources().push_back(
+      SourceFile("//quz/Quz.xcassets/Contents.json"));
+  bundle_data3.sources().push_back(
+      SourceFile("//quz/Quz.xcassets/quz.imageset/Contents.json"));
+  bundle_data3.sources().push_back(
+      SourceFile("//quz/Quz.xcassets/quz.imageset/QuzIcon-29.png"));
+  bundle_data3.sources().push_back(
+      SourceFile("//quz/Quz.xcassets/quz.imageset/QuzIcon-29@2x.png"));
+  bundle_data3.sources().push_back(
+      SourceFile("//quz/Quz.xcassets/quz.imageset/QuzIcon-29@3x.png"));
+  bundle_data3.action_values().outputs() = SubstitutionList::MakeForTest(
+      "{{bundle_resources_dir}}/{{source_file_part}}");
+  bundle_data3.SetToolchain(setup.toolchain());
+  bundle_data3.visibility().SetPublic();
+  ASSERT_TRUE(bundle_data3.OnResolved(&err));
+
+  Target create_bundle(
+      setup.settings(),
+      Label(SourceDir("//baz/"), "bar", setup.toolchain()->label().dir(),
+            setup.toolchain()->label().name()));
+  SetupBundleDataDir(&create_bundle.bundle_data(), "//out/Debug");
+  create_bundle.set_output_type(Target::CREATE_BUNDLE);
+  create_bundle.private_deps().push_back(LabelTargetPair(&bundle_data0));
+  create_bundle.private_deps().push_back(LabelTargetPair(&bundle_data1));
+  create_bundle.private_deps().push_back(LabelTargetPair(&bundle_data2));
+  create_bundle.private_deps().push_back(LabelTargetPair(&bundle_data3));
+  create_bundle.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(create_bundle.OnResolved(&err));
+
+  std::ostringstream out;
+  NinjaCreateBundleTargetWriter writer(&create_bundle, out);
+  writer.Run();
+
+  const char expected[] =
+      "build bar.bundle/Contents/Info.plist: copy_bundle_data "
+          "../../qux/qux-Info.plist\n"
+      "build bar.bundle/Contents/Resources/input1.txt: copy_bundle_data "
+          "../../foo/input1.txt\n"
+      "build bar.bundle/Contents/Resources/input2.txt: copy_bundle_data "
+          "../../foo/input2.txt\n"
+      "build obj/baz/bar.xcassets.inputdeps.stamp: stamp "
+          "obj/foo/assets.stamp "
+          "obj/quz/assets.stamp\n"
+      "build bar.bundle/Contents/Resources/Assets.car: compile_xcassets "
+          "../../foo/Foo.xcassets "
+          "../../quz/Quz.xcassets | obj/baz/bar.xcassets.inputdeps.stamp\n"
+      "build obj/baz/bar.stamp: stamp "
+          "bar.bundle/Contents/Info.plist "
+          "bar.bundle/Contents/Resources/input1.txt "
+          "bar.bundle/Contents/Resources/input2.txt "
+          "bar.bundle/Contents/Resources/Assets.car\n"
+      "build bar.bundle: phony obj/baz/bar.stamp\n";
+  std::string out_str = out.str();
+  EXPECT_EQ(expected, out_str);
+}
+
+// Tests code signing steps.
+TEST(NinjaCreateBundleTargetWriter, CodeSigning) {
+  Err err;
+
+  TestWithScope setup;
+  setup.build_settings()->SetBuildDir(SourceDir("//out/Debug/"));
+
+  Target executable(setup.settings(), Label(SourceDir("//baz/"), "quz"));
+  executable.set_output_type(Target::EXECUTABLE);
+  executable.sources().push_back(SourceFile("//baz/quz.c"));
+  executable.SetToolchain(setup.toolchain());
+  executable.visibility().SetPublic();
+  ASSERT_TRUE(executable.OnResolved(&err));
+
+  Target bundle_data(setup.settings(), Label(SourceDir("//foo/"), "data"));
+  bundle_data.set_output_type(Target::BUNDLE_DATA);
+  bundle_data.sources().push_back(SourceFile("//foo/input1.txt"));
+  bundle_data.sources().push_back(SourceFile("//foo/input2.txt"));
+  bundle_data.action_values().outputs() = SubstitutionList::MakeForTest(
+      "{{bundle_resources_dir}}/{{source_file_part}}");
+  bundle_data.SetToolchain(setup.toolchain());
+  bundle_data.visibility().SetPublic();
+  ASSERT_TRUE(bundle_data.OnResolved(&err));
+
+  Target create_bundle(
+      setup.settings(),
+      Label(SourceDir("//baz/"), "bar", setup.toolchain()->label().dir(),
+            setup.toolchain()->label().name()));
+  SetupBundleDataDir(&create_bundle.bundle_data(), "//out/Debug");
+  create_bundle.set_output_type(Target::CREATE_BUNDLE);
+  create_bundle.bundle_data().set_code_signing_script(
+      SourceFile("//build/codesign.py"));
+  create_bundle.bundle_data().code_signing_sources().push_back(
+      SourceFile("//out/Debug/quz"));
+  create_bundle.bundle_data().code_signing_outputs() =
+      SubstitutionList::MakeForTest(
+          "//out/Debug/bar.bundle/Contents/quz",
+          "//out/Debug/bar.bundle/_CodeSignature/CodeResources");
+  create_bundle.bundle_data().code_signing_args() =
+      SubstitutionList::MakeForTest("-b=quz", "bar.bundle");
+  create_bundle.public_deps().push_back(LabelTargetPair(&executable));
+  create_bundle.private_deps().push_back(LabelTargetPair(&bundle_data));
+  create_bundle.SetToolchain(setup.toolchain());
+  ASSERT_TRUE(create_bundle.OnResolved(&err));
+
+  std::ostringstream out;
+  NinjaCreateBundleTargetWriter writer(&create_bundle, out);
   writer.Run();
 
   const char expected[] =
       "rule __baz_bar___toolchain_default__code_signing_rule\n"
-      "  command =  ../../build/codesign.py -b=obj/baz/quz/bin bar.bundle\n"
+      "  command =  ../../build/codesign.py -b=quz bar.bundle\n"
       "  description = CODE SIGNING //baz:bar(//toolchain:default)\n"
       "  restat = 1\n"
       "\n"
-      "build bar.bundle/Resources/input1.txt: copy_bundle_data "
+      "build bar.bundle/Contents/Resources/input1.txt: copy_bundle_data "
           "../../foo/input1.txt\n"
-      "build bar.bundle/Resources/input2.txt: copy_bundle_data "
+      "build bar.bundle/Contents/Resources/input2.txt: copy_bundle_data "
           "../../foo/input2.txt\n"
       "build obj/baz/bar.codesigning.inputdeps.stamp: stamp "
           "../../build/codesign.py "
-          "obj/baz/quz/bin "
-          "bar.bundle/Resources/input1.txt "
-          "bar.bundle/Resources/input2.txt\n"
-      "build bar.bundle/quz bar.bundle/_CodeSignature/CodeResources: "
+          "quz "
+          "bar.bundle/Contents/Resources/input1.txt "
+          "bar.bundle/Contents/Resources/input2.txt "
+          "./quz\n"
+      "build bar.bundle/Contents/quz bar.bundle/_CodeSignature/CodeResources: "
           "__baz_bar___toolchain_default__code_signing_rule "
           "| obj/baz/bar.codesigning.inputdeps.stamp\n"
       "build obj/baz/bar.stamp: stamp "
-          "bar.bundle/quz "
+          "bar.bundle/Contents/quz "
           "bar.bundle/_CodeSignature/CodeResources\n"
       "build bar.bundle: phony obj/baz/bar.stamp\n";
   std::string out_str = out.str();
diff --git a/tools/gn/runtime_deps_unittest.cc b/tools/gn/runtime_deps_unittest.cc
index c4940a9..d0658f4 100644
--- a/tools/gn/runtime_deps_unittest.cc
+++ b/tools/gn/runtime_deps_unittest.cc
@@ -284,6 +284,7 @@
   InitTargetWithType(setup, &module_data, Target::BUNDLE_DATA);
   module_data.private_deps().push_back(LabelTargetPair(&loadable_module));
   module_data.bundle_data().file_rules().push_back(BundleFileRule(
+      nullptr,
       std::vector<SourceFile>{SourceFile(build_dir + "loadable_module.so")},
       SubstitutionPattern::MakeForTest("{{bundle_resources_dir}}")));
   ASSERT_TRUE(module_data.OnResolved(&err));
@@ -305,7 +306,7 @@
   InitTargetWithType(setup, &dylib_data, Target::BUNDLE_DATA);
   dylib_data.private_deps().push_back(LabelTargetPair(&dylib));
   dylib_data.bundle_data().file_rules().push_back(BundleFileRule(
-      std::vector<SourceFile>{SourceFile(build_dir + "dylib")},
+      nullptr, std::vector<SourceFile>{SourceFile(build_dir + "dylib")},
       SubstitutionPattern::MakeForTest("{{bundle_executable_dir}}")));
   ASSERT_TRUE(dylib_data.OnResolved(&err));
 
diff --git a/tools/gn/target_unittest.cc b/tools/gn/target_unittest.cc
index 622dd54..d1c7ab9 100644
--- a/tools/gn/target_unittest.cc
+++ b/tools/gn/target_unittest.cc
@@ -907,7 +907,7 @@
   ASSERT_EQ(a.bundle_data().file_rules().size(), 2u);
   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().asset_catalog_sources().size(), 4u);
+  ASSERT_EQ(a.bundle_data().assets_catalog_sources().size(), 1u);
   ASSERT_EQ(a.bundle_data().bundle_deps().size(), 2u);
 
   // C gets its data from D.
@@ -918,6 +918,6 @@
   // E does not have any bundle_data information but gets a list of
   // bundle_deps to propagate them during target resolution.
   ASSERT_TRUE(e.bundle_data().file_rules().empty());
-  ASSERT_TRUE(e.bundle_data().asset_catalog_sources().empty());
+  ASSERT_TRUE(e.bundle_data().assets_catalog_sources().empty());
   ASSERT_EQ(e.bundle_data().bundle_deps().size(), 2u);
 }