| #!/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. |
| |
| """Suggested call sequence: |
| |
| python tools/js/bower2bazel.py -w lib/js/bower_archives.bzl -b lib/js/bower_components.bzl |
| """ |
| |
| from __future__ import print_function |
| |
| import collections |
| import json |
| import hashlib |
| import optparse |
| import os |
| import subprocess |
| import sys |
| import tempfile |
| import glob |
| import bowerutil |
| |
| # list of licenses for packages that don't specify one in their bower.json file. |
| package_licenses = { |
| "es6-promise": "es6-promise", |
| "fetch": "fetch", |
| "iron-a11y-announcer": "polymer", |
| "iron-a11y-keys-behavior": "polymer", |
| "iron-autogrow-textarea": "polymer", |
| "iron-behaviors": "polymer", |
| "iron-dropdown": "polymer", |
| "iron-fit-behavior": "polymer", |
| "iron-flex-layout": "polymer", |
| "iron-form-element-behavior": "polymer", |
| "iron-input": "polymer", |
| "iron-meta": "polymer", |
| "iron-overlay-behavior": "polymer", |
| "iron-resizable-behavior": "polymer", |
| "iron-selector": "polymer", |
| "iron-validatable-behavior": "polymer", |
| "moment": "moment", |
| "neon-animation": "polymer", |
| "page": "page.js", |
| "polymer": "polymer", |
| "promise-polyfill": "promise-polyfill", |
| "web-animations-js": "Apache2.0", |
| "webcomponentsjs": "polymer", |
| } |
| |
| |
| def build_bower_json(version_targets, seeds): |
| """Generate bower JSON file, return its path. |
| |
| Args: |
| version_targets: bazel target names of the versions.json file. |
| seeds: an iterable of bower package names of the seed packages, ie. |
| the packages whose versions we control manually. |
| """ |
| bower_json = collections.OrderedDict() |
| bower_json['name'] = 'bower2bazel-output' |
| bower_json['version'] = '0.0.0' |
| bower_json['description'] = 'Auto-generated bower.json for dependency management' |
| bower_json['private'] = True |
| bower_json['dependencies'] = {} |
| |
| seeds = set(seeds) |
| for v in version_targets: |
| path = os.path.join("bazel-out/*-fastbuild/bin", v.lstrip("/").replace(":", "/")) |
| fs = glob.glob(path) |
| assert len(fs) == 1, '%s: file not found or multiple files found: %s' % (path, fs) |
| with open(fs[0]) as f: |
| j = json.load(f) |
| if "" in j: |
| # drop dummy entries. |
| del j[""] |
| |
| trimmed = {} |
| for k, v in j.items(): |
| if k in seeds: |
| trimmed[k] = v |
| |
| bower_json['dependencies'].update(trimmed) |
| |
| tmpdir = tempfile.mkdtemp() |
| ret = os.path.join(tmpdir, 'bower.json') |
| with open(ret, 'w') as f: |
| json.dump(bower_json, f, indent=2) |
| return ret |
| |
| |
| def bower_command(args): |
| base = subprocess.check_output(["bazel", "info", "output_base"]).strip() |
| exp = os.path.join(base, "external", "bower", "*npm_binary.tgz") |
| fs = sorted(glob.glob(exp)) |
| assert len(fs) == 1, "bower tarball not found or have multiple versions %s" % fs |
| return ["python", os.getcwd() + "/tools/js/run_npm_binary.py", sorted(fs)[0]] + args |
| |
| |
| def main(args): |
| opts = optparse.OptionParser() |
| opts.add_option('-w', help='.bzl output for WORKSPACE') |
| opts.add_option('-b', help='.bzl output for //lib:BUILD') |
| opts, args = opts.parse_args() |
| |
| target_str = subprocess.check_output([ |
| "bazel", "query", "kind(bower_component_bundle, //polygerrit-ui/...)"]) |
| seed_str = subprocess.check_output([ |
| "bazel", "query", "attr(seed, 1, kind(bower_component, deps(//polygerrit-ui/...)))"]) |
| targets = [s for s in target_str.split('\n') if s] |
| seeds = [s for s in seed_str.split('\n') if s] |
| prefix = "//lib/js:" |
| non_seeds = [s for s in seeds if not s.startswith(prefix)] |
| assert not non_seeds, non_seeds |
| seeds = set([s[len(prefix):] for s in seeds]) |
| |
| version_targets = [t + "-versions.json" for t in targets] |
| subprocess.check_call(['bazel', 'build'] + version_targets) |
| bower_json_path = build_bower_json(version_targets, seeds) |
| dir = os.path.dirname(bower_json_path) |
| cmd = bower_command(["install"]) |
| |
| build_out = sys.stdout |
| if opts.b: |
| build_out = open(opts.b + ".tmp", 'w') |
| |
| ws_out = sys.stdout |
| if opts.b: |
| ws_out = open(opts.w + ".tmp", 'w') |
| |
| header = """# DO NOT EDIT |
| # generated with the following command: |
| # |
| # %s |
| # |
| |
| """ % ' '.join(sys.argv) |
| |
| ws_out.write(header) |
| build_out.write(header) |
| |
| oldwd = os.getcwd() |
| os.chdir(dir) |
| subprocess.check_call(cmd) |
| |
| interpret_bower_json(seeds, ws_out, build_out) |
| ws_out.close() |
| build_out.close() |
| |
| os.chdir(oldwd) |
| os.rename(opts.w + ".tmp", opts.w) |
| os.rename(opts.b + ".tmp", opts.b) |
| |
| |
| def dump_workspace(data, seeds, out): |
| out.write('load("//tools/bzl:js.bzl", "bower_archive")\n\n') |
| out.write('def load_bower_archives():\n') |
| |
| for d in data: |
| if d["name"] in seeds: |
| continue |
| out.write(""" bower_archive( |
| name = "%(name)s", |
| package = "%(normalized-name)s", |
| version = "%(version)s", |
| sha1 = "%(bazel-sha1)s") |
| """ % d) |
| |
| |
| def dump_build(data, seeds, out): |
| out.write('load("//tools/bzl:js.bzl", "bower_component")\n\n') |
| out.write('def define_bower_components():\n') |
| for d in data: |
| out.write(" bower_component(\n") |
| out.write(" name = \"%s\",\n" % d["name"]) |
| out.write(" license = \"//lib:LICENSE-%s\",\n" % d["bazel-license"]) |
| deps = sorted(d.get("dependencies", {}).keys()) |
| if deps: |
| if len(deps) == 1: |
| out.write(" deps = [ \":%s\" ],\n" % deps[0]) |
| else: |
| out.write(" deps = [\n") |
| for dep in deps: |
| out.write(" \":%s\",\n" % dep) |
| out.write(" ],\n") |
| if d["name"] in seeds: |
| out.write(" seed = True,\n") |
| out.write(" )\n") |
| # done |
| |
| |
| def interpret_bower_json(seeds, ws_out, build_out): |
| out = subprocess.check_output(["find", "bower_components/", "-name", ".bower.json"]) |
| |
| data = [] |
| for f in sorted(out.split('\n')): |
| if not f: |
| continue |
| pkg = json.load(open(f)) |
| pkg_name = pkg["name"] |
| |
| pkg["bazel-sha1"] = bowerutil.hash_bower_component( |
| hashlib.sha1(), os.path.dirname(f)).hexdigest() |
| license = package_licenses.get(pkg_name, "DO_NOT_DISTRIBUTE") |
| |
| pkg["bazel-license"] = license |
| |
| # TODO(hanwen): bower packages can also have 'fully qualified' |
| # names, ("PolymerElements/iron-ajax") as well as short names |
| # ("iron-ajax"). It is possible for bower.json files to refer to |
| # long names as their dependencies. If any package does this, we |
| # will have to either 1) strip off the prefix (typically github |
| # user?), or 2) build a map of short name <=> fully qualified |
| # name. For now, we just ignore the problem. |
| pkg["normalized-name"] = pkg["name"] |
| data.append(pkg) |
| |
| dump_workspace(data, seeds, ws_out) |
| dump_build(data, seeds, build_out) |
| |
| |
| if __name__ == '__main__': |
| main(sys.argv[1:]) |