blob: 81072da787bf15524748ce4ccdfc0798c77df587 [file] [log] [blame]
#!/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
import collections
import json
import hashlib
import optparse
import os
import shutil
import subprocess
import sys
import tempfile
from tools import util
# This script is run with `buck run`, but needs to shell out to buck; this is
# only possible if we avoid buckd.
BUCK_ENV = dict(os.environ)
BUCK_ENV['NO_BUCKD'] = '1'
HEADER = """\
include_defs('//lib/js.defs')
# AUTOGENERATED BY BOWER2BUCK
#
# This file should be merged with an existing BUCK file containing these rules.
#
# This comment SHOULD NOT be copied to the existing BUCK file, and you should
# leave alone any non-bower_component contents of the file.
#
# Generally, the following attributes SHOULD be copied from this file to the
# existing BUCK file:
# - package: the normalized package name
# - version: the exact version number
# - deps: direct dependencies of the package
# - sha1: a hash of the package contents
#
# The following fields SHOULD NOT be copied to the existing BUCK file:
# - semver: manually-specified semantic version, not included in autogenerated
# output.
#
# The following fields require SPECIAL HANDLING:
# - license: all licenses in this file are specified as TODO. You must replace
# this text with one of the existing licenses defined in lib/BUCK, or
# define a new one if necessary. Leave existing licenses alone.
"""
def usage():
print(('Usage: %s -o <outfile> [//path/to:bower_components_rule...]'
% sys.argv[0]),
file=sys.stderr)
return 1
class Rule(object):
def __init__(self, bower_json_path):
with open(bower_json_path) as f:
bower_json = json.load(f)
self.name = bower_json['name']
self.version = bower_json['version']
self.deps = bower_json.get('dependencies', {})
self.license = bower_json.get('license', 'NO LICENSE')
self.sha1 = util.hash_bower_component(
hashlib.sha1(), os.path.dirname(bower_json_path)).hexdigest()
def to_rule(self, packages):
if self.name not in packages:
raise ValueError('No package name found for %s' % self.name)
lines = [
'bower_component(',
" name = '%s'," % self.name,
" package = '%s'," % packages[self.name],
" version = '%s'," % self.version,
]
if self.deps:
if len(self.deps) == 1:
lines.append(" deps = [':%s']," % next(self.deps.iterkeys()))
else:
lines.append(' deps = [')
lines.extend(" ':%s'," % d for d in sorted(self.deps.iterkeys()))
lines.append(' ],')
lines.extend([
" license = 'TODO: %s'," % self.license,
" sha1 = '%s'," % self.sha1,
')'])
return '\n'.join(lines)
def build_bower_json(targets, buck_out):
bower_json = collections.OrderedDict()
bower_json['name'] = 'bower2buck-output'
bower_json['version'] = '0.0.0'
bower_json['description'] = 'Auto-generated bower.json for dependency management'
bower_json['private'] = True
bower_json['dependencies'] = {}
deps = subprocess.check_output(
['buck', 'query', '-v', '0',
"filter('__download_bower', deps(%s))" % '+'.join(targets)],
env=BUCK_ENV)
deps = deps.replace('__download_bower', '__bower_version').split()
subprocess.check_call(['buck', 'build'] + deps, env=BUCK_ENV)
for dep in deps:
dep = dep.replace(':', '/').lstrip('/')
depout = os.path.basename(dep)
version_json = os.path.join(buck_out, 'gen', dep, depout)
with open(version_json) as f:
bower_json['dependencies'].update(json.load(f))
tmpdir = tempfile.mkdtemp()
atexit.register(lambda: shutil.rmtree(tmpdir))
ret = os.path.join(tmpdir, 'bower.json')
with open(ret, 'w') as f:
json.dump(bower_json, f, indent=2)
return ret
def get_package_name(name, package_version):
v = package_version.lower()
if '#' in v:
return v[:v.find('#')]
return name
def get_packages(path):
with open(path) as f:
bower_json = json.load(f)
return dict((n, get_package_name(n, v))
for n, v in bower_json.get('dependencies', {}).iteritems())
def collect_rules(packages):
# TODO(dborowitz): Use run_npm_binary instead of system bower.
rules = {}
subprocess.check_call(['bower', 'install'])
for dirpath, dirnames, filenames in os.walk('.', topdown=True):
if '.bower.json' not in filenames:
continue
del dirnames[:]
rule = Rule(os.path.join(dirpath, '.bower.json'))
rules[rule.name] = rule
# Oddly, the package name referred to in the deps section of dependents,
# e.g. 'PolymerElements/iron-ajax', is not found anywhere in this
# bower.json, which only contains 'iron-ajax'. Build up a map of short name
# to package name so we can resolve them later.
# TODO(dborowitz): We can do better:
# - Infer 'user/package' from GitHub URLs (i.e. a simple subset of Bower's package
# resolution logic).
# - Resolve aliases using https://bower.herokuapp.com/packages/shortname
# (not currently biting us but it might in the future.)
for n, v in rule.deps.iteritems():
p = get_package_name(n, v)
old = packages.get(n)
if old is not None and old != p:
raise ValueError('multiple packages named %s: %s != %s' % (n, p, old))
packages[n] = p
return rules
def find_buck_out():
dir = os.getcwd()
while not os.path.isfile(os.path.join(dir, '.buckconfig')):
dir = os.path.dirname(dir)
return os.path.join(dir, 'buck-out')
def main(args):
opts = optparse.OptionParser()
opts.add_option('-o', help='output file location')
opts, args = opts.parse_args()
if not opts.o or not all(a.startswith('//') for a in args):
return usage()
outfile = os.path.abspath(opts.o)
buck_out = find_buck_out()
targets = args if args else ['//polygerrit-ui/...']
bower_json_path = build_bower_json(targets, buck_out)
os.chdir(os.path.dirname(bower_json_path))
packages = get_packages(bower_json_path)
rules = collect_rules(packages)
with open(outfile, 'w') as f:
f.write(HEADER)
for _, r in sorted(rules.iteritems()):
f.write('\n\n%s' % r.to_rule(packages))
print('Wrote bower_components rules to:\n %s' % outfile)
if __name__ == '__main__':
main(sys.argv[1:])