[GN] Add support for code signing to "create_bundle" targets.

To be able to run iOS application on device, they have to be code signed.
The code signature needs all the files to have been copied in the bundle
(as it embeds a manifest) and embeds the signature in the binary. This
prevents running the code signature as a separate action (as it either
run before the file have been copied into the bundle, or it modify the
output of another target).

This CL adds support for running a script to perform the code signing as
the last step of creating the application bundle. If a script is defined,
it is run after all files have been copied into the bundle. The script can
add new file in the bundle but must not modify any file already there (on
iOS this means that the script will copy the binary into the bundle).

While adding support for code signing, found and fixed an issue where the
input dependencies of the create_bundle targets was incorrect (files that
are not generated were not listed as inputs).

BUG=600491

Review-Url: https://codereview.chromium.org/2060273002
Cr-Original-Commit-Position: refs/heads/master@{#399755}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: 14ddbb64afb087115791aeb2237dc880eac6de12
diff --git a/tools/gn/bundle_data.cc b/tools/gn/bundle_data.cc
index e182365..4218a15 100644
--- a/tools/gn/bundle_data.cc
+++ b/tools/gn/bundle_data.cc
@@ -8,6 +8,7 @@
 #include "tools/gn/filesystem_utils.h"
 #include "tools/gn/output_file.h"
 #include "tools/gn/settings.h"
+#include "tools/gn/substitution_writer.h"
 #include "tools/gn/target.h"
 
 namespace {
@@ -82,6 +83,10 @@
   }
   sources->insert(sources->end(), asset_catalog_sources_.begin(),
                   asset_catalog_sources_.end());
+  if (!code_signing_script_.is_null()) {
+    sources->insert(sources->end(), code_signing_sources_.begin(),
+                    code_signing_sources_.end());
+  }
 }
 
 void BundleData::GetOutputFiles(const Settings* settings,
@@ -105,6 +110,15 @@
   if (!asset_catalog_sources_.empty())
     outputs_as_source->push_back(GetCompiledAssetCatalogPath());
 
+  if (!code_signing_script_.is_null()) {
+    std::vector<SourceFile> code_signing_output_files;
+    SubstitutionWriter::GetListAsSourceFiles(code_signing_outputs_,
+                                             &code_signing_output_files);
+    outputs_as_source->insert(outputs_as_source->end(),
+                              code_signing_output_files.begin(),
+                              code_signing_output_files.end());
+  }
+
   if (!root_dir_.is_null())
     outputs_as_source->push_back(GetBundleRootDirOutput(settings));
 }
diff --git a/tools/gn/bundle_data.h b/tools/gn/bundle_data.h
index 3a1ce95..0b040f7 100644
--- a/tools/gn/bundle_data.h
+++ b/tools/gn/bundle_data.h
@@ -8,12 +8,14 @@
 #include <string>
 #include <vector>
 
+#include "tools/gn/action_values.h"
 #include "tools/gn/bundle_file_rule.h"
 #include "tools/gn/source_dir.h"
+#include "tools/gn/source_file.h"
+#include "tools/gn/substitution_list.h"
 #include "tools/gn/unique_vector.h"
 
 class OutputFile;
-class SourceFile;
 class Settings;
 class Target;
 
@@ -104,6 +106,28 @@
   std::string& product_type() { return product_type_; }
   const std::string& product_type() const { return product_type_; }
 
+  void set_code_signing_script(const SourceFile& script_file) {
+    code_signing_script_ = script_file;
+  }
+  const SourceFile& code_signing_script() const { return code_signing_script_; }
+
+  std::vector<SourceFile>& code_signing_sources() {
+    return code_signing_sources_;
+  }
+  const std::vector<SourceFile>& code_signing_sources() const {
+    return code_signing_sources_;
+  }
+
+  SubstitutionList& code_signing_outputs() { return code_signing_outputs_; }
+  const SubstitutionList& code_signing_outputs() const {
+    return code_signing_outputs_;
+  }
+
+  SubstitutionList& code_signing_args() { return code_signing_args_; }
+  const SubstitutionList& code_signing_args() const {
+    return code_signing_args_;
+  }
+
   // Recursive collection of all bundle_data that the target depends on.
   const UniqueTargets& bundle_deps() const { return bundle_deps_; }
 
@@ -123,6 +147,13 @@
   // the Xcode project file when using --ide=xcode.
   std::string product_type_;
 
+  // Holds the values (script name, sources, outputs, script arguments) for the
+  // code signing step if defined.
+  SourceFile code_signing_script_;
+  std::vector<SourceFile> code_signing_sources_;
+  SubstitutionList code_signing_outputs_;
+  SubstitutionList code_signing_args_;
+
   DISALLOW_COPY_AND_ASSIGN(BundleData);
 };
 
diff --git a/tools/gn/command_desc.cc b/tools/gn/command_desc.cc
index f2371e9..d1edfe4 100644
--- a/tools/gn/command_desc.cc
+++ b/tools/gn/command_desc.cc
@@ -337,7 +337,7 @@
     std::vector<SourceFile> output_files;
     target->bundle_data().GetOutputsAsSourceFiles(target->settings(),
                                                   &output_files);
-    PrintFileList(output_files, "", true, false);
+    PrintFileList(output_files, std::string(), true, false);
   } else {
     const SubstitutionList& outputs = target->action_values().outputs();
     if (!outputs.required_types().empty()) {
@@ -355,7 +355,7 @@
     std::vector<SourceFile> output_files;
     SubstitutionWriter::ApplyListToSources(target->settings(), outputs,
                                            target->sources(), &output_files);
-    PrintFileList(output_files, "", true, false);
+    PrintFileList(output_files, std::string(), true, false);
   }
 }
 
diff --git a/tools/gn/create_bundle_target_generator.cc b/tools/gn/create_bundle_target_generator.cc
index 8374998..99bc464 100644
--- a/tools/gn/create_bundle_target_generator.cc
+++ b/tools/gn/create_bundle_target_generator.cc
@@ -4,12 +4,14 @@
 
 #include "tools/gn/create_bundle_target_generator.h"
 
+#include "base/logging.h"
 #include "tools/gn/filesystem_utils.h"
 #include "tools/gn/parse_tree.h"
 #include "tools/gn/scope.h"
 #include "tools/gn/substitution_type.h"
 #include "tools/gn/target.h"
 #include "tools/gn/value.h"
+#include "tools/gn/value_extractors.h"
 #include "tools/gn/variables.h"
 
 CreateBundleTargetGenerator::CreateBundleTargetGenerator(
@@ -25,33 +27,36 @@
   target_->set_output_type(Target::CREATE_BUNDLE);
 
   BundleData& bundle_data = target_->bundle_data();
-  if (!GetBundleDir(SourceDir(),
-                    variables::kBundleRootDir,
-                    &bundle_data.root_dir()))
+  if (!FillBundleDir(SourceDir(), variables::kBundleRootDir,
+                     &bundle_data.root_dir()))
     return;
-  if (!GetBundleDir(bundle_data.root_dir(),
-                    variables::kBundleResourcesDir,
-                    &bundle_data.resources_dir()))
+  if (!FillBundleDir(bundle_data.root_dir(), variables::kBundleResourcesDir,
+                     &bundle_data.resources_dir()))
     return;
-  if (!GetBundleDir(bundle_data.root_dir(),
-                    variables::kBundleExecutableDir,
-                    &bundle_data.executable_dir()))
+  if (!FillBundleDir(bundle_data.root_dir(), variables::kBundleExecutableDir,
+                     &bundle_data.executable_dir()))
     return;
-  if (!GetBundleDir(bundle_data.root_dir(),
-                    variables::kBundlePlugInsDir,
-                    &bundle_data.plugins_dir()))
+  if (!FillBundleDir(bundle_data.root_dir(), variables::kBundlePlugInsDir,
+                     &bundle_data.plugins_dir()))
     return;
 
-  const Value* value = scope_->GetValue(variables::kProductType, true);
-  if (value) {
-    if (!value->VerifyTypeIs(Value::STRING, err_))
-      return;
+  if (!FillProductType())
+    return;
 
-    bundle_data.product_type().assign(value->string_value());
-  }
+  if (!FillCodeSigningScript())
+    return;
+
+  if (!FillCodeSigningSources())
+    return;
+
+  if (!FillCodeSigningOutputs())
+    return;
+
+  if (!FillCodeSigningArgs())
+    return;
 }
 
