Replace vulcanize with bundle_assets build rule

New rule uses polymer-bundler instead of deprecated vulcanize.
Also use this rule to package UI plugins.
Also for combining PolyGerrit UI.

Feature: Issue 7144
Change-Id: I17380c670fe4a980dc9748b356e7df18aebdb4ca
diff --git a/WORKSPACE b/WORKSPACE
index e490c63..f255ac7 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -955,7 +955,7 @@
 )
 
 npm_binary(
-    name = "vulcanize",
+    name = "polymer-bundler",
     repository = GERRIT,
 )
 
diff --git a/java/com/google/gerrit/httpd/raw/StaticModule.java b/java/com/google/gerrit/httpd/raw/StaticModule.java
index e90e372..20b799b 100644
--- a/java/com/google/gerrit/httpd/raw/StaticModule.java
+++ b/java/com/google/gerrit/httpd/raw/StaticModule.java
@@ -456,7 +456,7 @@
       // Special case assets during development that are built by Bazel and not
       // served out of the source tree.
       //
-      // In the war case, these are either inlined by vulcanize, or live under
+      // In the war case, these are either inlined, or live under
       // /polygerrit_ui in the war file, so we can just treat them as normal
       // assets.
       if (paths.isDev()) {
diff --git a/lib/js/npm.bzl b/lib/js/npm.bzl
index 0fd575d..92f44bd 100644
--- a/lib/js/npm.bzl
+++ b/lib/js/npm.bzl
@@ -1,11 +1,11 @@
 NPM_VERSIONS = {
     "bower": "1.8.2",
     "crisper": "2.0.2",
-    "vulcanize": "1.14.8",
+    "polymer-bundler": "4.0.2",
 }
 
 NPM_SHA1S = {
     "bower": "adf53529c8d4af02ef24fb8d5341c1419d33e2f7",
     "crisper": "7183c58cea33632fb036c91cefd1b43e390d22a2",
-    "vulcanize": "679107f251c19ab7539529b1e3fdd40829e6fc63",
+    "polymer-bundler": "6b296b6099ab5a0e93ca914cbe93e753f2395910",
 }
diff --git a/plugins/codemirror-editor b/plugins/codemirror-editor
index 9f614cd..4ebf98c 160000
--- a/plugins/codemirror-editor
+++ b/plugins/codemirror-editor
@@ -1 +1 @@
-Subproject commit 9f614cdc7e21afa4eb3d32236b4f7549f9079fd0
+Subproject commit 4ebf98c77086477a5fa63e339a539b47d4e8d202
diff --git a/polygerrit-ui/app/rules.bzl b/polygerrit-ui/app/rules.bzl
index 293ef8b..8bf104a 100644
--- a/polygerrit-ui/app/rules.bzl
+++ b/polygerrit-ui/app/rules.bzl
@@ -3,8 +3,8 @@
 load(
     "//tools/bzl:js.bzl",
     "bower_component",
+    "bundle_assets",
     "js_component",
-    "vulcanize",
 )
 
 def polygerrit_bundle(name, srcs, outs, app):
@@ -41,7 +41,7 @@
         ],
     )
 
-    vulcanize(
+    bundle_assets(
         name = appName,
         srcs = srcs,
         app = app,
diff --git a/tools/bzl/js.bzl b/tools/bzl/js.bzl
index 68a929f..d6d0c95 100644
--- a/tools/bzl/js.bzl
+++ b/tools/bzl/js.bzl
@@ -286,38 +286,38 @@
     },
 )
 
-"""Groups a set of bower components together in a zip file.
+def _bundle_impl(ctx):
+    """Groups a set of .html and .js together in a zip file.
 
-Outputs:
-  NAME-versions.json:
-    a JSON file containing a PKG-NAME => PKG-NAME#VERSION mapping for the
-    transitive dependencies.
-  NAME.zip:
-    a zip file containing the transitive dependencies for this bundle.
-"""
+    Outputs:
+      NAME-versions.json:
+        a JSON file containing a PKG-NAME => PKG-NAME#VERSION mapping for the
+        transitive dependencies.
+    NAME.zip:
+      a zip file containing the transitive dependencies for this bundle.
+    """
 
-def _vulcanize_impl(ctx):
     # intermediate artifact if split is wanted.
     if ctx.attr.split:
-        vulcanized = ctx.new_file(
+        bundled = ctx.new_file(
             ctx.configuration.genfiles_dir,
             ctx.outputs.html,
-            ".vulcanized.html",
+            ".bundled.html",
         )
     else:
-        vulcanized = ctx.outputs.html
+        bundled = ctx.outputs.html
     destdir = ctx.outputs.html.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,
+        "$p/" + ctx.file._bundler_archive.path,
         "--inline-scripts",
         "--inline-css",
         "--strip-comments",
-        "--out-html",
-        "$p/" + vulcanized.path,
+        "--out-file",
+        "$p/" + bundled.path,
         ctx.file.app.path,
     ])
 
