| #!/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 hashlib |
| import json |
| import optparse |
| import os |
| import shutil |
| import subprocess |
| import sys |
| |
| CACHE_DIR = os.path.expanduser(os.path.join( |
| '~', '.gerritcodereview', 'buck-cache', 'downloaded-artifacts')) |
| |
| |
| def hash_file(h, p): |
| with open(p, 'rb') as f: |
| while True: |
| b = f.read(8192) |
| if not b: |
| break |
| h.update(p) |
| |
| |
| def hash_dir(dir): |
| # It's hard to get zipfiles to hash deterministically. Instead, do a sorted |
| # walk and hash filenames and contents together. |
| h = hashlib.sha1() |
| |
| for root, dirs, files in os.walk(dir): |
| dirs.sort() |
| for f in sorted(files): |
| p = os.path.join(root, f) |
| h.update(p) |
| hash_file(h, p) |
| |
| return h.hexdigest() |
| |
| |
| def bower_cmd(bower, *args): |
| cmd = bower.split(' ') |
| cmd.extend(args) |
| return cmd |
| |
| |
| def bower_info(bower, name, package, version): |
| cmd = bower_cmd(bower, '-l=error', '-j', |
| 'info', '%s#%s' % (package, version)) |
| p = subprocess.Popen(cmd , stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
| out, err = p.communicate() |
| if p.returncode: |
| sys.stderr.write(err) |
| raise OSError('Command failed: %s' % cmd) |
| |
| try: |
| info = json.loads(out) |
| except ValueError: |
| raise ValueError('invalid JSON from %s:\n%s' % (cmd, out)) |
| info_name = info.get('name') |
| if info_name != name: |
| raise ValueError('expected package name %s, got: %s' % (name, info_name)) |
| return info |
| |
| |
| def ignore_deps(info): |
| # Tell bower to ignore dependencies so we just download this component. This |
| # is just an optimization, since we only pick out the component we need, but |
| # it's important when downloading sizable dependency trees. |
| # |
| # As of 1.6.5 I don't think ignoredDependencies can be specified on the |
| # command line with --config, so we have to create .bowerrc. |
| deps = info.get('dependencies') |
| if deps: |
| with open(os.path.join('.bowerrc'), 'w') as f: |
| json.dump({'ignoredDependencies': deps.keys()}, f) |
| |
| |
| def cache_entry(name, version, sha1): |
| c = os.path.join(CACHE_DIR, '%s-%s.zip' % (name, version)) |
| if sha1: |
| c += '-%s' % sha1 |
| return c |
| |
| |
| def main(args): |
| opts = optparse.OptionParser() |
| opts.add_option('-n', help='short name of component') |
| opts.add_option('-b', help='bower command') |
| opts.add_option('-p', help='full package name of component') |
| opts.add_option('-v', help='version number') |
| opts.add_option('-s', help='expected content sha1') |
| opts.add_option('-o', help='output file location') |
| opts, _ = opts.parse_args() |
| |
| outzip = os.path.join(os.getcwd(), opts.o) |
| # TODO(dborowitz): match download_file behavior of pulling any old file from |
| # the cache if there is no -s |
| # Also don't double-append sha1. |
| cached = cache_entry(opts.n, opts.v, opts.s) |
| if os.path.isfile(cached): |
| shutil.copyfile(cached, outzip) |
| return 0 |
| |
| info = bower_info(opts.b, opts.n, opts.p, opts.v) |
| ignore_deps(info) |
| subprocess.check_call( |
| bower_cmd(opts.b, '--quiet', 'install', '%s#%s' % (opts.p, opts.v))) |
| name = info['name'] |
| sha1 = hash_dir(name) |
| |
| if opts.s and sha1 != opts.s: |
| print(( |
| '%s#%s:\n' |
| 'expected %s\n' |
| 'received %s\n') % (opts.p, opts.v, opts.s, sha1), file=sys.stderr) |
| return 1 |
| |
| os.chdir('bower_components') |
| cmd = ['zip', '-q', '-r', outzip, opts.n] |
| subprocess.check_call(cmd) |
| shutil.copyfile(outzip, cache_entry(opts.n, opts.v, sha1)) |
| return 0 |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main(sys.argv[1:])) |