| #!/usr/bin/env python3 |
| # Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file |
| # for details. All rights reserved. Use of this source code is governed by a |
| # BSD-style license that can be found in the LICENSE file. |
| |
| import optparse |
| import os |
| import shutil |
| import subprocess |
| import sys |
| import time |
| |
| import utils |
| import zip_utils |
| |
| USAGE = 'usage: %prog [options] <apk>' |
| |
| |
| def parse_options(): |
| parser = optparse.OptionParser(usage=USAGE) |
| parser.add_option('--keystore', |
| help='keystore file (default ~/.android/app.keystore)', |
| default='~/.android/app.keystore') |
| parser.add_option('--sign', |
| help='Sign the passed in apk.', |
| default=False, |
| action='store_true') |
| parser.add_option('--use_apksigner', |
| help='Use apksigner to sign.', |
| default=False, |
| action='store_true') |
| parser.add_option('--output', |
| help='Where to put the signed apk.)', |
| default=None) |
| |
| (options, args) = parser.parse_args() |
| if len(args) != 1: |
| parser.error('Expected <apk> argument, got: ' + ' '.join(args)) |
| apk = args[0] |
| return (options, apk) |
| |
| |
| def add_baseline_profile_to_apk(apk, baseline_profile, |
| baseline_profile_metadata, tmp_dir): |
| if baseline_profile is None: |
| return apk |
| ts = time.time_ns() |
| dest_apk = os.path.join(tmp_dir, 'app-%s.apk' % ts) |
| dest_apk_aligned = os.path.join(tmp_dir, 'app-aligned-%s.apk' % ts) |
| dest_apk_signed = os.path.join(tmp_dir, 'app-signed-%s.apk' % ts) |
| shutil.copy2(apk, dest_apk) |
| zip_utils.remove_files_from_zip( |
| ['assets/dexopt/baseline.prof', 'assets/dexopt/baseline.profm'], |
| dest_apk) |
| zip_utils.add_file_to_zip(baseline_profile, 'assets/dexopt/baseline.prof', |
| dest_apk) |
| if baseline_profile_metadata is not None: |
| zip_utils.add_file_to_zip(baseline_profile_metadata, |
| 'assets/dexopt/baseline.profm', dest_apk) |
| align(dest_apk, dest_apk_aligned) |
| sign_with_apksigner(dest_apk_aligned, dest_apk_signed) |
| return dest_apk_signed |
| |
| |
| def align(apk, aligned_apk): |
| zipalign_path = ('zipalign' if 'build_tools' in os.environ.get('PATH') else |
| os.path.join(utils.getAndroidBuildTools(), 'zipalign')) |
| cmd = [zipalign_path, '-f', '-p', '4', apk, aligned_apk] |
| utils.RunCmd(cmd, quiet=True, logging=False) |
| return aligned_apk |
| |
| |
| def default_keystore(): |
| return os.path.join(os.getenv('HOME'), '.android', 'app.keystore') |
| |
| |
| def get_min_api(apk): |
| aapt = os.path.join(utils.getAndroidBuildTools(), 'aapt') |
| cmd = [aapt, 'dump', 'badging', apk] |
| stdout = subprocess.check_output(cmd).decode('utf-8').strip() |
| for line in stdout.splitlines(): |
| if line.startswith('sdkVersion:\''): |
| return int(line[len('sdkVersion:\''):-1]) |
| raise ValueError('Unexpected stdout: %s' % stdout) |
| |
| |
| def sign(unsigned_apk, signed_apk, keystore, quiet=False, logging=True): |
| utils.Print('Signing (ignore the warnings)', quiet=quiet) |
| cmd = ['zip', '-d', unsigned_apk, 'META-INF/*'] |
| utils.RunCmd(cmd, quiet=quiet, logging=logging, fail=False) |
| cmd = [ |
| 'jarsigner', '-sigalg', 'SHA1withRSA', '-digestalg', 'SHA1', |
| '-keystore', keystore, '-storepass', 'android', '-signedjar', |
| signed_apk, unsigned_apk, 'androiddebugkey' |
| ] |
| utils.RunCmd(cmd, quiet=quiet) |
| |
| |
| def sign_with_apksigner(unsigned_apk, |
| signed_apk, |
| keystore=None, |
| password='android', |
| quiet=False, |
| logging=True): |
| cmd = [ |
| os.path.join(utils.getAndroidBuildTools(), 'apksigner'), 'sign', '-v', |
| '--ks', keystore or default_keystore(), '--ks-pass', 'pass:' + password, |
| '--min-sdk-version', '19', '--out', signed_apk, '--v2-signing-enabled', |
| unsigned_apk |
| ] |
| utils.RunCmd(cmd, quiet=quiet, logging=logging) |
| return signed_apk |
| |
| |
| def main(): |
| (options, apk) = parse_options() |
| if options.sign: |
| if not options.output: |
| print('When signing you must specify an output apk') |
| return 1 |
| if not options.keystore: |
| print('When signing you must specify a keystore') |
| return 1 |
| if options.use_apksigner: |
| sign_with_apksigner(apk, options.output, options.keystore) |
| else: |
| sign(apk, options.output, options.keystore) |
| return 0 |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |