blob: 1da8d474e046d1c5b2ab205ff18e0726910d4743 [file] [log] [blame] [edit]
#!/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())