| # Copyright 2013 The Flutter 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 os | |
| import subprocess | |
| import sys | |
| import zipfile | |
| def run_command_checked(command, env=None): | |
| try: | |
| env = env if env is not None else os.environ | |
| subprocess.check_output(command, stderr=subprocess.STDOUT, text=True, env=env) | |
| except subprocess.CalledProcessError as cpe: | |
| print(cpe.output) | |
| raise cpe | |
| def is_mac(): | |
| return sys.platform == 'darwin' | |
| def java_home(): | |
| script_path = os.path.dirname(os.path.realpath(__file__)) | |
| if is_mac(): | |
| return os.path.join( | |
| script_path, '..', '..', '..', 'third_party', 'java', 'openjdk', 'Contents', 'Home' | |
| ) | |
| return os.path.join(script_path, '..', 'third_party', 'java', 'openjdk') | |
| def java_bin(): | |
| return os.path.join(java_home(), 'bin') | |
| def main(): | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument('--aapt2-bin', type=str, required=True, help='The path to the aapt2 binary.') | |
| parser.add_argument( | |
| '--zipalign-bin', type=str, required=True, help='The path to the zipalign binary.' | |
| ) | |
| parser.add_argument( | |
| '--apksigner-bin', type=str, required=True, help='The path to the apksigner binary.' | |
| ) | |
| parser.add_argument( | |
| '--android-manifest', type=str, required=True, help='The path to the AndroidManifest.xml.' | |
| ) | |
| parser.add_argument('--android-jar', type=str, required=True, help='The path to android.jar.') | |
| parser.add_argument('--output-path', type=str, required=True, help='The path to the output apk.') | |
| parser.add_argument( | |
| '--library', type=str, required=True, help='The path to the library to put in the apk.' | |
| ) | |
| parser.add_argument( | |
| '--keystore', type=str, required=True, help='The path to the debug keystore to sign the apk.' | |
| ) | |
| parser.add_argument( | |
| '--gen-dir', type=str, required=True, help='The directory for generated files.' | |
| ) | |
| parser.add_argument( | |
| '--android-abi', type=str, required=True, help='The android ABI of the library.' | |
| ) | |
| args = parser.parse_args() | |
| library_file = os.path.basename(args.library) | |
| apk_name = os.path.basename(args.output_path) | |
| unaligned_apk_path = os.path.join(args.gen_dir, '%s.unaligned' % apk_name) | |
| unsigned_apk_path = os.path.join(args.gen_dir, '%s.unsigned' % apk_name) | |
| apk_path = args.output_path | |
| java_path = ':'.join([java_bin(), os.environ['PATH']]) | |
| env = dict(os.environ, PATH=java_path, JAVA_HOME=java_home()) | |
| # Create the skeleton of the APK using aapt2. | |
| aapt2_command = [ | |
| args.aapt2_bin, | |
| 'link', | |
| '-I', | |
| args.android_jar, | |
| '--manifest', | |
| args.android_manifest, | |
| '-o', | |
| unaligned_apk_path, | |
| ] | |
| run_command_checked(aapt2_command, env=env) | |
| # Stuff the library in the APK which is just a regular ZIP file. Libraries are not compressed. | |
| with zipfile.ZipFile(unaligned_apk_path, 'a', compression=zipfile.ZIP_STORED) as zipf: | |
| zipf.write(args.library, 'lib/%s/%s' % (args.android_abi, library_file)) | |
| # Align the dylib to a page boundary. | |
| zipalign_command = [ | |
| args.zipalign_bin, | |
| '-p', # Page align the dylib | |
| '-f', # overwrite output if exists | |
| '4', # 32-bit alignment | |
| unaligned_apk_path, | |
| unsigned_apk_path, | |
| ] | |
| run_command_checked(zipalign_command, env=env) | |
| # Sign the APK. | |
| apksigner_command = [ | |
| args.apksigner_bin, 'sign', '--ks', args.keystore, '--ks-pass', 'pass:android', '--out', | |
| apk_path, unsigned_apk_path | |
| ] | |
| run_command_checked(apksigner_command, env=env) | |
| # Remove the intermediates so the out directory isn't full of large files. | |
| os.remove(unaligned_apk_path) | |
| os.remove(unsigned_apk_path) | |
| return 0 | |
| if __name__ == '__main__': | |
| sys.exit(main()) |