blob: 6e3528a8adf0eb98fe474aada3ad34491f2ef93a [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
6import glob
7import optparse
8import os
9import shutil
10import subprocess
11import sys
12import utils
13
14USAGE = 'usage: %prog [options] <apk>'
15
16def parse_options():
17 parser = optparse.OptionParser(usage=USAGE)
18 parser.add_option('--dex',
19 help='directory with dex files to use instead of those in the apk',
20 default=None)
21 parser.add_option('--out',
22 help='output file (default ./$(basename <apk>))',
23 default=None)
24 parser.add_option('--keystore',
25 help='keystore file (default ~/.android/app.keystore)',
26 default=None)
Søren Gjessec4b3f612017-05-24 10:10:43 +020027 parser.add_option('--install',
28 help='install the generated apk with adb options -t -r -d',
29 default=False,
30 action='store_true')
Mads Ager418d1ca2017-05-22 09:35:49 +020031 (options, args) = parser.parse_args()
32 if len(args) != 1:
33 parser.error('Expected <apk> argument, got: ' + ' '.join(args))
34 apk = args[0]
35 if not options.out:
36 options.out = os.path.basename(apk)
37 if not options.keystore:
38 options.keystore = findKeystore()
39 return (options, apk)
40
41def findKeystore():
42 return os.path.join(os.getenv('HOME'), '.android', 'app.keystore')
43
44def repack(processed_out, original_apk, temp):
45 processed_apk = os.path.join(temp, 'processed.apk')
46 shutil.copyfile(original_apk, processed_apk)
47 if not processed_out:
48 print 'Using original APK as is'
49 return processed_apk
50 print 'Repacking APK with dex files from', processed_apk
51 with utils.ChangedWorkingDirectory(temp):
52 cmd = ['zip', '-d', 'processed.apk', '*.dex']
53 utils.PrintCmd(cmd)
54 subprocess.check_call(cmd)
55 if processed_out.endswith('.zip') or processed_out.endswith('.jar'):
56 cmd = ['unzip', processed_out, '-d', temp]
57 utils.PrintCmd(cmd)
58 subprocess.check_call(cmd)
59 processed_out = temp
60 with utils.ChangedWorkingDirectory(processed_out):
61 dex = glob.glob('*.dex')
62 cmd = ['zip', '-u', '-9', processed_apk] + dex
63 utils.PrintCmd(cmd)
64 subprocess.check_call(cmd)
65 return processed_apk
66
67def sign(unsigned_apk, keystore, temp):
68 print 'Signing (ignore the warnings)'
69 cmd = ['zip', '-d', unsigned_apk, 'META-INF/*']
70 utils.PrintCmd(cmd)
71 subprocess.call(cmd)
72 signed_apk = os.path.join(temp, 'unaligned.apk')
73 cmd = [
74 'jarsigner',
75 '-sigalg', 'SHA1withRSA',
76 '-digestalg', 'SHA1',
77 '-keystore', keystore,
78 '-storepass', 'android',
79 '-signedjar', signed_apk,
80 unsigned_apk,
81 'androiddebugkey'
82 ]
83 utils.PrintCmd(cmd)
84 subprocess.check_call(cmd)
85 return signed_apk
86
87def align(signed_apk, temp):
88 print 'Aligning'
89 aligned_apk = os.path.join(temp, 'aligned.apk')
90 cmd = ['zipalign', '-f', '4', signed_apk, aligned_apk]
91 print ' '.join(cmd)
92 subprocess.check_call(cmd)
93 return signed_apk
94
95def main():
96 (options, apk) = parse_options()
97 with utils.TempDir() as temp:
98 processed_apk = None
99 if options.dex:
100 processed_apk = repack(options.dex, apk, temp)
101 else:
102 print 'Signing original APK without modifying dex files'
103 processed_apk = os.path.join(temp, 'processed.apk')
104 shutil.copyfile(apk, processed_apk)
105 signed_apk = sign(processed_apk, options.keystore, temp)
106 aligned_apk = align(signed_apk, temp)
107 print 'Writing result to', options.out
108 shutil.copyfile(aligned_apk, options.out)
Søren Gjessec4b3f612017-05-24 10:10:43 +0200109 if options.install:
110 cmd = ['adb', 'install', '-t', '-r', '-d', options.out]
111 utils.PrintCmd(cmd)
112 subprocess.check_call(cmd)
Mads Ager418d1ca2017-05-22 09:35:49 +0200113 return 0
114
115if __name__ == '__main__':
116 sys.exit(main())