Supervised user whitelists: Cleanup
[chromium-blink-merge.git] / android_webview / tools / apk_merger.py
bloba8aa44d015159e9458af98bae24c54661b22030a
1 #!/usr/bin/python
2 # Copyright 2014 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
6 """ Merges a 64-bit and a 32-bit APK into a single APK
8 """
10 import os
11 import sys
12 import shutil
13 import zipfile
14 import filecmp
15 import tempfile
16 import argparse
17 import subprocess
19 SRC_DIR = os.path.join(os.path.dirname(__file__), '..', '..')
20 SRC_DIR = os.path.abspath(SRC_DIR)
21 BUILD_ANDROID_GYP_DIR = os.path.join(SRC_DIR, 'build/android/gyp')
22 sys.path.append(BUILD_ANDROID_GYP_DIR)
24 import finalize_apk
25 from util import build_utils
27 class ApkMergeFailure(Exception):
28 pass
31 def UnpackApk(file_name, dst):
32 zippy = zipfile.ZipFile(file_name)
33 zippy.extractall(dst)
36 def GetNonDirFiles(top, base_dir):
37 """ Return a list containing all (non-directory) files in tree with top as
38 root.
40 Each file is represented by the relative path from base_dir to that file.
41 If top is a file (not a directory) then a list containing only top is
42 returned.
43 """
44 if os.path.isdir(top):
45 ret = []
46 for dirpath, _, filenames in os.walk(top):
47 for filename in filenames:
48 ret.append(
49 os.path.relpath(os.path.join(dirpath, filename), base_dir))
50 return ret
51 else:
52 return [os.path.relpath(top, base_dir)]
55 def GetDiffFiles(dcmp, base_dir):
56 """ Return the list of files contained only in the right directory of dcmp.
58 The files returned are represented by relative paths from base_dir.
59 """
60 copy_files = []
61 for file_name in dcmp.right_only:
62 copy_files.extend(
63 GetNonDirFiles(os.path.join(dcmp.right, file_name), base_dir))
65 # we cannot merge APKs with files with similar names but different contents
66 if len(dcmp.diff_files) > 0:
67 raise ApkMergeFailure('found differing files: %s in %s and %s' %
68 (dcmp.diff_files, dcmp.left, dcmp.right))
70 if len(dcmp.funny_files) > 0:
71 ApkMergeFailure('found uncomparable files: %s in %s and %s' %
72 (dcmp.funny_files, dcmp.left, dcmp.right))
74 for sub_dcmp in dcmp.subdirs.itervalues():
75 copy_files.extend(GetDiffFiles(sub_dcmp, base_dir))
76 return copy_files
79 def CheckFilesExpected(actual_files, expected_files):
80 """ Check that the lists of actual and expected files are the same. """
81 file_set = set()
82 for file_name in actual_files:
83 base_name = os.path.basename(file_name)
84 if base_name not in expected_files:
85 raise ApkMergeFailure('Found unexpected file named %s.' %
86 file_name)
87 if base_name in file_set:
88 raise ApkMergeFailure('Duplicate file %s to add to APK!' %
89 file_name)
90 file_set.add(base_name)
92 if len(file_set) != len(expected_files):
93 raise ApkMergeFailure('Missing expected files to add to APK!')
96 def AddDiffFiles(diff_files, tmp_dir_32, tmp_apk, expected_files):
97 """ Insert files only present in 32-bit APK into 64-bit APK (tmp_apk). """
98 old_dir = os.getcwd()
99 # Move into 32-bit directory to make sure the files we insert have correct
100 # relative paths.
101 os.chdir(tmp_dir_32)
102 try:
103 for diff_file in diff_files:
104 extra_flags = expected_files[os.path.basename(diff_file)]
105 build_utils.CheckOutput(['zip', '-r', '-X', '--no-dir-entries',
106 tmp_apk, diff_file] + extra_flags)
107 except build_utils.CalledProcessError as e:
108 raise ApkMergeFailure(
109 'Failed to add file %s to APK: %s' % (diff_file, e.output))
110 finally:
111 # Move out of 32-bit directory when done
112 os.chdir(old_dir)
115 def RemoveMetafiles(tmp_apk):
116 """ Remove all meta info to avoid certificate clashes """
117 try:
118 build_utils.CheckOutput(['zip', '-d', tmp_apk, 'META-INF/*'])
119 except build_utils.CalledProcessError as e:
120 raise ApkMergeFailure('Failed to delete Meta folder: ' + e.output)
123 def SignAndAlignApk(tmp_apk, signed_tmp_apk, new_apk, zipalign_path,
124 keystore_path, key_name, key_password):
125 try:
126 finalize_apk.JarSigner(
127 keystore_path,
128 key_name,
129 key_password,
130 tmp_apk,
131 signed_tmp_apk)
132 except build_utils.CalledProcessError as e:
133 raise ApkMergeFailure('Failed to sign APK: ' + e.output)
135 try:
136 finalize_apk.AlignApk(zipalign_path, signed_tmp_apk, new_apk)
137 except build_utils.CalledProcessError as e:
138 raise ApkMergeFailure('Failed to align APK: ' + e.output)
141 def main():
142 parser = argparse.ArgumentParser(
143 description='Merge a 32-bit APK into a 64-bit APK')
144 # Using type=os.path.abspath converts file paths to absolute paths so that
145 # we can change working directory without affecting these paths
146 parser.add_argument('--apk_32bit', required=True, type=os.path.abspath)
147 parser.add_argument('--apk_64bit', required=True, type=os.path.abspath)
148 parser.add_argument('--out_apk', required=True, type=os.path.abspath)
149 parser.add_argument('--zipalign_path', required=True, type=os.path.abspath)
150 parser.add_argument('--keystore_path', required=True, type=os.path.abspath)
151 parser.add_argument('--key_name', required=True)
152 parser.add_argument('--key_password', required=True)
153 args = parser.parse_args()
155 tmp_dir = tempfile.mkdtemp()
156 tmp_dir_64 = os.path.join(tmp_dir, '64_bit')
157 tmp_dir_32 = os.path.join(tmp_dir, '32_bit')
158 tmp_apk = os.path.join(tmp_dir, 'tmp.apk')
159 signed_tmp_apk = os.path.join(tmp_dir, 'signed.apk')
160 new_apk = args.out_apk
162 # Expected files to copy from 32- to 64-bit APK together with an extra flag
163 # setting the compression level of the file
164 expected_files = {'snapshot_blob_32.bin': ['-0'],
165 'natives_blob_32.bin': ['-0'],
166 'libwebviewchromium.so': []}
168 try:
169 shutil.copyfile(args.apk_64bit, tmp_apk)
171 # need to unpack APKs to compare their contents
172 UnpackApk(args.apk_64bit, tmp_dir_64)
173 UnpackApk(args.apk_32bit, tmp_dir_32)
175 dcmp = filecmp.dircmp(
176 tmp_dir_64,
177 tmp_dir_32,
178 ignore=['META-INF', 'AndroidManifest.xml'])
180 diff_files = GetDiffFiles(dcmp, tmp_dir_32)
182 # Check that diff_files match exactly those files we want to insert into
183 # the 64-bit APK.
184 CheckFilesExpected(diff_files, expected_files)
186 RemoveMetafiles(tmp_apk)
188 AddDiffFiles(diff_files, tmp_dir_32, tmp_apk, expected_files)
190 SignAndAlignApk(tmp_apk, signed_tmp_apk, new_apk, args.zipalign_path,
191 args.keystore_path, args.key_name, args.key_password)
193 except ApkMergeFailure as e:
194 print e
195 return 1
196 finally:
197 shutil.rmtree(tmp_dir)
198 return 0
201 if __name__ == '__main__':
202 sys.exit(main())