Add create_bundle support for GN

Docs-Preview: https://skia.org/?cl=75383
Bug: skia:7339
Change-Id: I985734e8b7b5af21a82cb8ee59acbfb5ff1d3ff7
Reviewed-on: https://skia-review.googlesource.com/75383
Commit-Queue: Jim Van Verth <jvanverth@google.com>
Reviewed-by: Mike Klein <mtklein@chromium.org>
diff --git a/BUILD.gn b/BUILD.gn
index 439f0a8..f37f4c6 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -959,36 +959,116 @@
   }
 
   template("test_app") {
-    if (defined(invoker.is_shared_library) && invoker.is_shared_library) {
-      shared_library("lib" + target_name) {
-        forward_variables_from(invoker, "*", [ "is_shared_library" ])
+    if (is_ios) {
+      app_name = target_name
+      gen_path = target_gen_dir
+
+      action("${app_name}_generate_info_plist") {
+        script = "//gn/gen_plist_ios.py"
+        outputs = [
+          "$gen_path/${app_name}_Info.plist",
+        ]
+        args = [ rebase_path("$gen_path/$app_name", root_build_dir) ]
+      }
+
+      bundle_data("${app_name}_bundle_info_plist") {
+        public_deps = [
+          ":${app_name}_generate_info_plist",
+        ]
+        sources = [
+          "$gen_path/${app_name}_Info.plist",
+        ]
+        outputs = [
+          "{{bundle_root_dir}}/Info.plist",
+        ]
+      }
+
+      executable("${app_name}_generate_executable") {
+        forward_variables_from(invoker,
+                               "*",
+                               [
+                                 "output_name",
+                                 "visibility",
+                                 "is_shared_library",
+                               ])
         configs += [ ":skia_private" ]
         testonly = true
+        output_name = rebase_path("$gen_path/$app_name", root_build_dir)
+      }
+
+      bundle_data("${app_name}_bundle_executable") {
+        public_deps = [
+          ":${app_name}_generate_executable",
+        ]
+        sources = [
+          "$gen_path/$app_name",
+        ]
+        outputs = [
+          "{{bundle_executable_dir}}/$app_name",
+        ]
+        testonly = true
+      }
+
+      create_bundle("$app_name") {
+        product_type = "com.apple.product-type.application"
+        testonly = true
+
+        bundle_root_dir = "${root_build_dir}/${target_name}.app"
+        bundle_resources_dir = bundle_root_dir
+        bundle_executable_dir = bundle_root_dir
+        bundle_plugins_dir = bundle_root_dir + "/Plugins"
+
+        deps = [
+          ":${app_name}_bundle_executable",
+          ":${app_name}_bundle_info_plist",
+        ]
+
+        # should only code sign when running on a device, not the simulator
+        if (target_cpu != "x64") {
+          code_signing_script = "//gn/codesign_ios.py"
+          code_signing_sources = [ "$target_gen_dir/$app_name" ]
+          code_signing_outputs = [
+            "$bundle_root_dir/_CodeSignature/CodeResources",
+            "$bundle_root_dir/embedded.mobileprovision",
+          ]
+          code_signing_args =
+              [ rebase_path("$bundle_root_dir", root_build_dir) ]
+        }
       }
     } else {
-      _executable = target_name
-      executable(_executable) {
-        forward_variables_from(invoker, "*", [ "is_shared_library" ])
-        configs += [ ":skia_private" ]
-        testonly = true
+      # !is_ios
+
+      if (defined(invoker.is_shared_library) && invoker.is_shared_library) {
+        shared_library("lib" + target_name) {
+          forward_variables_from(invoker, "*", [ "is_shared_library" ])
+          configs += [ ":skia_private" ]
+          testonly = true
+        }
+      } else {
+        _executable = target_name
+        executable(_executable) {
+          forward_variables_from(invoker, "*", [ "is_shared_library" ])
+          configs += [ ":skia_private" ]
+          testonly = true
+        }
       }
-    }
-    if (is_android && skia_android_serial != "" && defined(_executable)) {
-      action("push_" + target_name) {
-        script = "gn/push_to_android.py"
-        deps = [
-          ":" + _executable,
-        ]
-        _stamp = "$target_gen_dir/$_executable.pushed_$skia_android_serial"
-        outputs = [
-          _stamp,
-        ]
-        args = [
-          rebase_path("$root_build_dir/$_executable"),
-          skia_android_serial,
-          rebase_path(_stamp),
-        ]
-        testonly = true
+      if (is_android && skia_android_serial != "" && defined(_executable)) {
+        action("push_" + target_name) {
+          script = "gn/push_to_android.py"
+          deps = [
+            ":" + _executable,
+          ]
+          _stamp = "$target_gen_dir/$_executable.pushed_$skia_android_serial"
+          outputs = [
+            _stamp,
+          ]
+          args = [
+            rebase_path("$root_build_dir/$_executable"),
+            skia_android_serial,
+            rebase_path(_stamp),
+          ]
+          testonly = true
+        }
       }
     }
   }
diff --git a/gn/package_ios.py b/gn/codesign_ios.py
similarity index 71%
rename from gn/package_ios.py
rename to gn/codesign_ios.py
index eb07fc7..66a97d3 100644
--- a/gn/package_ios.py
+++ b/gn/codesign_ios.py
@@ -14,8 +14,9 @@
 import tempfile
 
 # Arguments to the script:
-#  app              path to binary to package, e.g. out/Debug/dm
-app, = sys.argv[1:]
+#  pkg              path to application directory, e.g. out/Debug/dm.app
+#                   executable and plist should already be in this directory
+pkg, = sys.argv[1:]
 
 # Find the Google signing identity.
 identity = None
@@ -34,35 +35,18 @@
     mobileprovision = p
 assert mobileprovision
 
-out, app = os.path.split(app)
-
-pkg = os.path.join(out, app + '.app')
-if not os.path.exists(pkg):
-  os.mkdir(pkg)
-
-# The binary and .mobileprovision just get copied into the package.
-shutil.copy(os.path.join(out, app), pkg)
+# The .mobileprovision just gets copied into the package.
 shutil.copy(mobileprovision,
             os.path.join(pkg, 'embedded.mobileprovision'))
 
-# Write a minimal Info.plist to name the package and point at the binary.
-with open(os.path.join(pkg, 'Info.plist'), 'w') as f:
-  f.write('''
-<plist version="1.0">
-  <dict>
-    <key>CFBundleExecutable</key> <string>{app}</string>
-    <key>CFBundleIdentifier</key> <string>com.google.{app}</string>
-    <key>CFBundlePackageType</key> <string>APPL</string>
-  </dict>
-</plist>
-'''.format(app=app))
-
 # Extract the appliciation identitifer prefix from the .mobileprovision.
 m = re.search(r'''<key>ApplicationIdentifierPrefix</key>
 \t<array>
 \t<string>(.*)</string>''', open(mobileprovision).read(), re.MULTILINE)
 prefix = m.group(1)
 
+app, _ = os.path.splitext(os.path.basename(pkg))
+
 # Write a minimal entitlements file, then codesign.
 with tempfile.NamedTemporaryFile() as f:
   f.write('''
diff --git a/gn/gen_plist_ios.py b/gn/gen_plist_ios.py
new file mode 100644
index 0000000..e4041be
--- /dev/null
+++ b/gn/gen_plist_ios.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python2.7
+#
+# Copyright 2017 Google Inc.
+#
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import os
+import sys
+
+# Arguments to the script:
+#  app              path to binary to package, e.g. out/Debug/gen/dm
+app, = sys.argv[1:]
+
+out, app = os.path.split(app)
+
+# Write a minimal Info.plist to name the package and point at the binary.
+with open(os.path.join(out, app + '_Info.plist'), 'w') as f:
+  f.write('''
+<plist version="1.0">
+  <dict>
+    <key>CFBundleVersion</key> <string>0.1.0</string>
+    <key>CFBundleShortVersionString</key> <string>0.1.0</string>
+    <key>CFBundleExecutable</key> <string>{app}</string>
+    <key>CFBundleIdentifier</key> <string>com.google.{app}</string>
+    <key>CFBundlePackageType</key> <string>APPL</string>
+  </dict>
+</plist>
+'''.format(app=app))
diff --git a/gn/toolchain/BUILD.gn b/gn/toolchain/BUILD.gn
index 13c8397..59d83e4 100644
--- a/gn/toolchain/BUILD.gn
+++ b/gn/toolchain/BUILD.gn
@@ -283,6 +283,18 @@
       description = "copy {{source}} {{output}}"
     }
 
+    tool("copy_bundle_data") {
+      cp_py = rebase_path("../cp.py")
+      command = "$python $cp_py {{source}} {{output}}"
+      description = "copy_bundle_data {{source}} {{output}}"
+    }
+
+    # We don't currently have any xcasset files so make this a NOP
+    tool("compile_xcassets") {
+      command = "true"
+      description = "compile_xcassets {{output}}"
+    }
+
     toolchain_args = {
       current_cpu = invoker.cpu
       current_os = invoker.os
diff --git a/infra/bots/ios_bin.isolate b/infra/bots/ios_bin.isolate
index 1994eb6..1f81c64 100644
--- a/infra/bots/ios_bin.isolate
+++ b/infra/bots/ios_bin.isolate
@@ -4,7 +4,6 @@
       'variables': {
         'files': [
           '../../platform_tools/ios/bin/',
-          '../../gn/package_ios.py',
         ],
       },
     }],
@@ -12,7 +11,6 @@
       'variables': {
         'files': [
           '../../platform_tools/ios/bin/',
-          '../../gn/package_ios.py',
         ],
       },
     }],
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/Test-iOS-Clang-iPadPro-GPU-GT7800-arm64-Debug-All.json b/infra/bots/recipe_modules/flavor/examples/full.expected/Test-iOS-Clang-iPadPro-GPU-GT7800-arm64-Debug-All.json
index d5cb977..f30c8c2 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/Test-iOS-Clang-iPadPro-GPU-GT7800-arm64-Debug-All.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/Test-iOS-Clang-iPadPro-GPU-GT7800-arm64-Debug-All.json
@@ -50,38 +50,6 @@
   },
   {
     "cmd": [
-      "python",
-      "-u",
-      "[START_DIR]/skia/gn/package_ios.py",
-      "[START_DIR]/out/Debug/dm"
-    ],
-    "env": {
-      "BUILDTYPE": "Debug",
-      "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]",
-      "SKIA_OUT": "[START_DIR]/out"
-    },
-    "infra_step": true,
-    "name": "package dm"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "[START_DIR]/skia/gn/package_ios.py",
-      "[START_DIR]/out/Debug/nanobench"
-    ],
-    "env": {
-      "BUILDTYPE": "Debug",
-      "CHROME_HEADLESS": "1",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]",
-      "SKIA_OUT": "[START_DIR]/out"
-    },
-    "infra_step": true,
-    "name": "package nanobench"
-  },
-  {
-    "cmd": [
       "[START_DIR]/skia/platform_tools/ios/bin/ios_push_file",
       "file.txt",
       "file.txt"
diff --git a/infra/bots/recipe_modules/flavor/ios_flavor.py b/infra/bots/recipe_modules/flavor/ios_flavor.py
index cc6143a..4cb91a5 100644
--- a/infra/bots/recipe_modules/flavor/ios_flavor.py
+++ b/infra/bots/recipe_modules/flavor/ios_flavor.py
@@ -33,17 +33,6 @@
         svg_dir='svgs',
         tmp_dir='tmp')
 
-  def compile(self, unused_target, **kwargs):
-    """ Build Skia with GN and sign the iOS apps"""
-    # Use the generic compile sets.
-    super(iOSFlavorUtils, self).compile(unused_target, **kwargs)
-
-    # Sign the apps.
-    for app in ['dm', 'nanobench']:
-      self._py('package ' + app,
-              self.m.vars.skia_dir.join('gn', 'package_ios.py'),
-              args=[self.out_dir.join(app)], infra_step=True)
-
   def step(self, name, cmd, env=None, **kwargs):
     bundle_id = 'com.google.%s' % cmd[0]
     self.m.run(self.m.step, name,
diff --git a/infra/bots/recipes/compile.expected/Build-Mac-Clang-arm64-Debug-iOS.json b/infra/bots/recipes/compile.expected/Build-Mac-Clang-arm64-Debug-iOS.json
index 26ca4ac..2c54814 100644
--- a/infra/bots/recipes/compile.expected/Build-Mac-Clang-arm64-Debug-iOS.json
+++ b/infra/bots/recipes/compile.expected/Build-Mac-Clang-arm64-Debug-iOS.json
@@ -150,42 +150,6 @@
     "cmd": [
       "python",
       "-u",
-      "[CUSTOM_/_B_WORK]/skia/gn/package_ios.py",
-      "[CUSTOM_/_B_WORK]/skia/out/Build-Mac-Clang-arm64-Debug-iOS/Debug/dm"
-    ],
-    "env": {
-      "BUILDTYPE": "Debug",
-      "CC": "/usr/bin/clang",
-      "CHROME_HEADLESS": "1",
-      "CXX": "/usr/bin/clang++",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]",
-      "SKIA_OUT": "[CUSTOM_/_B_WORK]/skia/out/Build-Mac-Clang-arm64-Debug-iOS"
-    },
-    "infra_step": true,
-    "name": "package dm"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "[CUSTOM_/_B_WORK]/skia/gn/package_ios.py",
-      "[CUSTOM_/_B_WORK]/skia/out/Build-Mac-Clang-arm64-Debug-iOS/Debug/nanobench"
-    ],
-    "env": {
-      "BUILDTYPE": "Debug",
-      "CC": "/usr/bin/clang",
-      "CHROME_HEADLESS": "1",
-      "CXX": "/usr/bin/clang++",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]",
-      "SKIA_OUT": "[CUSTOM_/_B_WORK]/skia/out/Build-Mac-Clang-arm64-Debug-iOS"
-    },
-    "infra_step": true,
-    "name": "package nanobench"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
       "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'nanobench', 'nanobench.exe', 'skpbench', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'lib/*.so', 'vulkan-1.dll']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[CUSTOM_/_B_WORK]/skia/out/Build-Mac-Clang-arm64-Debug-iOS/Debug",
       "[CUSTOM_[SWARM_OUT_DIR]]/out/Debug"
diff --git a/infra/bots/recipes/compile.expected/Build-Mac-Clang-x64-Release-iOS.json b/infra/bots/recipes/compile.expected/Build-Mac-Clang-x64-Release-iOS.json
index e3adce1..29daf28 100644
--- a/infra/bots/recipes/compile.expected/Build-Mac-Clang-x64-Release-iOS.json
+++ b/infra/bots/recipes/compile.expected/Build-Mac-Clang-x64-Release-iOS.json
@@ -150,42 +150,6 @@
     "cmd": [
       "python",
       "-u",
-      "[CUSTOM_/_B_WORK]/skia/gn/package_ios.py",
-      "[CUSTOM_/_B_WORK]/skia/out/Build-Mac-Clang-x64-Release-iOS/Release/dm"
-    ],
-    "env": {
-      "BUILDTYPE": "Release",
-      "CC": "/usr/bin/clang",
-      "CHROME_HEADLESS": "1",
-      "CXX": "/usr/bin/clang++",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]",
-      "SKIA_OUT": "[CUSTOM_/_B_WORK]/skia/out/Build-Mac-Clang-x64-Release-iOS"
-    },
-    "infra_step": true,
-    "name": "package dm"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
-      "[CUSTOM_/_B_WORK]/skia/gn/package_ios.py",
-      "[CUSTOM_/_B_WORK]/skia/out/Build-Mac-Clang-x64-Release-iOS/Release/nanobench"
-    ],
-    "env": {
-      "BUILDTYPE": "Release",
-      "CC": "/usr/bin/clang",
-      "CHROME_HEADLESS": "1",
-      "CXX": "/usr/bin/clang++",
-      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]",
-      "SKIA_OUT": "[CUSTOM_/_B_WORK]/skia/out/Build-Mac-Clang-x64-Release-iOS"
-    },
-    "infra_step": true,
-    "name": "package nanobench"
-  },
-  {
-    "cmd": [
-      "python",
-      "-u",
       "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'nanobench', 'nanobench.exe', 'skpbench', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'lib/*.so', 'vulkan-1.dll']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
       "[CUSTOM_/_B_WORK]/skia/out/Build-Mac-Clang-x64-Release-iOS/Release",
       "[CUSTOM_[SWARM_OUT_DIR]]/out/Release"
diff --git a/site/user/build.md b/site/user/build.md
index 2715c28..a9114c5 100644
--- a/site/user/build.md
+++ b/site/user/build.md
@@ -207,17 +207,15 @@
     bin/gn gen out/ios32  --args='target_os="ios" target_cpu="arm"'
     bin/gn gen out/iossim --args='target_os="ios" target_cpu="x64"'
 
-Googlers who want to sign and run iOS test binaries can do so by running something like
+This will also package (and for devices, sign) iOS test binaries. For the moment a
+Google provisioning profile is needed to sign.
 
-    python gn/package_ios.py out/Debug/dm
-    python gn/package_ios.py out/Release/nanobench
-
-These commands will create and sign `dm.app` or `nanobench.app` packages you
-can push to iOS devices registered for Google development.  `ios-deploy` makes
-installing and running these packages easy:
+For signed packages `ios-deploy` makes installing and running them on a device easy:
 
     ios-deploy -b out/Debug/dm.app -d --args "--match foo"
 
+Alternatively you can generate an Xcode project by passing `--ide=xcode` to `bin/gn gen`.
+
 If you find yourself missing a Google signing identity or provisioning profile,
 you'll want to have a read through go/appledev.