-bool CreateBundleTargetGenerator::GetBundleDir(
+bool CreateBundleTargetGenerator::FillBundleDir(
     const SourceDir& bundle_root_dir,
     const base::StringPiece& name,
     SourceDir* bundle_dir) {
@@ -77,3 +82,114 @@
   bundle_dir->SwapValue(&str);
   return true;
 }
+
+bool CreateBundleTargetGenerator::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 CreateBundleTargetGenerator::FillCodeSigningScript() {
+  const Value* value = scope_->GetValue(variables::kCodeSigningScript, true);
+  if (!value)
+    return true;
+
+  if (!value->VerifyTypeIs(Value::STRING, err_))
+    return false;
+
+  SourceFile script_file = scope_->GetSourceDir().ResolveRelativeFile(
+      *value, err_, scope_->settings()->build_settings()->root_path_utf8());
+  if (err_->has_error())
+    return false;
+
+  target_->bundle_data().set_code_signing_script(script_file);
+  return true;
+}
+
+bool CreateBundleTargetGenerator::FillCodeSigningSources() {
+  const Value* value = scope_->GetValue(variables::kCodeSigningSources, true);
+  if (!value)
+    return true;
+
+  if (target_->bundle_data().code_signing_script().is_null()) {
+    *err_ = Err(
+        function_call_,
+        "No code signing script."
+        "You must define code_signing_script if you use code_signing_sources.");
+    return false;
+  }
+
+  Target::FileList script_sources;
+  if (!ExtractListOfRelativeFiles(scope_->settings()->build_settings(), *value,
+                                  scope_->GetSourceDir(), &script_sources,
+                                  err_))
+    return false;
+
+  target_->bundle_data().code_signing_sources().swap(script_sources);
+  return true;
+}
+
+bool CreateBundleTargetGenerator::FillCodeSigningOutputs() {
+  const Value* value = scope_->GetValue(variables::kCodeSigningOutputs, true);
+  if (!value)
+    return true;
+
+  if (target_->bundle_data().code_signing_script().is_null()) {
+    *err_ = Err(
+        function_call_,
+        "No code signing script."
+        "You must define code_signing_script if you use code_signing_outputs.");
+    return false;
+  }
+
+  if (!value->VerifyTypeIs(Value::LIST, err_))
+    return false;
+
+  SubstitutionList& outputs = target_->bundle_data().code_signing_outputs();
+  if (!outputs.Parse(*value, err_))
+    return false;
+
+  if (outputs.list().empty()) {
+    *err_ =
+        Err(function_call_,
+            "Code signing script has no output."
+            "If you have no outputs, the build system can not tell when your\n"
+            "code signing script needs to be run.");
+    return false;
+  }
+
+  // Validate that outputs are in the output dir.
+  CHECK_EQ(value->list_value().size(), outputs.list().size());
+  for (size_t i = 0; i < value->list_value().size(); ++i) {
+    if (!EnsureSubstitutionIsInOutputDir(outputs.list()[i],
+                                         value->list_value()[i]))
+      return false;
+  }
+
+  return true;
+}
+
+bool CreateBundleTargetGenerator::FillCodeSigningArgs() {
+  const Value* value = scope_->GetValue(variables::kCodeSigningArgs, true);
+  if (!value)
+    return true;
+
+  if (target_->bundle_data().code_signing_script().is_null()) {
+    *err_ = Err(
+        function_call_,
+        "No code signing script."
+        "You must define code_signing_script if you use code_signing_args.");
+    return false;
+  }
+
+  if (!value->VerifyTypeIs(Value::LIST, err_))
+    return false;
+
+  return target_->bundle_data().code_signing_args().Parse(*value, err_);
+}
diff --git a/tools/gn/create_bundle_target_generator.h b/tools/gn/create_bundle_target_generator.h
index 50040b8..9527a1a 100644
--- a/tools/gn/create_bundle_target_generator.h
+++ b/tools/gn/create_bundle_target_generator.h
@@ -23,9 +23,16 @@
   void DoRun() override;
 
  private:
-  bool GetBundleDir(const SourceDir& bundle_root_dir,
-                    const base::StringPiece& name,
-                    SourceDir* bundle_dir);
+  bool FillBundleDir(const SourceDir& bundle_root_dir,
+                     const base::StringPiece& name,
+                     SourceDir* bundle_dir);
+
+  bool FillProductType();
+
+  bool FillCodeSigningScript();
+  bool FillCodeSigningSources();
+  bool FillCodeSigningOutputs();
+  bool FillCodeSigningArgs();
 
   DISALLOW_COPY_AND_ASSIGN(CreateBundleTargetGenerator);
 };
diff --git a/tools/gn/docs/reference.md b/tools/gn/docs/reference.md
index 70666b0..760bfe8 100644
--- a/tools/gn/docs/reference.md
+++ b/tools/gn/docs/reference.md
@@ -1436,12 +1436,32 @@
 
 ```
 
+### **Code signing**
+
+```
+  Some bundle needs to be code signed as part of the build (on iOS all
+  application needs to be code signed to run on a device). The code
+  signature can be configured via the code_signing_script variable.
+
+  If set, code_signing_script is the path of a script that invoked after
+  all files have been moved into the bundle. The script must not change
+  any file in the bundle, but may add new files.
+
+  If code_signing_script is defined, then code_signing_outputs must also
+  be defined and non-empty to inform when the script needs to be re-run.
+  The code_signing_args will be passed as is to the script (so path have
+  to be rebased) and additional inputs may be listed with the variable
+  code_signing_sources.
+
+```
+
 ### **Variables**
 
 ```
   bundle_root_dir*, bundle_resources_dir*, bundle_executable_dir*,
   bundle_plugins_dir*, deps, data_deps, public_deps, visibility,
