Additionally fix a bug in bower2buck.py where it would bail when a license is not specified in a Bower package info response. [1] http://caniuse.com/#feat=fetch [2] http://github.github.io/fetch/ Change-Id: Ibf754aab98bab6e34b7a9fb5d6c9801bb612f5d5
		
			
				
	
	
		
			215 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			215 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
#!/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:])
 |