blob: 51533278f43ab945dcad74d4680aaa5a079db92c [file] [log] [blame]
Ian Zernydcb172e2022-02-22 15:36:45 +01001#!/usr/bin/env python3
Mads Ager418d1ca2017-05-22 09:35:49 +02002# 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
6import glob
7import optparse
8import os
9import shutil
Mads Ager418d1ca2017-05-22 09:35:49 +020010import sys
Christoffer Quist Adamsen0aaca162023-02-27 13:02:01 +010011
12import apk_utils
Mads Ager418d1ca2017-05-22 09:35:49 +020013import utils
Christoffer Quist Adamsen0aaca162023-02-27 13:02:01 +010014import zip_utils
Mads Ager418d1ca2017-05-22 09:35:49 +020015
16USAGE = 'usage: %prog [options] <apk>'
17
18def parse_options():
19 parser = optparse.OptionParser(usage=USAGE)
20 parser.add_option('--dex',
Christoffer Quist Adamsen0aaca162023-02-27 13:02:01 +010021 help='Directory or archive with dex files to use instead '
22 'of those in the apk',
23 default=None)
24 parser.add_option('--desugared-library-dex',
25 help='Path to desugared library dex file to use or archive '
26 'containing a single classes.dex file',
Christoffer Quist Adamsen2dcea102019-02-20 11:31:38 +010027 default=None)
28 parser.add_option('--resources',
29 help=('pattern that matches resources to use instead of '
30 + 'those in the apk'),
Mads Ager418d1ca2017-05-22 09:35:49 +020031 default=None)
32 parser.add_option('--out',
33 help='output file (default ./$(basename <apk>))',
34 default=None)
35 parser.add_option('--keystore',
36 help='keystore file (default ~/.android/app.keystore)',
37 default=None)
Søren Gjessec4b3f612017-05-24 10:10:43 +020038 parser.add_option('--install',
39 help='install the generated apk with adb options -t -r -d',
40 default=False,
41 action='store_true')
Søren Gjesse12d41d42017-06-01 09:01:24 +020042 parser.add_option('--adb-options',
43 help='additional adb options when running adb',
44 default=None)
Christoffer Quist Adamsen1de3dde2019-01-24 13:17:46 +010045 parser.add_option('--quiet',
46 help='disable verbose logging',
47 default=False)
Christoffer Quist Adamsen8c803b42022-05-31 10:36:17 +020048 parser.add_option('--sign-before-align',
49 help='Sign the apk before aligning',
50 default=False,
51 action='store_true')
Mads Ager418d1ca2017-05-22 09:35:49 +020052 (options, args) = parser.parse_args()
53 if len(args) != 1:
54 parser.error('Expected <apk> argument, got: ' + ' '.join(args))
55 apk = args[0]
Mads Ager418d1ca2017-05-22 09:35:49 +020056 return (options, apk)
57
Christoffer Quist Adamsen0aaca162023-02-27 13:02:01 +010058def is_archive(file):
59 return file.endswith('.zip') or file.endswith('.jar')
60
61def repack(
62 apk, processed_out, desugared_library_dex, resources, temp, quiet, logging):
Mads Ager418d1ca2017-05-22 09:35:49 +020063 processed_apk = os.path.join(temp, 'processed.apk')
Christoffer Quist Adamsen2dcea102019-02-20 11:31:38 +010064 shutil.copyfile(apk, processed_apk)
Mads Ager418d1ca2017-05-22 09:35:49 +020065 if not processed_out:
Christoffer Quist Adamsen1de3dde2019-01-24 13:17:46 +010066 utils.Print('Using original APK as is', quiet=quiet)
Mads Ager418d1ca2017-05-22 09:35:49 +020067 return processed_apk
Christoffer Quist Adamsen1de3dde2019-01-24 13:17:46 +010068 utils.Print(
Christoffer Quist Adamsen0aaca162023-02-27 13:02:01 +010069 'Repacking APK with dex files from {}'.format(processed_out), quiet=quiet)
Christoffer Quist Adamsen2dcea102019-02-20 11:31:38 +010070
71 # Delete original dex files in APK.
Christoffer Quist Adamsen1de3dde2019-01-24 13:17:46 +010072 with utils.ChangedWorkingDirectory(temp, quiet=quiet):
Mads Ager418d1ca2017-05-22 09:35:49 +020073 cmd = ['zip', '-d', 'processed.apk', '*.dex']
Christoffer Quist Adamsen41cbdca2019-04-12 08:52:03 +020074 utils.RunCmd(cmd, quiet=quiet, logging=logging)
Christoffer Quist Adamsen2dcea102019-02-20 11:31:38 +010075
76 # Unzip the jar or zip file into `temp`.
Christoffer Quist Adamsen0aaca162023-02-27 13:02:01 +010077 if is_archive(processed_out):
Mads Ager418d1ca2017-05-22 09:35:49 +020078 cmd = ['unzip', processed_out, '-d', temp]
Christoffer Quist Adamsen1de3dde2019-01-24 13:17:46 +010079 if quiet:
80 cmd.insert(1, '-q')
Christoffer Quist Adamsen41cbdca2019-04-12 08:52:03 +020081 utils.RunCmd(cmd, quiet=quiet, logging=logging)
Mads Ager418d1ca2017-05-22 09:35:49 +020082 processed_out = temp
Christoffer Quist Adamsen0aaca162023-02-27 13:02:01 +010083 elif desugared_library_dex:
84 for dex_name in glob.glob('*.dex', root_dir=processed_out):
85 src = os.path.join(processed_out, dex_name)
86 dst = os.path.join(temp, dex_name)
87 shutil.copyfile(src, dst)
88 processed_out = temp
89
90 if desugared_library_dex:
91 desugared_library_dex_index = len(glob.glob('*.dex', root_dir=temp)) + 1
92 desugared_library_dex_name = 'classes%s.dex' % desugared_library_dex_index
93 desugared_library_dex_dst = os.path.join(temp, desugared_library_dex_name)
94 if is_archive(desugared_library_dex):
95 zip_utils.extract_member(
96 desugared_library_dex, 'classes.dex', desugared_library_dex_dst)
97 else:
98 shutil.copyfile(desugared_library_dex, desugared_library_dex_dst)
Christoffer Quist Adamsen2dcea102019-02-20 11:31:38 +010099
100 # Insert the new dex and resource files from `processed_out` into the APK.
Christoffer Quist Adamsen1de3dde2019-01-24 13:17:46 +0100101 with utils.ChangedWorkingDirectory(processed_out, quiet=quiet):
Christoffer Quist Adamsen2dcea102019-02-20 11:31:38 +0100102 dex_files = glob.glob('*.dex')
Christoffer Quist Adamsen0aaca162023-02-27 13:02:01 +0100103 dex_files.sort()
Christoffer Quist Adamsen2dcea102019-02-20 11:31:38 +0100104 resource_files = glob.glob(resources) if resources else []
Rico Wind5e486822022-08-17 14:04:35 +0200105 cmd = ['zip', '-u', '-0', processed_apk] + dex_files + resource_files
Christoffer Quist Adamsen41cbdca2019-04-12 08:52:03 +0200106 utils.RunCmd(cmd, quiet=quiet, logging=logging)
Mads Ager418d1ca2017-05-22 09:35:49 +0200107 return processed_apk
108
Christoffer Quist Adamsen41cbdca2019-04-12 08:52:03 +0200109def sign(unsigned_apk, keystore, temp, quiet, logging):
Mads Ager418d1ca2017-05-22 09:35:49 +0200110 signed_apk = os.path.join(temp, 'unaligned.apk')
Christoffer Quist Adamsen3f7e4282022-04-21 13:12:31 +0200111 return apk_utils.sign_with_apksigner(
Christoffer Quist Adamsen41cbdca2019-04-12 08:52:03 +0200112 unsigned_apk, signed_apk, keystore, quiet=quiet, logging=logging)
Mads Ager418d1ca2017-05-22 09:35:49 +0200113
Christoffer Quist Adamsen41cbdca2019-04-12 08:52:03 +0200114def align(signed_apk, temp, quiet, logging):
Christoffer Quist Adamsen1de3dde2019-01-24 13:17:46 +0100115 utils.Print('Aligning', quiet=quiet)
Mads Ager418d1ca2017-05-22 09:35:49 +0200116 aligned_apk = os.path.join(temp, 'aligned.apk')
Christoffer Quist Adamsen3f7e4282022-04-21 13:12:31 +0200117 return apk_utils.align(signed_apk, aligned_apk)
Mads Ager418d1ca2017-05-22 09:35:49 +0200118
Christoffer Quist Adamsen4038bbe2019-01-15 14:31:46 +0100119def masseur(
Christoffer Quist Adamsen0aaca162023-02-27 13:02:01 +0100120 apk, dex=None, desugared_library_dex=None, resources=None, out=None,
121 adb_options=None, sign_before_align=False, keystore=None, install=False,
122 quiet=False, logging=True):
Christoffer Quist Adamsen4038bbe2019-01-15 14:31:46 +0100123 if not out:
124 out = os.path.basename(apk)
125 if not keystore:
Christoffer Quist Adamsen3f7e4282022-04-21 13:12:31 +0200126 keystore = apk_utils.default_keystore()
Mads Ager418d1ca2017-05-22 09:35:49 +0200127 with utils.TempDir() as temp:
128 processed_apk = None
Christoffer Quist Adamsen4038bbe2019-01-15 14:31:46 +0100129 if dex:
Christoffer Quist Adamsen0aaca162023-02-27 13:02:01 +0100130 processed_apk = repack(
131 apk, dex, desugared_library_dex, resources, temp, quiet, logging)
Mads Ager418d1ca2017-05-22 09:35:49 +0200132 else:
Christoffer Quist Adamsen0aaca162023-02-27 13:02:01 +0100133 assert not desugared_library_dex
Christoffer Quist Adamsen1de3dde2019-01-24 13:17:46 +0100134 utils.Print(
135 'Signing original APK without modifying dex files', quiet=quiet)
Mads Ager418d1ca2017-05-22 09:35:49 +0200136 processed_apk = os.path.join(temp, 'processed.apk')
137 shutil.copyfile(apk, processed_apk)
Christoffer Quist Adamsen8c803b42022-05-31 10:36:17 +0200138 if sign_before_align:
139 signed_apk = sign(
140 processed_apk, keystore, temp, quiet=quiet, logging=logging)
141 aligned_apk = align(signed_apk, temp, quiet=quiet, logging=logging)
142 utils.Print('Writing result to {}'.format(out), quiet=quiet)
143 shutil.copyfile(aligned_apk, out)
144 else:
145 aligned_apk = align(processed_apk, temp, quiet=quiet, logging=logging)
146 signed_apk = sign(
147 aligned_apk, keystore, temp, quiet=quiet, logging=logging)
148 utils.Print('Writing result to {}'.format(out), quiet=quiet)
149 shutil.copyfile(signed_apk, out)
Christoffer Quist Adamsen4038bbe2019-01-15 14:31:46 +0100150 if install:
Christoffer Quist Adamsen1de3dde2019-01-24 13:17:46 +0100151 adb_cmd = ['adb']
152 if adb_options:
153 adb_cmd.extend(
154 [option for option in adb_options.split(' ') if option])
Christoffer Quist Adamsen4038bbe2019-01-15 14:31:46 +0100155 adb_cmd.extend(['install', '-t', '-r', '-d', out]);
Christoffer Quist Adamsen41cbdca2019-04-12 08:52:03 +0200156 utils.RunCmd(adb_cmd, quiet=quiet, logging=logging)
Christoffer Quist Adamsen4038bbe2019-01-15 14:31:46 +0100157
158def main():
159 (options, apk) = parse_options()
160 masseur(apk, **vars(options))
Mads Ager418d1ca2017-05-22 09:35:49 +0200161 return 0
162
163if __name__ == '__main__':
164 sys.exit(main())