blob: e794cc0be4543f2a3f4e32d5a4449b1608892673 [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 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:]))