| #!/usr/bin/env python3.8 |
| # Copyright 2020 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 argparse |
| import json |
| import os |
| import re |
| import shutil |
| import sys |
| import tarfile |
| |
| |
| class GatherPackageDeps: |
| """Helper class to take a `package_manifest.json` and copy all files referenced |
| into an archive that will then be available at runtime. |
| |
| Args: |
| package_json_path (string): An absolute path to the package's `package_manifest.json` file. |
| meta_far_path (string): An absolute path to the package's `meta.far` file. |
| output_dir (string): The absolute path to the directory that this should output into. |
| |
| Raises: ValueError if any parameter is empty. |
| """ |
| |
| # Selects everything that comes after '/' and/or any number of '../'. |
| path_stripper = re.compile(r'(?:(?:\.\.\/)+)?\/?(.+)') |
| |
| def __init__(self, package_json_path, meta_far_path, output_dir): |
| if package_json_path and os.path.exists(package_json_path): |
| self.package_json_path = package_json_path |
| else: |
| raise ValueError('package_json_path must be to a valid file') |
| |
| if meta_far_path and os.path.exists(meta_far_path): |
| self.meta_far_path = meta_far_path |
| else: |
| raise ValueError('meta_far_path must be to a valid file') |
| |
| if output_dir: |
| self.output_dir = output_dir |
| else: |
| raise ValueError('output_dir cannot be empty') |
| |
| def parse_package_json(self): |
| manifest_dict = {} |
| with open(self.package_json_path) as f: |
| data = json.load(f) |
| for file in data['blobs']: |
| if file['path'].startswith('meta/'): |
| continue |
| manifest_dict[file['path']] = file['source_path'] |
| return manifest_dict |
| |
| def copy_meta_far(self): |
| shutil.copyfile( |
| self.meta_far_path, os.path.join(self.output_dir, 'meta.far')) |
| |
| def copy_to_output_dir(self, manifest_dict): |
| for archive_path, source_path in manifest_dict.items(): |
| # Some `source_path`s are prefixed with a couple of `../`'s or are absolute paths. |
| # Examples: |
| # "../../prebuilt/third_party/clang/linux-x64/lib/aarch64-unknown-fuchsia/c++/libc++.so.2" |
| # "/root/to/fuchsia/out/core.x64-host_asan/obj/topaz/runtime/flutter_runner/flutter_aot_runner.meta/blobs/2ae9bee944d30eeec29608eb2f5e21df71f92bdb8f75f8c2ea1a2cd8d273915b" |
| # |
| # All of these files must end up in our `output_dir`, where `../`s are meaningless and |
| # wrong, and absolute paths are definitely wrong. All of these must be cleaned up into |
| # useable relative paths - relative to the `output_dir`. |
| # The above examples should become: |
| # "prebuilt/third_party/clang/linux-x64/lib/aarch64-unknown-fuchsia/c++/libc++.so.2" |
| # "root/to/fuchsia/out/core.x64-host_asan/obj/topaz/runtime/flutter_runner/flutter_aot_runner.meta/blobs/2ae9bee944d30eeec29608eb2f5e21df71f92bdb8f75f8c2ea1a2cd8d273915b" |
| # |
| # These are the paths as they will appear within the output `tar` file and will be how |
| # they are referenced within the manifest file. |
| m = self.path_stripper.match(source_path) |
| out_path = os.path.join(self.output_dir, m.group(1)) |
| os.makedirs(os.path.dirname(out_path), exist_ok=True) |
| shutil.copyfile(source_path, out_path) |
| manifest_dict[archive_path] = m.group(1) |
| |
| def write_new_manifest(self, manifest_dict): |
| with open(os.path.join(self.output_dir, 'package.manifest'), 'w') as f: |
| for archive_path, source_path in manifest_dict.items(): |
| f.write(archive_path + '=' + source_path + '\n') |
| f.write('meta/package=meta.far\n') |
| |
| def archive_output(self): |
| tar_path = os.path.join(self.output_dir, 'package.tar') |
| # Explicitly use the GNU_FORMAT because the current dart library |
| # (v.3.0.0) does not support parsing other tar formats that allow for |
| # filenames longer than 100 characters. |
| with tarfile.open(tar_path, 'w', format=tarfile.GNU_FORMAT) as tar: |
| for root, _, files in os.walk(self.output_dir): |
| for name in files: |
| relative_dir = os.path.relpath(root, self.output_dir) |
| input_path = os.path.join(root, name) |
| relative_path = os.path.join(relative_dir, name) |
| if input_path == tar_path: |
| continue |
| tar.add(input_path, arcname=relative_path) |
| |
| def run(self): |
| manifest_dict = self.parse_package_json() |
| self.copy_meta_far() |
| self.copy_to_output_dir(manifest_dict) |
| self.write_new_manifest(manifest_dict) |
| self.archive_output() |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser() |
| parser.add_argument( |
| '--package_json_path', |
| required=True, |
| help= |
| 'The path to the package_manifest.json generated by a `fuchsia_package`.' |
| ) |
| parser.add_argument( |
| '--meta_far_path', |
| required=True, |
| help='The path to the package\'s meta.far.') |
| parser.add_argument( |
| '--output_dir', |
| required=True, |
| help= |
| 'The path to where the new manifest and all required files will be copied to.' |
| ) |
| args = parser.parse_args() |
| |
| try: |
| gatherer = GatherPackageDeps( |
| args.package_json_path, args.meta_far_path, args.output_dir).run() |
| except Exception as e: |
| print('GatherPackageDeps errored during run: %s' % e) |
| return 1 |
| |
| return 0 |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |