blob: b1ea43eca571c59bed0b7a96a4f0d6d466a35852 [file] [log] [blame]
Mads Ager418d1ca2017-05-22 09:35:49 +02001#!/usr/bin/env python
2# Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
3# for details. All rights reserved. Use of this source code is governed by a
4# BSD-style license that can be found in the LICENSE file.
5
Rico Wind0ed24cc2018-03-23 07:16:35 +01006import apk_utils
Mads Ager418d1ca2017-05-22 09:35:49 +02007import glob
8import optparse
9import os
10import shutil
Mads Ager418d1ca2017-05-22 09:35:49 +020011import sys
12import utils
13
14USAGE = 'usage: %prog [options] <apk>'
15
16def parse_options():
17 parser = optparse.OptionParser(usage=USAGE)
18 parser.add_option('--dex',
Christoffer Quist Adamsen2dcea102019-02-20 11:31:38 +010019 help=('directory with dex files to use instead of those in '
20 + 'the apk'),
21 default=None)
22 parser.add_option('--resources',
23 help=('pattern that matches resources to use instead of '
24 + 'those in the apk'),
Mads Ager418d1ca2017-05-22 09:35:49 +020025 default=None)
26 parser.add_option('--out',
27 help='output file (default ./$(basename <apk>))',
28 default=None)
29 parser.add_option('--keystore',
30 help='keystore file (default ~/.android/app.keystore)',
31 default=None)
Søren Gjessec4b3f612017-05-24 10:10:43 +020032 parser.add_option('--install',
33 help='install the generated apk with adb options -t -r -d',
34 default=False,
35 action='store_true')
Søren Gjesse12d41d42017-06-01 09:01:24 +020036 parser.add_option('--adb-options',
37 help='additional adb options when running adb',
38 default=None)
Christoffer Quist Adamsen1de3dde2019-01-24 13:17:46 +010039 parser.add_option('--quiet',
40 help='disable verbose logging',
41 default=False)
Mads Ager418d1ca2017-05-22 09:35:49 +020042 (options, args) = parser.parse_args()
43 if len(args) != 1:
44 parser.error('Expected <apk> argument, got: ' + ' '.join(args))
45 apk = args[0]
Mads Ager418d1ca2017-05-22 09:35:49 +020046 return (options, apk)
47
48def findKeystore():
49 return os.path.join(os.getenv('HOME'), '.android', 'app.keystore')
50
Christoffer Quist Adamsen41cbdca2019-04-12 08:52:03 +020051def repack(apk, processed_out, resources, temp, quiet, logging):
Mads Ager418d1ca2017-05-22 09:35:49 +020052 processed_apk = os.path.join(temp, 'processed.apk')
Christoffer Quist Adamsen2dcea102019-02-20 11:31:38 +010053 shutil.copyfile(apk, processed_apk)
Mads Ager418d1ca2017-05-22 09:35:49 +020054 if not processed_out:
Christoffer Quist Adamsen1de3dde2019-01-24 13:17:46 +010055 utils.Print('Using original APK as is', quiet=quiet)
Mads Ager418d1ca2017-05-22 09:35:49 +020056 return processed_apk
Christoffer Quist Adamsen1de3dde2019-01-24 13:17:46 +010057 utils.Print(
58 'Repacking APK with dex files from {}'.format(processed_apk), quiet=quiet)
Christoffer Quist Adamsen2dcea102019-02-20 11:31:38 +010059
60 # Delete original dex files in APK.
Christoffer Quist Adamsen1de3dde2019-01-24 13:17:46 +010061 with utils.ChangedWorkingDirectory(temp, quiet=quiet):
Mads Ager418d1ca2017-05-22 09:35:49 +020062 cmd = ['zip', '-d', 'processed.apk', '*.dex']
Christoffer Quist Adamsen41cbdca2019-04-12 08:52:03 +020063 utils.RunCmd(cmd, quiet=quiet, logging=logging)
Christoffer Quist Adamsen2dcea102019-02-20 11:31:38 +010064
65 # Unzip the jar or zip file into `temp`.
Mads Ager418d1ca2017-05-22 09:35:49 +020066 if processed_out.endswith('.zip') or processed_out.endswith('.jar'):
67 cmd = ['unzip', processed_out, '-d', temp]
Christoffer Quist Adamsen1de3dde2019-01-24 13:17:46 +010068 if quiet:
69 cmd.insert(1, '-q')
Christoffer Quist Adamsen41cbdca2019-04-12 08:52:03 +020070 utils.RunCmd(cmd, quiet=quiet, logging=logging)
Mads Ager418d1ca2017-05-22 09:35:49 +020071 processed_out = temp
Christoffer Quist Adamsen2dcea102019-02-20 11:31:38 +010072
73 # Insert the new dex and resource files from `processed_out` into the APK.
Christoffer Quist Adamsen1de3dde2019-01-24 13:17:46 +010074 with utils.ChangedWorkingDirectory(processed_out, quiet=quiet):
Christoffer Quist Adamsen2dcea102019-02-20 11:31:38 +010075 dex_files = glob.glob('*.dex')
76 resource_files = glob.glob(resources) if resources else []
77 cmd = ['zip', '-u', '-9', processed_apk] + dex_files + resource_files
Christoffer Quist Adamsen41cbdca2019-04-12 08:52:03 +020078 utils.RunCmd(cmd, quiet=quiet, logging=logging)
Mads Ager418d1ca2017-05-22 09:35:49 +020079 return processed_apk
80
Christoffer Quist Adamsen41cbdca2019-04-12 08:52:03 +020081def sign(unsigned_apk, keystore, temp, quiet, logging):
Mads Ager418d1ca2017-05-22 09:35:49 +020082 signed_apk = os.path.join(temp, 'unaligned.apk')
Christoffer Quist Adamsen41cbdca2019-04-12 08:52:03 +020083 apk_utils.sign_with_apksigner(
84 unsigned_apk, signed_apk, keystore, quiet=quiet, logging=logging)
Mads Ager418d1ca2017-05-22 09:35:49 +020085 return signed_apk
86
Christoffer Quist Adamsen41cbdca2019-04-12 08:52:03 +020087def align(signed_apk, temp, quiet, logging):
Christoffer Quist Adamsen1de3dde2019-01-24 13:17:46 +010088 utils.Print('Aligning', quiet=quiet)
Mads Ager418d1ca2017-05-22 09:35:49 +020089 aligned_apk = os.path.join(temp, 'aligned.apk')
Morten Krogh-Jespersenc8efedd2019-01-28 11:36:17 +010090 zipalign_path = (
91 'zipalign' if 'build_tools' in os.environ.get('PATH')
Morten Krogh-Jespersen220e5702019-02-27 12:57:01 +010092 else os.path.join(utils.getAndroidBuildTools(), 'zipalign'))
Morten Krogh-Jespersenc8efedd2019-01-28 11:36:17 +010093 cmd = [
94 zipalign_path,
95 '-f',
96 '4',
97 signed_apk,
98 aligned_apk
99 ]
Christoffer Quist Adamsen41cbdca2019-04-12 08:52:03 +0200100 utils.RunCmd(cmd, quiet=quiet, logging=logging)
Mads Ager418d1ca2017-05-22 09:35:49 +0200101 return signed_apk
102
Christoffer Quist Adamsen4038bbe2019-01-15 14:31:46 +0100103def masseur(
Christoffer Quist Adamsen2dcea102019-02-20 11:31:38 +0100104 apk, dex=None, resources=None, out=None, adb_options=None, keystore=None,
Christoffer Quist Adamsen41cbdca2019-04-12 08:52:03 +0200105 install=False, quiet=False, logging=True):
Christoffer Quist Adamsen4038bbe2019-01-15 14:31:46 +0100106 if not out:
107 out = os.path.basename(apk)
108 if not keystore:
109 keystore = findKeystore()
Mads Ager418d1ca2017-05-22 09:35:49 +0200110 with utils.TempDir() as temp:
111 processed_apk = None
Christoffer Quist Adamsen4038bbe2019-01-15 14:31:46 +0100112 if dex:
Christoffer Quist Adamsen41cbdca2019-04-12 08:52:03 +0200113 processed_apk = repack(apk, dex, resources, temp, quiet, logging)
Mads Ager418d1ca2017-05-22 09:35:49 +0200114 else:
Christoffer Quist Adamsen1de3dde2019-01-24 13:17:46 +0100115 utils.Print(
116 'Signing original APK without modifying dex files', quiet=quiet)
Mads Ager418d1ca2017-05-22 09:35:49 +0200117 processed_apk = os.path.join(temp, 'processed.apk')
118 shutil.copyfile(apk, processed_apk)
Christoffer Quist Adamsen41cbdca2019-04-12 08:52:03 +0200119 signed_apk = sign(
120 processed_apk, keystore, temp, quiet=quiet, logging=logging)
121 aligned_apk = align(signed_apk, temp, quiet=quiet, logging=logging)
Christoffer Quist Adamsen1de3dde2019-01-24 13:17:46 +0100122 utils.Print('Writing result to {}'.format(out), quiet=quiet)
Christoffer Quist Adamsen4038bbe2019-01-15 14:31:46 +0100123 shutil.copyfile(aligned_apk, out)
Christoffer Quist Adamsen4038bbe2019-01-15 14:31:46 +0100124 if install:
Christoffer Quist Adamsen1de3dde2019-01-24 13:17:46 +0100125 adb_cmd = ['adb']
126 if adb_options:
127 adb_cmd.extend(
128 [option for option in adb_options.split(' ') if option])
Christoffer Quist Adamsen4038bbe2019-01-15 14:31:46 +0100129 adb_cmd.extend(['install', '-t', '-r', '-d', out]);
Christoffer Quist Adamsen41cbdca2019-04-12 08:52:03 +0200130 utils.RunCmd(adb_cmd, quiet=quiet, logging=logging)
Christoffer Quist Adamsen4038bbe2019-01-15 14:31:46 +0100131
132def main():
133 (options, apk) = parse_options()
134 masseur(apk, **vars(options))
Mads Ager418d1ca2017-05-22 09:35:49 +0200135 return 0
136
137if __name__ == '__main__':
138 sys.exit(main())