Merge branch 'stable-2.15'
* stable-2.15:
Update Gerrit API to 2.14.12
Change-Id: Ie8a781ddf4528c64b8b25ec3895ffee17b2d6459
diff --git a/bouncycastle.bzl b/bouncycastle.bzl
index 9c75973..02960bc 100644
--- a/bouncycastle.bzl
+++ b/bouncycastle.bzl
@@ -5,23 +5,23 @@
"""
# This should be the same version used in Gerrit.
-BC_VERS = "1.57"
+BC_VERS = "1.60"
def bouncycastle_repos():
maven_jar(
name = "bouncycastle_bcprov",
artifact = "org.bouncycastle:bcprov-jdk15on:" + BC_VERS,
- sha1 = "f66a135611d42c992e5745788c3f94eb06464537",
+ sha1 = "bd47ad3bd14b8e82595c7adaa143501e60842a84",
)
maven_jar(
name = "bouncycastle_bcpg",
artifact = "org.bouncycastle:bcpg-jdk15on:" + BC_VERS,
- sha1 = "7b2d587f5e3780b79e1d35af3e84d00634e9420b",
+ sha1 = "13c7a199c484127daad298996e95818478431a2c",
)
maven_jar(
name = "bouncycastle_bcpkix",
artifact = "org.bouncycastle:bcpkix-jdk15on:" + BC_VERS,
- sha1 = "5c96e34bc9bd4cd6870e6d193a99438f1e274ca7",
+ sha1 = "d0c46320fbc07be3a24eb13a56cee4e3d38e0c75",
)
native.bind(
name = "bcprov",
diff --git a/gerrit_api_maven_local.bzl b/gerrit_api_maven_local.bzl
index e95d7d0..e431f69 100644
--- a/gerrit_api_maven_local.bzl
+++ b/gerrit_api_maven_local.bzl
@@ -5,7 +5,7 @@
gerrit_api is rule for fetching Gerrit plugin API using Bazel.
"""
-VER = "2.15.2-SNAPSHOT"
+VER = "2.16-SNAPSHOT"
def gerrit_api_maven_local():
bouncycastle_repos()
diff --git a/gerrit_polymer.bzl b/gerrit_polymer.bzl
new file mode 100644
index 0000000..1f490cc
--- /dev/null
+++ b/gerrit_polymer.bzl
@@ -0,0 +1,18 @@
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive", "http_file")
+
+def gerrit_polymer():
+ native.http_archive(
+ name = "io_bazel_rules_closure",
+ sha256 = "4dd84dd2bdd6c9f56cb5a475d504ea31d199c34309e202e9379501d01c3067e5",
+ strip_prefix = "rules_closure-3103a773820b59b76345f94c231cb213e0d404e2",
+ urls = ["https://github.com/bazelbuild/rules_closure/archive/3103a773820b59b76345f94c231cb213e0d404e2.tar.gz"],
+ )
+
+ # File is specific to Polymer and copied from the Closure Github -- should be
+ # synced any time there are major changes to Polymer.
+ # https://github.com/google/closure-compiler/blob/master/contrib/externs/polymer-1.0.js
+ native.http_file(
+ name = "polymer_closure",
+ sha256 = "5a589bdba674e1fec7188e9251c8624ebf2d4d969beb6635f9148f420d1e08b1",
+ urls = ["https://raw.githubusercontent.com/google/closure-compiler/775609aad61e14aef289ebec4bfc09ad88877f9e/contrib/externs/polymer-1.0.js"],
+ )
diff --git a/lib/BUILD b/lib/BUILD
new file mode 100644
index 0000000..00301d3
--- /dev/null
+++ b/lib/BUILD
@@ -0,0 +1 @@
+# Empty marker file, indicating this directory is a Bazel package.
diff --git a/lib/js/BUILD b/lib/js/BUILD
new file mode 100644
index 0000000..00301d3
--- /dev/null
+++ b/lib/js/BUILD
@@ -0,0 +1 @@
+# Empty marker file, indicating this directory is a Bazel package.
diff --git a/lib/js/externs/BUILD b/lib/js/externs/BUILD
new file mode 100644
index 0000000..fab3954
--- /dev/null
+++ b/lib/js/externs/BUILD
@@ -0,0 +1,25 @@
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+package(
+ default_visibility = ["//visibility:public"],
+)
+
+load("@io_bazel_rules_closure//closure:defs.bzl", "closure_js_library")
+
+closure_js_library(
+ name = "plugin",
+ srcs = ["plugin.js"],
+ no_closure_library = True,
+)
diff --git a/lib/js/externs/plugin.js b/lib/js/externs/plugin.js
new file mode 100644
index 0000000..c88c724
--- /dev/null
+++ b/lib/js/externs/plugin.js
@@ -0,0 +1,30 @@
+/**
+ * @license
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @fileoverview Closure compiler externs for the Gerrit UI plugins.
+ * @externs
+ */
+
+/* eslint-disable no-var */
+
+var Gerrit = {};
+
+/**
+ * @param {!Function} callback
+ */
+Gerrit.install = function(callback) {};
diff --git a/lib/js/npm.bzl b/lib/js/npm.bzl
new file mode 100644
index 0000000..92f44bd
--- /dev/null
+++ b/lib/js/npm.bzl
@@ -0,0 +1,11 @@
+NPM_VERSIONS = {
+ "bower": "1.8.2",
+ "crisper": "2.0.2",
+ "polymer-bundler": "4.0.2",
+}
+
+NPM_SHA1S = {
+ "bower": "adf53529c8d4af02ef24fb8d5341c1419d33e2f7",
+ "crisper": "7183c58cea33632fb036c91cefd1b43e390d22a2",
+ "polymer-bundler": "6b296b6099ab5a0e93ca914cbe93e753f2395910",
+}
diff --git a/lib/polymer_externs/BUILD b/lib/polymer_externs/BUILD
new file mode 100644
index 0000000..9a45508
--- /dev/null
+++ b/lib/polymer_externs/BUILD
@@ -0,0 +1,32 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+package(
+ default_visibility = ["//visibility:public"],
+)
+
+load("@io_bazel_rules_closure//closure:defs.bzl", "closure_js_library")
+
+genrule(
+ name = "polymer_closure_renamed",
+ srcs = ["@polymer_closure//file"],
+ outs = ["polymer_closure_renamed.js"],
+ cmd = "cp $< $@",
+)
+
+closure_js_library(
+ name = "polymer_closure",
+ srcs = [":polymer_closure_renamed"],
+ no_closure_library = True,
+)
diff --git a/tools/js.bzl b/tools/js.bzl
new file mode 100644
index 0000000..8b99f29
--- /dev/null
+++ b/tools/js.bzl
@@ -0,0 +1,520 @@
+NPMJS = "NPMJS"
+
+GERRIT = "GERRIT:"
+
+load("//lib/js:npm.bzl", "NPM_SHA1S", "NPM_VERSIONS")
+load("@io_bazel_rules_closure//closure:defs.bzl", "closure_js_binary", "closure_js_library")
+
+def _npm_tarball(name):
+ return "%s@%s.npm_binary.tgz" % (name, NPM_VERSIONS[name])
+
+def _npm_binary_impl(ctx):
+ """rule to download a NPM archive."""
+ name = ctx.name
+ version = NPM_VERSIONS[name]
+ sha1 = NPM_SHA1S[name]
+
+ dir = "%s-%s" % (name, version)
+ filename = "%s.tgz" % dir
+ base = "%s@%s.npm_binary.tgz" % (name, version)
+ dest = ctx.path(base)
+ repository = ctx.attr.repository
+ if repository == GERRIT:
+ url = "http://gerrit-maven.storage.googleapis.com/npm-packages/%s" % filename
+ elif repository == NPMJS:
+ url = "http://registry.npmjs.org/%s/-/%s" % (name, filename)
+ else:
+ fail("repository %s not in {%s,%s}" % (repository, GERRIT, NPMJS))
+
+ python = ctx.which("python")
+ script = ctx.path(ctx.attr._download_script)
+
+ 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", "package(default_visibility=['//visibility:public'])\nfilegroup(name='tarball', srcs=['%s'])" % base, False)
+
+npm_binary = repository_rule(
+ attrs = {
+ # Label resolves within repo of the .bzl file.
+ "_download_script": attr.label(default = Label("//tools:download_file.py")),
+ "repository": attr.string(default = NPMJS),
+ },
+ local = True,
+ implementation = _npm_binary_impl,
+)
+
+# 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),
+ ] + args)
+
+def _bower_archive(ctx):
+ """Download a bower package."""
+ download_name = "%s__download_bower.zip" % ctx.name
+ renamed_name = "%s__renamed.zip" % ctx.name
+ version_name = "%s__version.json" % ctx.name
+
+ cmd = [
+ ctx.which("python"),
+ ctx.path(ctx.attr._download_bower),
+ "-b",
+ "%s" % _run_npm_binary_str(ctx, ctx.attr._bower_archive, []),
+ "-n",
+ ctx.name,
+ "-p",
+ ctx.attr.package,
+ "-v",
+ ctx.attr.version,
+ "-s",
+ ctx.attr.sha1,
+ "-o",
+ download_name,
+ ]
+
+ out = ctx.execute(cmd)
+ if out.return_code:
+ fail("failed %s: %s" % (" ".join(cmd), out.stderr))
+
+ _bash(ctx, " && ".join([
+ "TMP=$(mktemp -d || mktemp -d -t bazel-tmp)",
+ "TZ=UTC",
+ "export UTC",
+ "cd $TMP",
+ "mkdir bower_components",
+ "cd bower_components",
+ "unzip %s" % ctx.path(download_name),
+ "cd ..",
+ "find . -exec touch -t 198001010000 '{}' ';'",
+ "zip -Xr %s bower_components" % renamed_name,
+ "cd ..",
+ "rm -rf ${TMP}",
+ ]))
+
+ dep_version = ctx.attr.semver if ctx.attr.semver else ctx.attr.version
+ ctx.file(
+ version_name,
+ '"%s":"%s#%s"' % (ctx.name, ctx.attr.package, dep_version),
+ )
+ ctx.file(
+ "BUILD",
+ "\n".join([
+ "package(default_visibility=['//visibility:public'])",
+ "filegroup(name = 'zipfile', srcs = ['%s'], )" % download_name,
+ "filegroup(name = 'version_json', srcs = ['%s'], visibility=['//visibility:public'])" % version_name,
+ ]),
+ False,
+ )
+
+def _bash(ctx, cmd):
+ cmd_list = ["bash", "-c", cmd]
+ out = ctx.execute(cmd_list)
+ if out.return_code:
+ fail("failed %s: %s" % (" ".join(cmd_list), out.stderr))
+
+bower_archive = repository_rule(
+ _bower_archive,
+ attrs = {
+ "_bower_archive": attr.label(default = Label("@bower//:%s" % _npm_tarball("bower"))),
+ "_run_npm": attr.label(default = Label("//tools/js:run_npm_binary.py")),
+ "_download_bower": attr.label(default = Label("//tools/js:download_bower.py")),
+ "sha1": attr.string(mandatory = True),
+ "version": attr.string(mandatory = True),
+ "package": attr.string(mandatory = True),
+ "semver": attr.string(),
+ },
+)
+
+def _bower_component_impl(ctx):
+ transitive_zipfiles = depset([ctx.file.zipfile])
+ for d in ctx.attr.deps:
+ transitive_zipfiles += d.transitive_zipfiles
+
+ transitive_licenses = depset()
+ if ctx.file.license:
+ transitive_licenses += depset([ctx.file.license])
+
+ for d in ctx.attr.deps:
+ transitive_licenses += d.transitive_licenses
+
+ transitive_versions = depset(ctx.files.version_json)
+ for d in ctx.attr.deps:
+ transitive_versions += d.transitive_versions
+
+ return struct(
+ transitive_licenses = transitive_licenses,
+ transitive_versions = transitive_versions,
+ transitive_zipfiles = transitive_zipfiles,
+ )
+
+_common_attrs = {
+ "deps": attr.label_list(providers = [
+ "transitive_zipfiles",
+ "transitive_versions",
+ "transitive_licenses",
+ ]),
+}
+
+def _js_component(ctx):
+ dir = ctx.outputs.zip.path + ".dir"
+ name = ctx.outputs.zip.basename
+ if name.endswith(".zip"):
+ name = name[:-4]
+ dest = "%s/%s" % (dir, name)
+ cmd = " && ".join([
+ "TZ=UTC",
+ "export TZ",
+ "mkdir -p %s" % dest,
+ "cp %s %s/" % (" ".join([s.path for s in ctx.files.srcs]), dest),
+ "cd %s" % dir,
+ "find . -exec touch -t 198001010000 '{}' ';'",
+ "zip -Xqr ../%s *" % ctx.outputs.zip.basename,
+ ])
+
+ ctx.actions.run_shell(
+ inputs = ctx.files.srcs,
+ outputs = [ctx.outputs.zip],
+ command = cmd,
+ mnemonic = "GenBowerZip",
+ )
+
+ licenses = depset()
+ if ctx.file.license:
+ licenses += depset([ctx.file.license])
+
+ return struct(
+ transitive_licenses = licenses,
+ transitive_versions = depset(),
+ transitive_zipfiles = list([ctx.outputs.zip]),
+ )
+
+js_component = rule(
+ _js_component,
+ attrs = dict(_common_attrs.items() + {
+ "srcs": attr.label_list(allow_files = [".js"]),
+ "license": attr.label(allow_single_file = True),
+ }.items()),
+ outputs = {
+ "zip": "%{name}.zip",
+ },
+)
+
+_bower_component = rule(
+ _bower_component_impl,
+ attrs = dict(_common_attrs.items() + {
+ "zipfile": attr.label(allow_single_file = [".zip"]),
+ "license": attr.label(allow_single_file = True),
+ "version_json": attr.label(allow_files = [".json"]),
+
+ # If set, define by hand, and don't regenerate this entry in bower2bazel.
+ "seed": attr.bool(default = False),
+ }.items()),
+)
+
+# TODO(hanwen): make license mandatory.
+def bower_component(name, license = None, **kwargs):
+ prefix = "//lib:LICENSE-"
+ if license and not license.startswith(prefix):
+ license = prefix + license
+ _bower_component(
+ name = name,
+ license = license,
+ zipfile = "@%s//:zipfile" % name,
+ version_json = "@%s//:version_json" % name,
+ **kwargs
+ )
+
+def _bower_component_bundle_impl(ctx):
+ """A bunch of bower components zipped up."""
+ zips = depset()
+ for d in ctx.attr.deps:
+ zips += d.transitive_zipfiles
+
+ versions = depset()
+ for d in ctx.attr.deps:
+ versions += d.transitive_versions
+
+ licenses = depset()
+ for d in ctx.attr.deps:
+ licenses += d.transitive_versions
+
+ out_zip = ctx.outputs.zip
+ out_versions = ctx.outputs.version_json
+
+ ctx.actions.run_shell(
+ inputs = list(zips),
+ outputs = [out_zip],
+ command = " && ".join([
+ "p=$PWD",
+ "TZ=UTC",
+ "export TZ",
+ "rm -rf %s.dir" % out_zip.path,
+ "mkdir -p %s.dir/bower_components" % out_zip.path,
+ "cd %s.dir/bower_components" % out_zip.path,
+ "for z in %s; do unzip -q $p/$z ; done" % " ".join(sorted([z.path for z in zips])),
+ "cd ..",
+ "find . -exec touch -t 198001010000 '{}' ';'",
+ "zip -Xqr $p/%s bower_components/*" % out_zip.path,
+ ]),
+ mnemonic = "BowerCombine",
+ )
+
+ ctx.actions.run_shell(
+ inputs = list(versions),
+ outputs = [out_versions],
+ 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_licenses = licenses,
+ transitive_versions = versions,
+ transitive_zipfiles = zips,
+ )
+
+bower_component_bundle = rule(
+ _bower_component_bundle_impl,
+ attrs = _common_attrs,
+ outputs = {
+ "zip": "%{name}.zip",
+ "version_json": "%{name}-versions.json",
+ },
+)
+
+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.
+ """
+
+ # intermediate artifact if split is wanted.
+ if ctx.attr.split:
+ bundled = ctx.new_file(
+ ctx.configuration.genfiles_dir,
+ ctx.outputs.html,
+ ".bundled.html",
+ )
+ else:
+ 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._bundler_archive.path,
+ "--inline-scripts",
+ "--inline-css",
+ "--strip-comments",
+ "--out-file",
+ "$p/" + bundled.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,
+ ])
+
+ # Node/NPM is not (yet) hermeticized, so we have to get the binary
+ # from the environment, and it may be under $HOME, so we can't run
+ # in the sandbox.
+ node_tweaks = dict(
+ execution_requirements = {"local": "1"},
+ use_default_shell_env = True,
+ )
+ ctx.actions.run_shell(
+ mnemonic = "Bundle",
+ inputs = [
+ ctx.file._run_npm,
+ ctx.file.app,
+ ctx.file._bundler_archive,
+ ] + list(zips) + ctx.files.srcs,
+ outputs = [bundled],
+ command = cmd,
+ **node_tweaks
+ )
+
+ if ctx.attr.split:
+ hermetic_npm_command = "export PATH && " + " ".join([
+ "python",
+ ctx.file._run_npm.path,
+ ctx.file._crisper_archive.path,
+ "--always-write-script",
+ "--source",
+ bundled.path,
+ "--html",
+ ctx.outputs.html.path,
+ "--js",
+ ctx.outputs.js.path,
+ ])
+
+ ctx.actions.run_shell(
+ mnemonic = "Crisper",
+ inputs = [
+ ctx.file._run_npm,
+ ctx.file.app,
+ ctx.file._crisper_archive,
+ bundled,
+ ],
+ outputs = [ctx.outputs.js, ctx.outputs.html],
+ command = hermetic_npm_command,
+ **node_tweaks
+ )
+
+def _bundle_output_func(name, split):
+ _ignore = [name] # unused.
+ out = {"html": "%{name}.html"}
+ if split:
+ out["js"] = "%{name}.js"
+ return out
+
+_bundle_rule = rule(
+ _bundle_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),
+ "split": attr.bool(default = True),
+ "_run_npm": attr.label(
+ default = Label("//tools/js:run_npm_binary.py"),
+ allow_single_file = True,
+ ),
+ "_bundler_archive": attr.label(
+ default = Label("@polymer-bundler//:%s" % _npm_tarball("polymer-bundler")),
+ allow_single_file = True,
+ ),
+ "_crisper_archive": attr.label(
+ default = Label("@crisper//:%s" % _npm_tarball("crisper")),
+ allow_single_file = True,
+ ),
+ },
+ outputs = _bundle_output_func,
+)
+
+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.
+
+ This rule bundles all Polymer elements and JS dependencies into .html and .js files.
+ Run-time dependencies (e.g. JS libraries loaded after plugin starts) should be provided using "assets" property.
+ Output of this rule is a FileSet with "${name}_fs", with deploy artifacts in "plugins/${name}/static".
+
+ Args:
+ name: String, plugin name.
+ app: String, the main or root source file.
+ assets: Fileset, additional files to be used by plugin in runtime, exported to "plugins/${name}/static".
+ srcs: Source files required for combining.
+ """
+
+ # Combines all .js and .html files into foo_combined.js and foo_combined.html
+ _bundle_rule(
+ name = name + "_combined",
+ app = app,
+ srcs = srcs if app in srcs else srcs + [app],
+ pkg = PACKAGE_NAME,
+ **kwargs
+ )
+
+ closure_js_binary(
+ name = name + "_bin",
+ compilation_level = "SIMPLE",
+ defs = [
+ "--polymer_version=1",
+ "--language_out=ECMASCRIPT6",
+ "--rewrite_polyfills=false",
+ ],
+ deps = [
+ name + "_closure_lib",
+ ],
+ )
+
+ closure_js_library(
+ name = name + "_closure_lib",
+ srcs = [name + "_combined.js"],
+ convention = "GOOGLE",
+ no_closure_library = True,
+ deps = [
+ "@com_googlesource_gerrit_bazlets//lib/polymer_externs:polymer_closure",
+ "@com_googlesource_gerrit_bazlets//lib/js/externs:plugin",
+ ],
+ )
+
+ native.genrule(
+ name = name + "_rename_html",
+ srcs = [name + "_combined.html"],
+ outs = [name + ".html"],
+ cmd = "sed 's/<script src=\"" + name + "_combined.js\"/<script src=\"" + name + ".js\"/g' $(SRCS) > $(OUTS)",
+ output_to_bindir = True,
+ )
+
+ native.genrule(
+ name = name + "_rename_js",
+ srcs = [name + "_bin.js"],
+ outs = [name + ".js"],
+ cmd = "cp $< $@",
+ output_to_bindir = True,
+ )
+
+ static_files = [
+ name + ".js",
+ name + ".html",
+ ]
+
+ if assets:
+ nested, direct = [], []
+ for x in assets:
+ target = nested if "/" in x else direct
+ target.append(x)
+
+ static_files += direct
+
+ if nested:
+ native.genrule(
+ name = name + "_copy_assets",
+ srcs = assets,
+ outs = [f.split("/")[-1] for f in nested],
+ cmd = "cp $(SRCS) $(@D)",
+ output_to_bindir = True,
+ )
+ static_files += [":" + name + "_copy_assets"]
+
+ native.filegroup(
+ name = name,
+ srcs = static_files,
+ )
diff --git a/tools/js/BUILD b/tools/js/BUILD
new file mode 100644
index 0000000..fedaf7f
--- /dev/null
+++ b/tools/js/BUILD
@@ -0,0 +1 @@
+exports_files(["run_npm_binary.py"])
diff --git a/tools/js/run_npm_binary.py b/tools/js/run_npm_binary.py
new file mode 100644
index 0000000..bdee5ab
--- /dev/null
+++ b/tools/js/run_npm_binary.py
@@ -0,0 +1,102 @@
+#!/usr/bin/env python
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import print_function
+
+import atexit
+from distutils import spawn
+import hashlib
+import os
+import shutil
+import subprocess
+import sys
+import tarfile
+import tempfile
+
+
+def extract(path, outdir, bin):
+ if os.path.exists(os.path.join(outdir, bin)):
+ return # Another process finished extracting, ignore.
+
+ # Use a temp directory adjacent to outdir so shutil.move can use the same
+ # device atomically.
+ tmpdir = tempfile.mkdtemp(dir=os.path.dirname(outdir))
+
+ def cleanup():
+ try:
+ shutil.rmtree(tmpdir)
+ except OSError:
+ pass # Too late now
+ atexit.register(cleanup)
+
+ def extract_one(mem):
+ dest = os.path.join(outdir, mem.name)
+ tar.extract(mem, path=tmpdir)
+ try:
+ os.makedirs(os.path.dirname(dest))
+ except OSError:
+ pass # Either exists, or will fail on the next line.
+ shutil.move(os.path.join(tmpdir, mem.name), dest)
+
+ with tarfile.open(path, 'r:gz') as tar:
+ for mem in tar.getmembers():
+ if mem.name != bin:
+ extract_one(mem)
+ # Extract bin last so other processes only short circuit when
+ # extraction is finished.
+ if bin in tar.getnames():
+ extract_one(tar.getmember(bin))
+
+
+def main(args):
+ path = args[0]
+ suffix = '.npm_binary.tgz'
+ tgz = os.path.basename(path)
+
+ parts = tgz[:-len(suffix)].split('@')
+
+ if not tgz.endswith(suffix) or len(parts) != 2:
+ print('usage: %s <path/to/npm_binary>' % sys.argv[0], file=sys.stderr)
+ return 1
+
+ name, _ = parts
+
+ # Avoid importing from gerrit because we don't want to depend on the right
+ # working directory
+ 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)
+
+ nodejs = spawn.find_executable('nodejs')
+ if nodejs:
+ # Debian installs Node.js as 'nodejs', due to a conflict with another
+ # package.
+ 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:])
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))