| #!/usr/bin/env python |
| # Copyright 2018 The Fuchsia Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import StringIO |
| import argparse |
| import json |
| import operator |
| import os |
| import sys |
| import tarfile |
| import time |
| import zipfile |
| |
| |
| def generate_script(images): |
| # The bootserver must be in there or we lose. |
| # TODO(mcgrathr): Multiple bootservers for different platforms |
| # and switch in the script. |
| [bootserver] = [image['path'] for image in images |
| if image['name'] == 'bootserver'] |
| script = '''\ |
| #!/bin/sh |
| dir="$(dirname "$0")" |
| set -x |
| ''' |
| switches = dict((switch, '"$dir/%s"' % image['path']) |
| for image in images if 'bootserver' in image |
| for switch in image['bootserver']) |
| primary = switches.get('') |
| if primary: |
| del switches[''] |
| cmd = ['exec', '"$dir/%s"' % bootserver] |
| for switch, path in sorted(switches.iteritems()): |
| cmd += [switch, path] |
| if primary: |
| cmd.append(primary) |
| cmd.append('"$@"') |
| script += ' '.join(cmd) + '\n' |
| return script |
| |
| |
| def tgz_archiver(outfile): |
| with tarfile.open(outfile, 'w:gz', dereference=True) as archive: |
| while True: |
| contents, name, executable = (yield) |
| def sanitize_tarinfo(info): |
| assert info.isfile() |
| info.mode = 0o555 if executable else 0o444 |
| info.uid = 0 |
| info.gid = 0 |
| info.uname = '' |
| info.gname = '' |
| return info |
| if type(contents) in (str, unicode): |
| archive.add(contents, name, filter=sanitize_tarinfo) |
| else: |
| info = sanitize_tarinfo(tarfile.TarInfo(name)) |
| contents = contents() |
| info.size = len(contents) |
| info.mtime = time.time() |
| archive.addfile(info, StringIO.StringIO(contents)) |
| |
| |
| def zip_archiver(outfile): |
| with zipfile.ZipFile(outfile, 'w', zipfile.ZIP_DEFLATED) as archive: |
| archive.comment = 'Fuchsia build archive' |
| while True: |
| contents, name, executable = (yield) |
| if type(contents) in (str, unicode): |
| archive.write(contents, name) |
| else: |
| archive.writestr(name, contents()) |
| |
| |
| def write_archive(outfile, format, images): |
| # Synthesize a sanitized form of the input. |
| packed_images = [] |
| for image in images: |
| path = image['path'] |
| if 'archive' in image: |
| del image['archive'] |
| image['path'] = image['name'] + '.' + image['type'] |
| packed_images.append((path, image)) |
| |
| # Generate a script that uses the sanitized file names. |
| def generate_archive_script(): |
| return generate_script([image for path, image in packed_images]) |
| packed_images.append((generate_archive_script, { |
| 'name': 'pave', |
| 'type': 'sh', |
| 'path': 'pave.sh' |
| })) |
| |
| # Self-reference. |
| packed_images.append( |
| (lambda: json.dumps([image for contents, image in packed_images], |
| indent=2, sort_keys=True), |
| { |
| 'name': 'images', |
| 'type': 'json', |
| 'path': 'images.json', |
| })) |
| |
| # Canonicalize the order of the files in the archive. |
| packed_images = sorted(packed_images, key=lambda packed: packed[1]['path']) |
| |
| # The archiver is a generator that we send the details about each image. |
| archiver = {'tgz': tgz_archiver, 'zip': zip_archiver}[format](outfile) |
| archiver.next() |
| for contents, image in packed_images: |
| executable = image['type'] == 'sh' or image['type'].startswith('exe') |
| archiver.send((contents, image['path'], executable)) |
| archiver.close() |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser(description='Pack Fuchsia build images.') |
| parser.add_argument('--depfile', |
| metavar='FILE', |
| help='Write Ninja dependencies file') |
| parser.add_argument('json', nargs='+', |
| metavar='FILE', |
| help='Read JSON image list from FILE') |
| parser.add_argument('--pave', |
| metavar='FILE', |
| help='Write paving bootserver script to FILE') |
| parser.add_argument('--archive', |
| metavar='FILE', |
| help='Write archive to FILE') |
| parser.add_argument('--format', choices=['tgz', 'zip'], |
| help='Archive format (default: from FILE suffix)') |
| args = parser.parse_args() |
| |
| if args.archive and not args.format: |
| if args.archive.endswith('.zip'): |
| args.format = 'zip' |
| elif args.archive.endswith('.tgz') or args.archive.endswith('.tar.gz'): |
| args.format = 'tgz' |
| else: |
| sys.stderr.write('''\ |
| Cannot guess archive format from file name %r; use --format. |
| ''') |
| sys.exit(1) |
| |
| # Keep track of every input file for the depfile. |
| files_read = set() |
| def read_json_file(filename): |
| files_read.add(filename) |
| with open(filename, 'r') as f: |
| return json.load(f) |
| |
| images = reduce(operator.add, |
| (read_json_file(file) for file in args.json), |
| []) |
| |
| outfile = None |
| |
| # First write the local script that works relative to the build directory. |
| if args.pave: |
| outfile = args.pave |
| with os.fdopen(os.open(outfile, os.O_CREAT | os.O_WRONLY, 0o777), |
| 'w') as script_file: |
| script_file.write(generate_script(images)) |
| |
| if args.archive: |
| outfile = args.archive |
| archive_images = [image for image in images |
| if (image.get('archive', False) or |
| 'bootserver' in image)] |
| files_read |= set(image['path'] for image in archive_images) |
| write_archive(outfile, args.format, archive_images) |
| |
| if outfile and args.depfile: |
| with open(args.depfile, 'w') as depfile: |
| depfile.write('%s: %s\n' % (outfile, ' '.join(sorted(files_read)))) |
| |
| |
| if __name__ == "__main__": |
| main() |