Upload/Download dart_snapshotter

BUG=
R=jamesr@chromium.org

Review URL: https://codereview.chromium.org/1300463002 .
diff --git a/build/module_args/mojo.gni b/build/module_args/mojo.gni
index d7cf153..e3c88d2 100644
--- a/build/module_args/mojo.gni
+++ b/build/module_args/mojo.gni
@@ -11,6 +11,10 @@
 # Linux and Android).
 mojo_build_mojo_shell_from_source = true
 
+# To build the dart_snapshotter from source, set this variable to true. To use
+# the prebuilt dart_snapshotter, omit this variable or set it to false.
+mojo_build_dart_snapshotter_from_source = true
+
 # To build the network service from source, set this variable to true. To use
 # the prebuilt network service, omit this variable or set it to false.
 mojo_build_network_service_from_source = false
diff --git a/mojo/public/dart/rules.gni b/mojo/public/dart/rules.gni
index 7ef8c57..103b989 100644
--- a/mojo/public/dart/rules.gni
+++ b/mojo/public/dart/rules.gni
@@ -9,6 +9,7 @@
 # - dartzip_package
 # - dartzip_packaged_application
 
+import("../mojo.gni")
 import("//build/module_args/mojo.gni")
 import("//build/module_args/dart.gni")
 
@@ -16,11 +17,17 @@
   bundle_prefix = target_name
   bundle = "$target_gen_dir/${bundle_prefix}.dartx"
   snapshot = "$target_gen_dir/${bundle_prefix}_snapshot.bin"
-  dart_snapshotter = "$root_out_dir/dart_snapshotter"
-  if (current_toolchain != host_toolchain) {
-    toolchain_name = get_label_info(host_toolchain, "name")
-    dart_snapshotter = "$root_out_dir/$toolchain_name/dart_snapshotter"
+
+  if (mojo_use_prebuilt_dart_snapshotter) {
+    dart_snapshotter_path =
+        rebase_path("mojo/public/tools:copy_dart_snapshotter", ".", mojo_root)
+    dart_snapshotter_rule = "$dart_snapshotter_path($host_toolchain)"
+  } else {
+    dart_snapshotter_rule = dart_snapshotter_bin
   }
+  dart_snapshotter_dir =
+      get_label_info("$dart_snapshotter_rule", "root_out_dir")
+  dart_snapshotter = "$dart_snapshotter_dir/dart_snapshotter"
 
   action("gen_${bundle_prefix}_snapshot") {
     main_dart = invoker.main_dart
@@ -50,7 +57,7 @@
     ]
 
     deps = [
-      dart_snapshotter_bin,
+      dart_snapshotter_rule,
     ]
     if (defined(invoker.deps)) {
       deps += invoker.deps
diff --git a/mojo/public/mojo.gni b/mojo/public/mojo.gni
index 2e25814..bb06cdb 100644
--- a/mojo/public/mojo.gni
+++ b/mojo/public/mojo.gni
@@ -2,6 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//build/module_args/dart.gni")
 import("//build/module_args/mojo.gni")
 
 # If using the prebuilt shell, gate its usage by the platforms for which it is
@@ -12,6 +13,16 @@
   mojo_use_prebuilt_mojo_shell = is_linux || is_android
 }
 
+# If using the prebuilt dart_snapshotter, gate its usage by the platforms for
+# which it is published.
+mojo_use_prebuilt_dart_snapshotter = false
+if (!defined(mojo_build_dart_snapshotter_from_source) ||
+    !mojo_build_dart_snapshotter_from_source) {
+  mojo_use_prebuilt_dart_snapshotter = true
+} else {
+  assert(defined(dart_snapshotter_bin))
+}
+
 # If using the prebuilt network service, gate its usage by the platforms for
 # which it is published.
 mojo_use_prebuilt_network_service = false
diff --git a/mojo/public/tools/BUILD.gn b/mojo/public/tools/BUILD.gn
index 2368ae9..28a4925 100644
--- a/mojo/public/tools/BUILD.gn
+++ b/mojo/public/tools/BUILD.gn
@@ -28,6 +28,24 @@
   }
 }
 