@@ -346,13 +346,13 @@
         use_default_shell_env = True,
     )
     ctx.actions.run_shell(
-        mnemonic = "Vulcanize",
+        mnemonic = "Bundle",
         inputs = [
             ctx.file._run_npm,
             ctx.file.app,
-            ctx.file._vulcanize_archive,
+            ctx.file._bundler_archive,
         ] + list(zips) + ctx.files.srcs,
-        outputs = [vulcanized],
+        outputs = [bundled],
         command = cmd,
         **node_tweaks
     )
@@ -364,7 +364,7 @@
             ctx.file._crisper_archive.path,
             "--always-write-script",
             "--source",
-            vulcanized.path,
+            bundled.path,
             "--html",
             ctx.outputs.html.path,
             "--js",
@@ -377,22 +377,22 @@
                 ctx.file._run_npm,
                 ctx.file.app,
                 ctx.file._crisper_archive,
-                vulcanized,
+                bundled,
             ],
             outputs = [ctx.outputs.js, ctx.outputs.html],
             command = hermetic_npm_command,
             **node_tweaks
         )
 
-def _vulcanize_output_func(name, split):
+def _bundle_output_func(name, split):
     _ignore = [name]  # unused.
     out = {"html": "%{name}.html"}
     if split:
         out["js"] = "%{name}.js"
     return out
 
-_vulcanize_rule = rule(
-    _vulcanize_impl,
+_bundle_rule = rule(
+    _bundle_impl,
     attrs = {
         "deps": attr.label_list(providers = ["transitive_zipfiles"]),
         "app": attr.label(
@@ -412,8 +412,8 @@
             default = Label("//tools/js:run_npm_binary.py"),
             allow_single_file = True,
         ),
-        "_vulcanize_archive": attr.label(
-            default = Label("@vulcanize//:%s" % _npm_tarball("vulcanize")),
+        "_bundler_archive": attr.label(
+            default = Label("@polymer-bundler//:%s" % _npm_tarball("polymer-bundler")),
             allow_single_file = True,
         ),
         "_crisper_archive": attr.label(
@@ -421,12 +421,12 @@
             allow_single_file = True,
         ),
     },
-    outputs = _vulcanize_output_func,
+    outputs = _bundle_output_func,
 )
 
-def vulcanize(*args, **kwargs):
-    """Vulcanize runs vulcanize and (optionally) crisper on a set of sources."""
-    _vulcanize_rule(*args, pkg = PACKAGE_NAME, **kwargs)
+def bundle_assets(*args, **kwargs):
+    """Combine html, js, css files and optionally split into js and html bundles."""
+    _bundle_rule(*args, pkg = PACKAGE_NAME, **kwargs)
 
 def polygerrit_plugin(name, app, srcs = [], assets = None, **kwargs):
     """Bundles plugin dependencies for deployment.
@@ -443,7 +443,7 @@
     """
 
     # Combines all .js and .html files into foo_combined.js and foo_combined.html
-    _vulcanize_rule(
+    _bundle_rule(
         name = name + "_combined",
         app = app,
         srcs = srcs if app in srcs else srcs + [app],
diff --git a/tools/js/run_npm_binary.py b/tools/js/run_npm_binary.py
index dfcdaca..bdee5ab 100644
--- a/tools/js/run_npm_binary.py
+++ b/tools/js/run_npm_binary.py
@@ -56,7 +56,8 @@
                 extract_one(mem)
         # Extract bin last so other processes only short circuit when
         # extraction is finished.
-        extract_one(tar.getmember(bin))
+        if bin in tar.getnames():
+            extract_one(tar.getmember(bin))
 
 
 def main(args):
@@ -77,7 +78,9 @@
     sha1 = hashlib.sha1(open(path, 'rb').read()).hexdigest()
     outdir = '%s-%s' % (path[:-len(suffix)], sha1)
     rel_bin = os.path.join('package', 'bin', name)
+    rel_lib_bin = os.path.join('package', 'lib', 'bin', name + '.js')
     bin = os.path.join(outdir, rel_bin)
+    libbin = os.path.join(outdir, rel_lib_bin)
     if not os.path.isfile(bin):
         extract(path, outdir, rel_bin)
 
@@ -85,7 +88,12 @@
     if nodejs:
         # Debian installs Node.js as 'nodejs', due to a conflict with another
         # package.
-        subprocess.check_call([nodejs, bin] + args[1:])
+        if not os.path.isfile(bin) and os.path.isfile(libbin):
+            subprocess.check_call([nodejs, libbin] + args[1:])
+        else:
+            subprocess.check_call([nodejs, bin] + args[1:])
+    elif not os.path.isfile(bin) and os.path.isfile(libbin):
+        subprocess.check_call([libbin] + args[1:])
     else:
         subprocess.check_call([bin] + args[1:])