bazel: add rules for vulcanize & crisper, and use them in //polygerrit-ui/app.

Change-Id: Icaa15889f9963464897742c5003bbf0b15c56ffd
diff --git a/tools/bzl/js.bzl b/tools/bzl/js.bzl
index 978059a..38d6c91 100644
--- a/tools/bzl/js.bzl
+++ b/tools/bzl/js.bzl
@@ -39,11 +39,12 @@
   python = ctx.which("python")
   script = ctx.path(ctx.attr._download_script)
 
-  args = [python, script, "-o", dest, "-u", url]
+  sha1 = NPM_SHA1S[name]
+  args = [python, script, "-o", dest, "-u", url, "-v", sha1]
   out = ctx.execute(args)
   if out.return_code:
     fail("failed %s: %s" % (args, out.stderr))
-  ctx.file("BUILD", "filegroup(name='tarball', srcs=['%s'])" % base, False)
+  ctx.file("BUILD", "package(default_visibility=['//visibility:public'])\nfilegroup(name='tarball', srcs=['%s'])" % base, False)
 
 npm_binary = repository_rule(
     implementation=_npm_binary_impl,
@@ -55,12 +56,13 @@
     })
 
 
-def _run_npm_binary_str(ctx, tarball, name):
+# for use in repo rules.
+def _run_npm_binary_str(ctx, tarball, args):
   python_bin = ctx.which("python")
   return " ".join([
     python_bin,
     ctx.path(ctx.attr._run_npm),
-    ctx.path(tarball)])
+    ctx.path(tarball)] + args)
 
 
 def _bower_archive(ctx):
@@ -72,7 +74,7 @@
   cmd = [
       ctx.which("python"),
       ctx.path(ctx.attr._download_bower),
-      '-b', '%s' % _run_npm_binary_str(ctx, ctx.attr._bower_archive, "bower"),
+      '-b', '%s' % _run_npm_binary_str(ctx, ctx.attr._bower_archive, []),
       '-n', ctx.name,
       '-p', ctx.attr.package,
       '-v', ctx.attr.version,
@@ -235,6 +237,10 @@
   for d in ctx.attr.deps:
     versions += d.transitive_versions
 
+  licenses = set([])
+  for d in ctx.attr.deps:
+    licenses += d.transitive_versions
+
   out_zip = ctx.outputs.zip
   out_versions = ctx.outputs.version_json
 
@@ -259,6 +265,11 @@
     mnemonic="BowerVersions",
     command="(echo '{' ; for j in  %s ; do cat $j; echo ',' ; done ; echo \\\"\\\":\\\"\\\"; echo '}') > %s" % (" ".join([v.path for v in versions]), out_versions.path))
 
+  return struct(
+    transitive_zipfiles=zips,
+    transitive_versions=versions,
+    transitive_licenses=licenses)
+
 
 bower_component_bundle = rule(
   _bower_component_bundle_impl,
@@ -268,3 +279,88 @@
     "version_json": "%{name}-versions.json",
   }
 )
+
+def _vulcanize_impl(ctx):
+  destdir = ctx.outputs.vulcanized.path + ".dir"
+  zips =  [z for d in ctx.attr.deps for z in d.transitive_zipfiles ]
+
+  hermetic_npm_binary = " ".join([
+    'python',
+    "$p/" + ctx.file._run_npm.path,
+    "$p/" + ctx.file._vulcanize_archive.path,
+    '--inline-scripts',
+    '--inline-css',
+    '--strip-comments',
+    '--out-html', "$p/" + ctx.outputs.vulcanized.path,
+    ctx.file.app.path
+  ])
+
+  pkg_dir = ctx.attr.pkg.lstrip("/")
+  cmd = " && ".join([
+    # unpack dependencies.
+    "export PATH",
+    "p=$PWD",
+    "rm -rf %s" % destdir,
+    "mkdir -p %s/%s/bower_components" % (destdir, pkg_dir),
+    "for z in %s; do unzip -qd %s/%s/bower_components/ $z; done" % (
+      ' '.join([z.path for z in zips]), destdir, pkg_dir),
+    "tar -cf - %s | tar -C %s -xf -" % (" ".join([s.path for s in ctx.files.srcs]), destdir),
+    "cd %s" % destdir,
+    hermetic_npm_binary,
+  ])
+  ctx.action(
+    mnemonic = "Vulcanize",
+    inputs = [ctx.file._run_npm, ctx.file.app,
+              ctx.file._vulcanize_archive
+    ] + list(zips) + ctx.files.srcs,
+    outputs = [ctx.outputs.vulcanized],
+    command = cmd)
+
+  hermetic_npm_command = "export PATH && " + " ".join([
+    'python',
+    ctx.file._run_npm.path,
+    ctx.file._crisper_archive.path,
+    "--always-write-script",
+    "--source", ctx.outputs.vulcanized.path,
+    "--html", ctx.outputs.html.path,
+    "--js", ctx.outputs.js.path])
+
+  ctx.action(
+    mnemonic = "Crisper",
+    inputs = [ctx.file._run_npm, ctx.file.app,
+              ctx.file._crisper_archive, ctx.outputs.vulcanized],
+    outputs = [ctx.outputs.js, ctx.outputs.html],
+    command = hermetic_npm_command)
+
+
+_vulcanize_rule = rule(
+  _vulcanize_impl,
+  attrs = {
+    "deps": attr.label_list(providers=["transitive_zipfiles"]),
+    "app": attr.label(mandatory=True, allow_single_file=True),
+    "srcs": attr.label_list(allow_files=[".js", ".html", ".txt", ".css", ".ico"]),
+
+    "pkg": attr.string(mandatory=True),
+    "_run_npm": attr.label(
+      default=Label("//tools/js:run_npm_binary.py"),
+      allow_single_file=True
+    ),
+    "_vulcanize_archive": attr.label(
+      default=Label("@vulcanize//:%s" % _npm_tarball("vulcanize")),
+      allow_single_file=True
+    ),
+    "_crisper_archive": attr.label(
+      default=Label("@crisper//:%s" % _npm_tarball("crisper")),
+      allow_single_file=True
+    ),
+  },
+  outputs = {
+    "vulcanized": "%{name}.vulcanized.html",
+    "html": "%{name}.crisped.html",
+    "js": "%{name}.crisped.js",
+  }
+)
+
+def vulcanize(*args, **kwargs):
+  """Vulcanize runs vulcanize and crisper on a set of sources."""
+  _vulcanize_rule(*args, pkg=PACKAGE_NAME, **kwargs)