-  product_type
+  product_type, code_signing_args, code_signing_script,
+  code_signing_sources, code_signing_outputs
   * = required
 
 ```
@@ -1477,25 +1497,26 @@
 
       executable("${app_name}_generate_executable") {
         forward_variables_from(invoker, "*", [
-                                                "output_name",
-                                                "visibility",
-                                               ])
+                                               "output_name",
+                                               "visibility",
+                                             ])
         output_name =
             rebase_path("$gen_path/$app_name", root_build_dir)
       }
 
-      bundle_data("${app_name}_bundle_executable") {
-        deps = [ ":${app_name}_generate_executable" ]
-        sources = [ "$gen_path/$app_name" ]
-        outputs = [ "{{bundle_executable_dir}}/$app_name" ]
+      code_signing =
+          defined(invoker.code_signing) && invoker.code_signing
+
+      if (is_ios && !code_signing) {
+        bundle_data("${app_name}_bundle_executable") {
+          deps = [ ":${app_name}_generate_executable" ]
+          sources = [ "$gen_path/$app_name" ]
+          outputs = [ "{{bundle_executable_dir}}/$app_name" ]
+        }
       }
 
       create_bundle("${app_name}.app") {
         product_type = "com.apple.product-type.application"
-        deps = [
-          ":${app_name}_bundle_executable",
-          ":${app_name}_bundle_info_plist",
-        ]
         if (is_ios) {
           bundle_root_dir = "${root_build_dir}/$target_name"
           bundle_resources_dir = bundle_root_dir
@@ -1507,11 +1528,33 @@
           bundle_executable_dir = bundle_root_dir + "/MacOS"
           bundle_plugins_dir = bundle_root_dir + "/Plugins"
         }
-      }
-
-      group(target_name) {
-        forward_variables_from(invoker, ["visibility"])
-        deps = [ ":${app_name}.app" ]
+        deps = [ ":${app_name}_bundle_info_plist" ]
+        if (is_ios && code_signing) {
+          deps += [ ":${app_name}_generate_executable" ]
+          code_signing_script = "//build/config/ios/codesign.py"
+          code_signing_sources = [
+            invoker.entitlements_path,
+            "$target_gen_dir/$app_name",
+          ]
+          code_signing_outputs = [
+            "$bundle_root_dir/$app_name",
+            "$bundle_root_dir/_CodeSignature/CodeResources",
+            "$bundle_root_dir/embedded.mobileprovision",
+            "$target_gen_dir/$app_name.xcent",
+          ]
+          code_signing_args = [
+            "-i=" + ios_code_signing_identity,
+            "-b=" + rebase_path(
+                "$target_gen_dir/$app_name", root_build_dir),
+            "-e=" + rebase_path(
+                invoker.entitlements_path, root_build_dir),
+            "-e=" + rebase_path(
+                "$target_gen_dir/$app_name.xcent", root_build_dir),
+            rebase_path(bundle_root_dir, root_build_dir),
+          ]
+        } else {
+          deps += [ ":${app_name}_bundle_executable" ]
+        }
       }
     }
   }
@@ -4272,6 +4315,48 @@
 
 
 ```
