From 7113b19be803d54abca058b9d9384bee0625b206 Mon Sep 17 00:00:00 2001 From: Jooyung Han Date: Tue, 20 Sep 2022 17:00:27 +0900 Subject: [PATCH] Add a host tool to create an APEX bundle Even though the soong build system generates a bundle module for an apex, the bundle module itself should be further processed to be an AppBundle (.aab) file which can be uploaded to Play. This script fills the gap by invoking bundletool to create an AppBundle (.aab) file out of soong-built bundle module for an apex. (Note: uploading APEX bundle (.aab) to Play is not supported yet.) You can create an .aab file by: - TARGET_BUILD_APPS={apex name} m dist - m build-apex-bundle - build-apex-bundle --output out.aab out/dist/{apex name}-base.zip For now it creates a single-ABI APEX bundle. In the future it can be extended to support multiple-ABI APEX bundles. Bug: 236673372 Test: m build-apex-bundle Test: TARGET_BUILD_APPS=com.google.cf.bt m dist Test: build-apex-bundle --output bt.aab out/dist/com.google.cf.bt-base.zip Change-Id: Id321efcd42c0fe60294a8348047c9ebbf7acf391 --- scripts/Android.bp | 11 ++++ scripts/build-apex-bundle.py | 114 +++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 scripts/build-apex-bundle.py diff --git a/scripts/Android.bp b/scripts/Android.bp index b5b588b8d..5dd45cd50 100644 --- a/scripts/Android.bp +++ b/scripts/Android.bp @@ -199,6 +199,17 @@ python_binary_host { ], } +python_binary_host { + name: "build-apex-bundle", + main: "build-apex-bundle.py", + srcs: [ + "build-apex-bundle.py", + ], + required: [ + "bundletool", + ], +} + sh_binary_host { name: "list_image", src: "list_image.sh", diff --git a/scripts/build-apex-bundle.py b/scripts/build-apex-bundle.py new file mode 100644 index 000000000..dcdd9ef7d --- /dev/null +++ b/scripts/build-apex-bundle.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python +# +# Copyright (C) 2022 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. +# +"""A tool to create an APEX bundle out of Soong-built base.zip""" + +from __future__ import print_function + +import argparse +import sys +import tempfile +import zipfile +import os +import json +import subprocess + + +def parse_args(): + """Parse commandline arguments.""" + parser = argparse.ArgumentParser() + parser.add_argument( + '--overwrite', + action='store_true', + help='If set, any previous existing output will be overwritten') + parser.add_argument('--output', help='specify the output .aab file') + parser.add_argument( + 'input', help='specify the input -base.zip file') + return parser.parse_args() + + +def build_bundle(input, output, overwrite): + base_zip = zipfile.ZipFile(input) + + tmpdir = tempfile.mkdtemp() + tmp_base_zip = os.path.join(tmpdir, 'base.zip') + tmp_bundle_config = os.path.join(tmpdir, 'bundle_config.json') + + bundle_config = None + abi = [] + + # This block performs three tasks + # - extract/load bundle_config.json from input => bundle_config + # - get ABI from input => abi + # - discard bundle_config.json from input => tmp/base.zip + with zipfile.ZipFile(tmp_base_zip, 'a') as out: + for info in base_zip.infolist(): + + # discard bundle_config.json + if info.filename == 'bundle_config.json': + bundle_config = json.load(base_zip.open(info.filename)) + continue + + # get ABI from apex/{abi}.img + dir, basename = os.path.split(info.filename) + name, ext = os.path.splitext(basename) + if dir == 'apex' and ext == '.img': + abi.append(name) + + # copy entries to tmp/base.zip + out.writestr(info, base_zip.open(info.filename).read()) + + base_zip.close() + + if not bundle_config: + raise ValueError(f'bundle_config.json not found in {input}') + if len(abi) != 1: + raise ValueError(f'{input} should have only a single apex/*.img file') + + # add ABI to tmp/bundle_config.json + apex_config = bundle_config['apex_config'] + if 'supported_abi_set' not in apex_config: + apex_config['supported_abi_set'] = [] + supported_abi_set = apex_config['supported_abi_set'] + supported_abi_set.append({'abi': abi}) + + with open(tmp_bundle_config, 'w') as out: + json.dump(bundle_config, out) + + # invoke bundletool + cmd = [ + 'bundletool', 'build-bundle', '--config', tmp_bundle_config, '--modules', + tmp_base_zip, '--output', output + ] + if overwrite: + cmd.append('--overwrite') + subprocess.check_call(cmd) + + +def main(): + """Program entry point.""" + try: + args = parse_args() + build_bundle(args.input, args.output, args.overwrite) + + # pylint: disable=broad-except + except Exception as err: + print('error: ' + str(err), file=sys.stderr) + sys.exit(-1) + + +if __name__ == '__main__': + main()