| #!/usr/bin/python |
| # Copyright (C) 2013 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 |
| |
| from hashlib import sha1 |
| from optparse import OptionParser |
| from os import link, makedirs, path, remove |
| import shutil |
| from subprocess import check_call, CalledProcessError |
| from sys import stderr |
| from zipfile import ZipFile, BadZipfile, LargeZipFile |
| |
| REPO_ROOTS = { |
| 'GERRIT': 'https://gerrit-api.commondatastorage.googleapis.com/release', |
| 'ECLIPSE': 'https://repo.eclipse.org/content/groups/releases', |
| 'MAVEN_CENTRAL': 'http://repo1.maven.org/maven2', |
| 'MAVEN_LOCAL': 'file://' + path.expanduser('~/.m2/repository'), |
| } |
| |
| GERRIT_HOME = path.expanduser('~/.gerritcodereview') |
| CACHE_DIR = path.join(GERRIT_HOME, 'buck-cache') |
| LOCAL_PROPERTIES = 'local.properties' |
| |
| |
| def hashfile(p): |
| d = sha1() |
| with open(p, 'rb') as f: |
| while True: |
| b = f.read(8192) |
| if not b: |
| break |
| d.update(b) |
| return d.hexdigest() |
| |
| def safe_mkdirs(d): |
| if path.isdir(d): |
| return |
| try: |
| makedirs(d) |
| except OSError as err: |
| if not path.isdir(d): |
| raise err |
| |
| def download_properties(root_dir): |
| """ Get the download properties. |
| |
| First tries to find the properties file in the given root directory, |
| and if not found there, tries in the Gerrit settings folder in the |
| user's home directory. |
| |
| Returns a set of download properties, which may be empty. |
| |
| """ |
| p = {} |
| local_prop = path.join(root_dir, LOCAL_PROPERTIES) |
| if not path.isfile(local_prop): |
| local_prop = path.join(GERRIT_HOME, LOCAL_PROPERTIES) |
| if path.isfile(local_prop): |
| try: |
| with open(local_prop) as fd: |
| for line in fd: |
| if line.startswith('download.'): |
| d = [e.strip() for e in line.split('=', 1)] |
| name, url = d[0], d[1] |
| p[name[len('download.'):]] = url |
| except OSError: |
| pass |
| return p |
| |
| def cache_entry(args): |
| if args.v: |
| h = args.v |
| else: |
| h = sha1(args.u).hexdigest() |
| name = '%s-%s' % (path.basename(args.o), h) |
| return path.join(CACHE_DIR, name) |
| |
| def resolve_url(url, redirects): |
| s = url.find(':') |
| if s < 0: |
| return url |
| scheme, rest = url[:s], url[s+1:] |
| if scheme not in REPO_ROOTS: |
| return url |
| if scheme in redirects: |
| root = redirects[scheme] |
| else: |
| root = REPO_ROOTS[scheme] |
| root = root.rstrip('/') |
| rest = rest.lstrip('/') |
| return '/'.join([root, rest]) |
| |
| opts = OptionParser() |
| opts.add_option('-o', help='local output file') |
| opts.add_option('-u', help='URL to download') |
| opts.add_option('-v', help='expected content SHA-1') |
| opts.add_option('-x', action='append', help='file to delete from ZIP') |
| opts.add_option('--exclude_java_sources', action='store_true') |
| opts.add_option('--unsign', action='store_true') |
| args, _ = opts.parse_args() |
| |
| root_dir = args.o |
| while root_dir: |
| root_dir, n = path.split(root_dir) |
| if n == 'buck-out': |
| break |
| |
| redirects = download_properties(root_dir) |
| cache_ent = cache_entry(args) |
| src_url = resolve_url(args.u, redirects) |
| |
| if not path.exists(cache_ent): |
| try: |
| safe_mkdirs(path.dirname(cache_ent)) |
| except OSError as err: |
| print('error creating directory %s: %s' % |
| (path.dirname(cache_ent), err), file=stderr) |
| exit(1) |
| |
| print('Download %s' % src_url, file=stderr) |
| try: |
| check_call(['curl', '--proxy-anyauth', '-sfo', cache_ent, src_url]) |
| except OSError as err: |
| print('could not invoke curl: %s\nis curl installed?' % err, file=stderr) |
| exit(1) |
| except CalledProcessError as err: |
| print('error using curl: %s' % err, file=stderr) |
| exit(1) |
| |
| if args.v: |
| have = hashfile(cache_ent) |
| if args.v != have: |
| print(( |
| '%s:\n' + |
| 'expected %s\n' + |
| 'received %s\n') % (src_url, args.v, have), file=stderr) |
| try: |
| remove(cache_ent) |
| except OSError as err: |
| if path.exists(cache_ent): |
| print('error removing %s: %s' % (cache_ent, err), file=stderr) |
| exit(1) |
| |
| exclude = [] |
| if args.x: |
| exclude += args.x |
| if args.exclude_java_sources: |
| try: |
| zf = ZipFile(cache_ent, 'r') |
| try: |
| for n in zf.namelist(): |
| if n.endswith('.java'): |
| exclude.append(n) |
| finally: |
| zf.close() |
| except (BadZipfile, LargeZipFile) as err: |
| print('error opening %s: %s' % (cache_ent, err), file=stderr) |
| exit(1) |
| |
| if args.unsign: |
| try: |
| zf = ZipFile(cache_ent, 'r') |
| try: |
| for n in zf.namelist(): |
| if (n.endswith('.RSA') |
| or n.endswith('.SF') |
| or n.endswith('.LIST')): |
| exclude.append(n) |
| finally: |
| zf.close() |
| except (BadZipfile, LargeZipFile) as err: |
| print('error opening %s: %s' % (cache_ent, err), file=stderr) |
| exit(1) |
| |
| safe_mkdirs(path.dirname(args.o)) |
| if exclude: |
| try: |
| shutil.copyfile(cache_ent, args.o) |
| except (shutil.Error, IOError) as err: |
| print('error copying to %s: %s' % (args.o, err), file=stderr) |
| exit(1) |
| try: |
| check_call(['zip', '-d', args.o] + exclude) |
| except CalledProcessError as err: |
| print('error removing files from zip: %s' % err, file=stderr) |
| exit(1) |
| else: |
| try: |
| link(cache_ent, args.o) |
| except OSError as err: |
| try: |
| shutil.copyfile(cache_ent, args.o) |
| except (shutil.Error, IOError) as err: |
| print('error copying to %s: %s' % (args.o, err), file=stderr) |
| exit(1) |