
The 'touch' command uses the system default timezone if none is supplied. This makes zip files dependent on the timezone of the generating machine. Zip by default stores extended attributes (eg. ctime, atime) on Unix systems. These can be further sources of non-determinism, so switch it off with --no-extra Change-Id: I7e102be7368ef339cbaf47524b90688c32a09238
394 lines
11 KiB
Python
394 lines
11 KiB
Python
NPMJS = "NPMJS"
|
|
|
|
GERRIT = "GERRIT:"
|
|
|
|
NPM_VERSIONS = {
|
|
"bower": "1.8.0",
|
|
"crisper": "2.0.2",
|
|
"vulcanize": "1.14.8",
|
|
}
|
|
|
|
NPM_SHA1S = {
|
|
"bower": "55dbebef0ad9155382d9e9d3e497c1372345b44a",
|
|
"crisper": "7183c58cea33632fb036c91cefd1b43e390d22a2",
|
|
"vulcanize": "679107f251c19ab7539529b1e3fdd40829e6fc63",
|
|
}
|
|
|
|
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_VERSIONS[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)
|
|
|
|
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", "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_zipfiles=transitive_zipfiles,
|
|
transitive_versions=transitive_versions,
|
|
transitive_licenses=transitive_licenses,
|
|
)
|
|
|
|
_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.action(
|
|
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_zipfiles=list([ctx.outputs.zip]),
|
|
transitive_versions=depset(),
|
|
transitive_licenses=licenses)
|
|
|
|
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.action(
|
|
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.action(
|
|
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_zipfiles=zips,
|
|
transitive_versions=versions,
|
|
transitive_licenses=licenses)
|
|
|
|
bower_component_bundle = rule(
|
|
_bower_component_bundle_impl,
|
|
attrs = _common_attrs,
|
|
outputs = {
|
|
"zip": "%{name}.zip",
|
|
"version_json": "%{name}-versions.json",
|
|
},
|
|
)
|
|
"""Groups a set of bower components 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.
|
|
"""
|
|
|
|
def _vulcanize_impl(ctx):
|
|
# intermediate artifact.
|
|
vulcanized = ctx.new_file(
|
|
ctx.configuration.genfiles_dir, ctx.outputs.html, ".vulcanized.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,
|
|
'--inline-scripts',
|
|
'--inline-css',
|
|
'--strip-comments',
|
|
'--out-html', "$p/" + 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,
|
|
])
|
|
|
|
# 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(
|
|
use_default_shell_env = True,
|
|
execution_requirements = {"local": "1"},
|
|
)
|
|
ctx.action(
|
|
mnemonic = "Vulcanize",
|
|
inputs = [ctx.file._run_npm, ctx.file.app,
|
|
ctx.file._vulcanize_archive
|
|
] + list(zips) + ctx.files.srcs,
|
|
outputs = [vulcanized],
|
|
command = cmd,
|
|
**node_tweaks)
|
|
|
|
hermetic_npm_command = "export PATH && " + " ".join([
|
|
'python',
|
|
ctx.file._run_npm.path,
|
|
ctx.file._crisper_archive.path,
|
|
"--always-write-script",
|
|
"--source", 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, vulcanized],
|
|
outputs = [ctx.outputs.js, ctx.outputs.html],
|
|
command = hermetic_npm_command,
|
|
**node_tweaks)
|
|
|
|
_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 = {
|
|
"html": "%{name}.html",
|
|
"js": "%{name}.js",
|
|
},
|
|
)
|
|
|
|
def vulcanize(*args, **kwargs):
|
|
"""Vulcanize runs vulcanize and crisper on a set of sources."""
|
|
_vulcanize_rule(*args, pkg=PACKAGE_NAME, **kwargs)
|