+if (mojo_use_prebuilt_dart_snapshotter) {
+  copy("copy_dart_snapshotter") {
+    if (host_os == "linux") {
+      platform = "linux-x64"
+    } else if (host_os == "mac") {
+      platform = "mac-x64"
+    } else {
+      assert(false, "$host_os not supported")
+    }
+    sources = [
+      "prebuilt/dart_snapshotter/$platform/dart_snapshotter",
+    ]
+    outputs = [
+      "$root_out_dir/dart_snapshotter",
+    ]
+  }
+}
+
 if (mojo_use_prebuilt_network_service) {
   copy("copy_network_service") {
     filename = "network_service.mojo"
diff --git a/mojo/public/tools/download_dart_snapshotter.py b/mojo/public/tools/download_dart_snapshotter.py
new file mode 100755
index 0000000..0227c68
--- /dev/null
+++ b/mojo/public/tools/download_dart_snapshotter.py
@@ -0,0 +1,101 @@
+#!/usr/bin/env python
+# Copyright 2014 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.
+
+import argparse
+import os
+import subprocess
+import sys
+import tempfile
+import zipfile
+
+BINARY_FOR_PLATFORM = {
+    "linux-x64" : "dart_snapshotter",
+    "mac-x64": "dart_snapshotter",
+}
+
+CURRENT_PATH = os.path.dirname(os.path.realpath(__file__))
+sys.path.insert(0, os.path.join(CURRENT_PATH, "pylib"))
+import gs
+
+PREBUILT_FILE_PATH = os.path.join(CURRENT_PATH, "prebuilt", "dart_snapshotter")
+
+
+def download(tools_directory, version_file):
+  stamp_path = os.path.join(PREBUILT_FILE_PATH, "VERSION")
+
+  version_path = os.path.join(CURRENT_PATH, version_file)
+  with open(version_path) as version_file:
+    version = version_file.read().strip()
+
+  try:
+    with open(stamp_path) as stamp_file:
+      current_version = stamp_file.read().strip()
+      if current_version == version:
+        return 0  # Already have the right version.
+  except IOError:
+    pass  # If the stamp file does not exist we need to download new binaries.
+
+  if sys.platform.startswith("linux"):
+    platforms = ["linux-x64"]
+  elif sys.platform.startswith("darwin"):
+    platforms = ["mac-x64"]
+  else:
+    print "No prebuilt dart_snapshotter available for %s" % sys.platform
+    return 0
+
+  for platform in platforms:
+    download_version_for_platform(version, platform, tools_directory)
+
+  with open(stamp_path, 'w') as stamp_file:
+    stamp_file.write(version)
+  return 0
+
+def download_version_for_platform(version, platform, tools_directory):
+  find_depot_tools_path = os.path.join(CURRENT_PATH, tools_directory)
+  sys.path.insert(0, find_depot_tools_path)
+  # pylint: disable=F0401
+  import find_depot_tools
+  depot_tools_path = find_depot_tools.add_depot_tools_to_path()
+
+  basename = platform + ".zip"
+  gs_path = "gs://mojo/dart_snapshotter/" + version + "/" + basename
+
+  with tempfile.NamedTemporaryFile() as temp_zip_file:
+    gs.download_from_public_bucket(gs_path, temp_zip_file.name,
+                                   depot_tools_path)
+    binary_name = BINARY_FOR_PLATFORM[platform]
+    output_dir = os.path.join(PREBUILT_FILE_PATH, platform)
+    with zipfile.ZipFile(temp_zip_file.name) as z:
+      zi = z.getinfo(binary_name)
+      mode = zi.external_attr >> 16
+      z.extract(zi, output_dir)
+      os.chmod(os.path.join(output_dir, binary_name), mode)
+
+
+def main():
+  parser = argparse.ArgumentParser(
+      description="Download dart_snapshotter binaries from google storage")
+  parser.add_argument("--tools-directory",
+                      dest="tools_directory",
+                      metavar="<tools-directory>",
+                      type=str,
+                      required=True,
+                      help="Path to the directory containing "
+                           "find_depot_tools.py, specified as a relative path "
+                           "from the location of this file.")
+  parser.add_argument("--version-file",
+                      dest="version_file",
+                      metavar="<version-file>",
+                      type=str,
+                      default="../VERSION",
+                      help="Path to the file containing the version of the "
+                           "dart_snapshotter to be fetched, specified as a"
+                           "relative path from the location of this file "
+                           "(default: %(default)s).")
+  args = parser.parse_args()
+  return download(args.tools_directory, args.version_file)
+
+if __name__ == "__main__":
+  sys.exit(main())
diff --git a/mojo/tools/mopy/paths.py b/mojo/tools/mopy/paths.py
index 2ba8ec8..deb30f4 100644
--- a/mojo/tools/mopy/paths.py
+++ b/mojo/tools/mopy/paths.py
@@ -28,6 +28,8 @@
 
     if self.build_dir is not None:
       self.mojo_shell_path = os.path.join(self.build_dir, "mojo_shell")
+      self.dart_snapshotter_path = os.path.join(
+          self.build_dir, "dart_snapshotter")
       self.sky_shell_path = os.path.join(self.build_dir, "sky_shell")
       # TODO(vtl): Use the host OS here, since |config| may not be available.
       # In any case, if the target is Windows, but the host isn't, using
@@ -35,20 +37,27 @@
       if Config.GetHostOS() == Config.OS_WINDOWS:
         self.mojo_shell_path += ".exe"
         self.sky_shell_path += ".exe"
+        self.dart_snapshotter_path += ".exe"
       if config and config.target_os == Config.OS_ANDROID:
         self.target_mojo_shell_path = os.path.join(self.build_dir,
                                                    "apks",
                                                    "MojoShell.apk")
+        # dart_snapshotter only runs on the host platform. There is no build to
+        # run on Android.
+        self.target_dart_snapshotter_path = None
         self.target_sky_shell_path = os.path.join(self.build_dir,
                                                   "apks",
                                                   "SkyDemo.apk")
       else:
         self.target_mojo_shell_path = self.mojo_shell_path
+        self.target_dart_snapshotter_path = self.dart_snapshotter_path
         self.target_sky_shell_path = self.sky_shell_path
     else:
       self.mojo_shell_path = None
+      self.dart_snapshotter_path = None
       self.sky_shell_path = None
       self.target_mojo_shell_path = None
+      self.target_dart_snapshotter_path = None
       self.target_sky_shell_path = None
 
   def RelPath(self, path):
diff --git a/mojo/tools/upload_binaries.py b/mojo/tools/upload_binaries.py
index d1c25cc..9cd4a04 100755
--- a/mojo/tools/upload_binaries.py
+++ b/mojo/tools/upload_binaries.py
@@ -154,6 +154,36 @@
   write_file_to_gs(version, latest_file, config, dry_run)
 
 
+def upload_dart_snapshotter(config, dry_run, verbose):
+  # Only built for Linux and Mac.
+  if not config.target_os in [Config.OS_LINUX, Config.OS_MAC]:
+    return
+
+  paths = Paths(config)
+  zipfile_name = target(config)
+  version = Version().version
+
+  dest = "gs://mojo/dart_snapshotter/" + version + "/" + zipfile_name + ".zip"
+  with tempfile.NamedTemporaryFile() as zip_file:
+    with zipfile.ZipFile(zip_file, 'w') as z:
+      dart_snapshotter_path = paths.target_dart_snapshotter_path
+      with open(dart_snapshotter_path) as dart_snapshotter_binary:
+        dart_snapshotter_filename = os.path.basename(dart_snapshotter_path)
+        zipinfo = zipfile.ZipInfo(dart_snapshotter_filename)
+        zipinfo.external_attr = 0777 << 16L
+        compress_type = zipfile.ZIP_DEFLATED
+        zipinfo.compress_type = compress_type
+        zipinfo.date_time = time.gmtime(os.path.getmtime(dart_snapshotter_path))
+        if verbose:
+          print "zipping %s" % dart_snapshotter_path
+        z.writestr(zipinfo, dart_snapshotter_binary.read())
+    upload(config, zip_file.name, dest, dry_run, gzip=False)
+
+  # Update the LATEST file to contain the version of the new binary.
+  latest_file = "gs://mojo/dart_snapshotter/%s/LATEST" % target(config)
+  write_file_to_gs(version, latest_file, config, dry_run)
+
+
 def upload_app(app_binary_path, config, dry_run):
   app_binary_name = os.path.basename(app_binary_path)
   version = Version().version
@@ -228,6 +258,8 @@
   upload_symbols(config, build_directory,
                  args.symbols_upload_url, args.dry_run)
 
+  upload_dart_snapshotter(config, args.dry_run, args.verbose)
+
   return 0
 
 if __name__ == "__main__":