+## **code_signing_args**: [string list] Arguments passed to code signing script.
+
+```
+  For create_bundle targets, code_signing_args is the list of arguments
+  to pass to the code signing script. Typically you would use source
+  expansion (see "gn help source_expansion") to insert the source file
+  names.
+
+  See also "gn help create_bundle".
+
+
+```
+## **code_signing_outputs**: [file list] Output files for code signing step.
+
+```
+  Outputs from the code signing step of a create_bundle target. Must
+  refer to files in the build directory.
+
+  See also "gn help create_bundle".
+
+
+```
+## **code_signing_script**: [file name] Script for code signing.
+```
+  An absolute or buildfile-relative file name of a Python script to run
+  for a create_bundle target to perform code signing step.
+
+  See also "gn help create_bundle".
+
+
+```
+## **code_signing_sources**: [file list] Sources for code signing step.
+
+```
+  A list of files used as input for code signing script step of a
+  create_bundle target. Non-absolute paths will be resolved relative to
+  the current build file.
+
+  See also "gn help create_bundle".
+
+
+```
 ## **complete_static_lib**: [boolean] Links all deps into a static library.
 
 ```
diff --git a/tools/gn/functions_target.cc b/tools/gn/functions_target.cc
index 645c24a..5cfd20b 100644
--- a/tools/gn/functions_target.cc
+++ b/tools/gn/functions_target.cc
@@ -334,11 +334,28 @@
     "  dependencies should be placed in the bundle. A create_bundle can\n"
     "  declare its own explicit data and data_deps, however.\n"
     "\n"
+    "Code signing\n"
+    "\n"
+    "  Some bundle needs to be code signed as part of the build (on iOS all\n"
+    "  application needs to be code signed to run on a device). The code\n"
+    "  signature can be configured via the code_signing_script variable.\n"
+    "\n"
+    "  If set, code_signing_script is the path of a script that invoked after\n"
+    "  all files have been moved into the bundle. The script must not change\n"
+    "  any file in the bundle, but may add new files.\n"
+    "\n"
+    "  If code_signing_script is defined, then code_signing_outputs must also\n"
+    "  be defined and non-empty to inform when the script needs to be re-run.\n"
+    "  The code_signing_args will be passed as is to the script (so path have\n"
+    "  to be rebased) and additional inputs may be listed with the variable\n"
+    "  code_signing_sources.\n"
+    "\n"
     "Variables\n"
     "\n"
     "  bundle_root_dir*, bundle_resources_dir*, bundle_executable_dir*,\n"
     "  bundle_plugins_dir*, deps, data_deps, public_deps, visibility,\n"
-    "  product_type\n"
+    "  product_type, code_signing_args, code_signing_script,\n"
+    "  code_signing_sources, code_signing_outputs\n"
     "  * = required\n"
     "\n"
     "Example\n"
@@ -371,25 +388,26 @@
     "\n"
     "      executable(\"${app_name}_generate_executable\") {\n"
     "        forward_variables_from(invoker, \"*\", [\n"
-    "                                                \"output_name\",\n"
-    "                                                \"visibility\",\n"
-    "                                               ])\n"
+    "                                               \"output_name\",\n"
+    "                                               \"visibility\",\n"
+    "                                             ])\n"
     "        output_name =\n"
     "            rebase_path(\"$gen_path/$app_name\", root_build_dir)\n"
     "      }\n"
     "\n"
-    "      bundle_data(\"${app_name}_bundle_executable\") {\n"
-    "        deps = [ \":${app_name}_generate_executable\" ]\n"
-    "        sources = [ \"$gen_path/$app_name\" ]\n"
-    "        outputs = [ \"{{bundle_executable_dir}}/$app_name\" ]\n"
+    "      code_signing =\n"
+    "          defined(invoker.code_signing) && invoker.code_signing\n"
+    "\n"
+    "      if (is_ios && !code_signing) {\n"
+    "        bundle_data(\"${app_name}_bundle_executable\") {\n"
+    "          deps = [ \":${app_name}_generate_executable\" ]\n"
+    "          sources = [ \"$gen_path/$app_name\" ]\n"
+    "          outputs = [ \"{{bundle_executable_dir}}/$app_name\" ]\n"
+    "        }\n"
     "      }\n"
     "\n"
     "      create_bundle(\"${app_name}.app\") {\n"
     "        product_type = \"com.apple.product-type.application\"\n"
-    "        deps = [\n"
-    "          \":${app_name}_bundle_executable\",\n"
-    "          \":${app_name}_bundle_info_plist\",\n"
-    "        ]\n"
     "        if (is_ios) {\n"
     "          bundle_root_dir = \"${root_build_dir}/$target_name\"\n"
     "          bundle_resources_dir = bundle_root_dir\n"
@@ -401,11 +419,33 @@
     "          bundle_executable_dir = bundle_root_dir + \"/MacOS\"\n"
     "          bundle_plugins_dir = bundle_root_dir + \"/Plugins\"\n"
     "        }\n"
-    "      }\n"
-    "\n"
-    "      group(target_name) {\n"
-    "        forward_variables_from(invoker, [\"visibility\"])\n"
-    "        deps = [ \":${app_name}.app\" ]\n"
+    "        deps = [ \":${app_name}_bundle_info_plist\" ]\n"
+    "        if (is_ios && code_signing) {\n"
+    "          deps += [ \":${app_name}_generate_executable\" ]\n"
+    "          code_signing_script = \"//build/config/ios/codesign.py\"\n"
+    "          code_signing_sources = [\n"
+    "            invoker.entitlements_path,\n"
+    "            \"$target_gen_dir/$app_name\",\n"
+    "          ]\n"
+    "          code_signing_outputs = [\n"
+    "            \"$bundle_root_dir/$app_name\",\n"
+    "            \"$bundle_root_dir/_CodeSignature/CodeResources\",\n"
+    "            \"$bundle_root_dir/embedded.mobileprovision\",\n"
+    "            \"$target_gen_dir/$app_name.xcent\",\n"
+    "          ]\n"
+    "          code_signing_args = [\n"
+    "            \"-i=\" + ios_code_signing_identity,\n"
+    "            \"-b=\" + rebase_path(\n"
+    "                \"$target_gen_dir/$app_name\", root_build_dir),\n"
+    "            \"-e=\" + rebase_path(\n"
+    "                invoker.entitlements_path, root_build_dir),\n"
+    "            \"-e=\" + rebase_path(\n"
+    "                \"$target_gen_dir/$app_name.xcent\", root_build_dir),\n"
+    "            rebase_path(bundle_root_dir, root_build_dir),\n"
+    "          ]\n"
+    "        } else {\n"
+    "          deps += [ \":${app_name}_bundle_executable\" ]\n"
+    "        }\n"
     "      }\n"
     "    }\n"
     "  }\n";
diff --git a/tools/gn/ninja_create_bundle_target_writer.cc b/tools/gn/ninja_create_bundle_target_writer.cc
index 84da2ff..26701e5 100644
--- a/tools/gn/ninja_create_bundle_target_writer.cc
+++ b/tools/gn/ninja_create_bundle_target_writer.cc
@@ -4,6 +4,8 @@
 
 #include "tools/gn/ninja_create_bundle_target_writer.h"
 
+#include "base/macros.h"
+#include "base/strings/string_util.h"
 #include "tools/gn/filesystem_utils.h"
 #include "tools/gn/ninja_utils.h"
 #include "tools/gn/output_file.h"
@@ -24,6 +26,22 @@
           "doesn't define a \"" + tool_name + "\" tool."));
 }
 
+bool EnsureAllToolsAvailable(const Target* target) {
+  const Toolchain::ToolType kRequiredTools[] = {
+      Toolchain::TYPE_COPY_BUNDLE_DATA, Toolchain::TYPE_COMPILE_XCASSETS,
+      Toolchain::TYPE_STAMP,
+  };
+
+  for (size_t i = 0; i < arraysize(kRequiredTools); ++i) {
+    if (!target->toolchain()->GetTool(kRequiredTools[i])) {
+      FailWithMissingToolError(kRequiredTools[i], target);
+      return false;
+    }
+  }
+
+  return true;
+}
+
 }  // namespace
 
 NinjaCreateBundleTargetWriter::NinjaCreateBundleTargetWriter(
@@ -34,83 +52,18 @@
 NinjaCreateBundleTargetWriter::~NinjaCreateBundleTargetWriter() {}
 
 void NinjaCreateBundleTargetWriter::Run() {
-  if (!target_->toolchain()->GetTool(Toolchain::TYPE_COPY_BUNDLE_DATA)) {
-    FailWithMissingToolError(Toolchain::TYPE_COPY_BUNDLE_DATA, target_);
+  if (!EnsureAllToolsAvailable(target_))
     return;
-  }
 
-  if (!target_->toolchain()->GetTool(Toolchain::TYPE_COMPILE_XCASSETS)) {
-    FailWithMissingToolError(Toolchain::TYPE_COMPILE_XCASSETS, target_);
-    return;
-  }
+  std::string code_signing_rule_name = WriteCodeSigningRuleDefinition();
 
-  if (!target_->toolchain()->GetTool(Toolchain::TYPE_STAMP)) {
-    FailWithMissingToolError(Toolchain::TYPE_STAMP, target_);
-    return;
-  }
-
-  std::vector<OutputFile> output_files;
   OutputFile input_dep =
       WriteInputDepsStampAndGetDep(std::vector<const Target*>());
 
-  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);
-
-      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;
-    }
-  }
-
-  if (!target_->bundle_data().asset_catalog_sources().empty()) {
-    OutputFile output_file(
-        settings_->build_settings(),
-        target_->bundle_data().GetCompiledAssetCatalogPath());
-    output_files.push_back(output_file);
-
-    out_ << "build ";
-    path_output_.WriteFile(out_, output_file);
-    out_ << ": "
-         << GetNinjaRulePrefixForToolchain(settings_)
-         << 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;
-      out_ << " ";
-      path_output_.WriteFile(out_, asset_catalog_bundle);
-      asset_catalog_bundles.insert(asset_catalog_bundle);
-    }
-
-    out_ << " |";
-    for (const auto& source : target_->bundle_data().asset_catalog_sources()) {
-      out_ << " ";
-      path_output_.WriteFile(out_, source);
-    }
-    if (!input_dep.value().empty()) {
-      out_ << " || ";
-      path_output_.WriteFile(out_, input_dep);
-    }
-    out_ << std::endl;
-  }
-
-  out_ << std::endl;
+  std::vector<OutputFile> output_files;
+  WriteCopyBundleDataRules(input_dep, &output_files);
+  WriteCompileAssetsCatalogRule(input_dep, &output_files);
+  WriteCodeSigningRules(code_signing_rule_name, input_dep, &output_files);
 
   std::vector<OutputFile> order_only_deps;
   for (const auto& pair : target_->data_deps())
@@ -128,3 +81,172 @@
   out_ << ": phony " << target_->dependency_output_file().value();
   out_ << std::endl;
 }
+
+std::string NinjaCreateBundleTargetWriter::WriteCodeSigningRuleDefinition() {
+  if (target_->bundle_data().code_signing_script().is_null())
+    return std::string();
+
+  std::string target_label = target_->label().GetUserVisibleName(true);
+  std::string custom_rule_name(target_label);
+  base::ReplaceChars(custom_rule_name, ":/()", "_", &custom_rule_name);
+  custom_rule_name.append("_code_signing_rule");
+
+  out_ << "rule " << custom_rule_name << std::endl;
+  out_ << "  command = ";
+  path_output_.WriteFile(out_, settings_->build_settings()->python_path());
+  out_ << " ";
+  path_output_.WriteFile(out_, target_->bundle_data().code_signing_script());
+
+  const SubstitutionList& args = target_->bundle_data().code_signing_args();
+  EscapeOptions args_escape_options;
+  args_escape_options.mode = ESCAPE_NINJA_COMMAND;
+
+  for (const auto& arg : args.list()) {
+    out_ << " ";
+    SubstitutionWriter::WriteWithNinjaVariables(arg, args_escape_options, out_);
+  }
+  out_ << std::endl;
+  out_ << "  description = CODE SIGNING " << target_label << std::endl;
+  out_ << "  restat = 1" << std::endl;
+  out_ << std::endl;
+
+  return custom_rule_name;
+}
+
+void NinjaCreateBundleTargetWriter::WriteCopyBundleDataRules(
+    const OutputFile& input_dep,
+    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);
+
+      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::WriteCompileAssetsCatalogRule(
+    const OutputFile& input_dep,
+    std::vector<OutputFile>* output_files) {
+  if (target_->bundle_data().asset_catalog_sources().empty())
+    return;
+
+  OutputFile output_file(settings_->build_settings(),
+                         target_->bundle_data().GetCompiledAssetCatalogPath());
+  output_files->push_back(output_file);
+
+  out_ << "build ";
+  path_output_.WriteFile(out_, output_file);
+  out_ << ": " << GetNinjaRulePrefixForToolchain(settings_)
+       << 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;
+    out_ << " ";
+    path_output_.WriteFile(out_, asset_catalog_bundle);
+    asset_catalog_bundles.insert(asset_catalog_bundle);
+  }
+
+  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_ << std::endl;
+}
+
+void NinjaCreateBundleTargetWriter::WriteCodeSigningRules(
+    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);
+
+  out_ << "build";
+  std::vector<OutputFile> code_signing_output_files;
+  SubstitutionWriter::GetListAsOutputFiles(
+      settings_, target_->bundle_data().code_signing_outputs(),
+      &code_signing_output_files);
+  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.
+  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_ << 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(
+      target_->bundle_data().code_signing_script());
+  code_signing_input_files.insert(
+      code_signing_input_files.end(),
+      target_->bundle_data().code_signing_sources().begin(),
+      target_->bundle_data().code_signing_sources().end());
+  for (const OutputFile& output_file : *output_files) {
+    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()));
+  }
+
+  DCHECK(!code_signing_input_files.empty());
+  if (code_signing_input_files.size() == 1)
+    return OutputFile(settings_->build_settings(), code_signing_input_files[0]);
+
+  OutputFile code_signing_input_stamp_file =
+      OutputFile(RebasePath(GetTargetOutputDir(target_).value(),
+                            settings_->build_settings()->build_dir(),
+                            settings_->build_settings()->root_path_utf8()));
+  code_signing_input_stamp_file.value().append(target_->label().name());
+  code_signing_input_stamp_file.value().append(".codesigning.inputdeps.stamp");
+
+  out_ << "build ";
+  path_output_.WriteFile(out_, code_signing_input_stamp_file);
+  out_ << ": " << GetNinjaRulePrefixForToolchain(settings_)
+       << Toolchain::ToolTypeToName(Toolchain::TYPE_STAMP);
+
+  for (const SourceFile& source : code_signing_input_files) {
+    out_ << " ";
+    path_output_.WriteFile(out_, source);
+  }
+  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 42b900f..7c4239c 100644
--- a/tools/gn/ninja_create_bundle_target_writer.h
+++ b/tools/gn/ninja_create_bundle_target_writer.h
@@ -17,6 +17,41 @@
   void Run() override;
 
  private:
+  // Writes the Ninja rule for invoking the code signing script.
+  //
+  // Returns the name of the custom rule generated for the code signing step if
+  // defined, otherwise returns an empty string.
+  std::string WriteCodeSigningRuleDefinition();
+
+  // Writes the rule 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);
+
+  // Writes the rule 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.
+  void WriteCompileAssetsCatalogRule(const OutputFile& input_dep,
+                                     std::vector<OutputFile>* output_files);
+
+  // Writes the code signing rule (if a script is defined).
+  //
+  // 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);
+
+  // 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 d35b519..1c6f8e0 100644
--- a/tools/gn/ninja_create_bundle_target_writer_unittest.cc
+++ b/tools/gn/ninja_create_bundle_target_writer_unittest.cc
@@ -53,7 +53,6 @@
           "../../foo/input1.txt\n"
       "build bar.bundle/Resources/input2.txt: copy_bundle_data "
           "../../foo/input2.txt\n"
-      "\n"
       "build obj/baz/bar.stamp: stamp "
           "bar.bundle/Resources/input1.txt "
           "bar.bundle/Resources/input2.txt\n"
@@ -98,7 +97,6 @@
           "../../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"
-      "\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();
@@ -141,7 +139,6 @@
           "../../foo/input1.txt\n"
       "build bar.bundle/Contents/Resources/input2.txt: copy_bundle_data "
           "../../foo/input2.txt\n"
-      "\n"
       "build obj/baz/bar.stamp: stamp "
           "bar.bundle/Contents/Resources/input1.txt "
           "bar.bundle/Contents/Resources/input2.txt\n"
@@ -152,7 +149,7 @@
 
 // Tests complex target with multiple bundle_data sources, including
 // some asset catalog.
-TEST(NinjaCreateBundleTargetWriter, OrderOnlyDeps) {
+TEST(NinjaCreateBundleTargetWriter, ImplicitDeps) {
   TestWithScope setup;
   Err err;
 
@@ -203,14 +200,14 @@
           "../../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/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"
-      "\n"
       "build obj/baz/bar.stamp: stamp "
           "bar.bundle/Resources/input1.txt "
           "bar.bundle/Resources/input2.txt "
@@ -220,3 +217,82 @@
   std::string out_str = out.str();
   EXPECT_EQ(expected, out_str);
 }
+
+// Tests multiple files with an output pattern.
+TEST(NinjaCreateBundleTargetWriter, CodeSigning) {
+  TestWithScope setup;
+  Err err;
+
+  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));
+
+  std::ostringstream out;
+  NinjaCreateBundleTargetWriter writer(&target, 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"
+      "  description = CODE SIGNING //baz:bar(//toolchain:default)\n"
+      "  restat = 1\n"
+      "\n"
+      "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.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: "
+          "__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/_CodeSignature/CodeResources\n"
+      "build bar.bundle: phony obj/baz/bar.stamp\n";
+  std::string out_str = out.str();
+  EXPECT_EQ(expected, out_str);
+}
diff --git a/tools/gn/variables.cc b/tools/gn/variables.cc
index 210eb5d..3c717d8 100644
--- a/tools/gn/variables.cc
+++ b/tools/gn/variables.cc
@@ -648,6 +648,54 @@
     "    ...\n"
     "  }\n";
 
+const char kCodeSigningArgs[] = "code_signing_args";
+const char kCodeSigningArgs_HelpShort[] =
+    "code_signing_args: [string list] Arguments passed to code signing script.";
+const char kCodeSigningArgs_Help[] =
+    "code_signing_args: [string list] Arguments passed to code signing "
+        "script.\n"
+    "\n"
+    "  For create_bundle targets, code_signing_args is the list of arguments\n"
+    "  to pass to the code signing script. Typically you would use source\n"
+    "  expansion (see \"gn help source_expansion\") to insert the source file\n"
+    "  names.\n"
+    "\n"
+    "  See also \"gn help create_bundle\".\n";
+
+const char kCodeSigningScript[] = "code_signing_script";
+const char kCodeSigningScript_HelpShort[] =
+    "code_signing_script: [file name] Script for code signing.";
+const char kCodeSigningScript_Help[] =
+    "code_signing_script: [file name] Script for code signing."
+    "\n"
+    "  An absolute or buildfile-relative file name of a Python script to run\n"
+    "  for a create_bundle target to perform code signing step.\n"
+    "\n"
+    "  See also \"gn help create_bundle\".\n";
+
+const char kCodeSigningSources[] = "code_signing_sources";
+const char kCodeSigningSources_HelpShort[] =
+    "code_signing_sources: [file list] Sources for code signing step.";
+const char kCodeSigningSources_Help[] =
+    "code_signing_sources: [file list] Sources for code signing step.\n"
+    "\n"
+    "  A list of files used as input for code signing script step of a\n"
+    "  create_bundle target. Non-absolute paths will be resolved relative to\n"
+    "  the current build file.\n"
+    "\n"
+    "  See also \"gn help create_bundle\".\n";
+
+const char kCodeSigningOutputs[] = "code_signing_outputs";
+const char kCodeSigningOutputs_HelpShort[] =
+    "code_signing_outputs: [file list] Output files for code signing step.";
+const char kCodeSigningOutputs_Help[] =
+    "code_signing_outputs: [file list] Output files for code signing step.\n"
+    "\n"
+    "  Outputs from the code signing step of a create_bundle target. Must\n"
+    "  refer to files in the build directory.\n"
+    "\n"
+    "  See also \"gn help create_bundle\".\n";
+
 const char kCompleteStaticLib[] = "complete_static_lib";
 const char kCompleteStaticLib_HelpShort[] =
     "complete_static_lib: [boolean] Links all deps into a static library.";
@@ -1698,6 +1746,10 @@
     INSERT_VARIABLE(CflagsObjC)
     INSERT_VARIABLE(CflagsObjCC)
     INSERT_VARIABLE(CheckIncludes)
+    INSERT_VARIABLE(CodeSigningArgs)
+    INSERT_VARIABLE(CodeSigningScript)
+    INSERT_VARIABLE(CodeSigningSources)
+    INSERT_VARIABLE(CodeSigningOutputs)
     INSERT_VARIABLE(CompleteStaticLib)
     INSERT_VARIABLE(Configs)
     INSERT_VARIABLE(Console)
diff --git a/tools/gn/variables.h b/tools/gn/variables.h
index 0aaa8e0..8ed165f 100644
--- a/tools/gn/variables.h
+++ b/tools/gn/variables.h
@@ -135,6 +135,22 @@
 extern const char kCheckIncludes_HelpShort[];
 extern const char kCheckIncludes_Help[];
 
+extern const char kCodeSigningArgs[];
+extern const char kCodeSigningArgs_HelpShort[];
+extern const char kCodeSigningArgs_Help[];
+
+extern const char kCodeSigningScript[];
+extern const char kCodeSigningScript_HelpShort[];
+extern const char kCodeSigningScript_Help[];
+
+extern const char kCodeSigningSources[];
+extern const char kCodeSigningSources_HelpShort[];
+extern const char kCodeSigningSources_Help[];
+
+extern const char kCodeSigningOutputs[];
+extern const char kCodeSigningOutputs_HelpShort[];
+extern const char kCodeSigningOutputs_Help[];
+
 extern const char kCompleteStaticLib[];
 extern const char kCompleteStaticLib_HelpShort[];
 extern const char kCompleteStaticLib_Help[];