blob: ef5f2cfbd8cfdd6480aa4dea51d36f9211ec6f25 [file] [log] [blame]
Ben Murdoch097c5b22016-05-18 11:27:45 +01001#!/usr/bin/env python
2#
3# Copyright (c) 2015 The Chromium Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Adds the code parts to a resource APK."""
8
9import argparse
10import itertools
11import os
12import shutil
13import sys
14import zipfile
15
16from util import build_utils
17
18
19# Taken from aapt's Package.cpp:
20_NO_COMPRESS_EXTENSIONS = ('.jpg', '.jpeg', '.png', '.gif', '.wav', '.mp2',
21 '.mp3', '.ogg', '.aac', '.mpg', '.mpeg', '.mid',
22 '.midi', '.smf', '.jet', '.rtttl', '.imy', '.xmf',
23 '.mp4', '.m4a', '.m4v', '.3gp', '.3gpp', '.3g2',
24 '.3gpp2', '.amr', '.awb', '.wma', '.wmv', '.webm')
25
26
27def _ParseArgs(args):
28 parser = argparse.ArgumentParser()
29 build_utils.AddDepfileOption(parser)
30 parser.add_argument('--assets',
31 help='GYP-list of files to add as assets in the form '
32 '"srcPath:zipPath", where ":zipPath" is optional.',
33 default='[]')
34 parser.add_argument('--write-asset-list',
35 action='store_true',
36 help='Whether to create an assets/assets_list file.')
37 parser.add_argument('--uncompressed-assets',
38 help='Same as --assets, except disables compression.',
39 default='[]')
40 parser.add_argument('--resource-apk',
41 help='An .ap_ file built using aapt',
42 required=True)
43 parser.add_argument('--output-apk',
44 help='Path to the output file',
45 required=True)
46 parser.add_argument('--dex-file',
47 help='Path to the classes.dex to use')
48 parser.add_argument('--native-libs',
49 action='append',
50 help='GYP-list of native libraries to include. '
51 'Can be specified multiple times.',
52 default=[])
53 parser.add_argument('--secondary-native-libs',
54 action='append',
55 help='GYP-list of native libraries for secondary '
56 'android-abi. Can be specified multiple times.',
57 default=[])
58 parser.add_argument('--android-abi',
59 help='Android architecture to use for native libraries')
60 parser.add_argument('--secondary-android-abi',
61 help='The secondary Android architecture to use for'
62 'secondary native libraries')
63 parser.add_argument('--native-lib-placeholders',
64 help='GYP-list of native library placeholders to add.',
65 default='[]')
66 parser.add_argument('--emma-device-jar',
67 help='Path to emma_device.jar to include.')
68 parser.add_argument('--uncompress-shared-libraries',
69 action='store_true',
70 help='Uncompress shared libraries')
71 options = parser.parse_args(args)
72 options.assets = build_utils.ParseGypList(options.assets)
73 options.uncompressed_assets = build_utils.ParseGypList(
74 options.uncompressed_assets)
75 options.native_lib_placeholders = build_utils.ParseGypList(
76 options.native_lib_placeholders)
77 all_libs = []
78 for gyp_list in options.native_libs:
79 all_libs.extend(build_utils.ParseGypList(gyp_list))
80 options.native_libs = all_libs
81 secondary_libs = []
82 for gyp_list in options.secondary_native_libs:
83 secondary_libs.extend(build_utils.ParseGypList(gyp_list))
84 options.secondary_native_libs = secondary_libs
85
86
87 if not options.android_abi and (options.native_libs or
88 options.native_lib_placeholders):
89 raise Exception('Must specify --android-abi with --native-libs')
90 if not options.secondary_android_abi and options.secondary_native_libs:
91 raise Exception('Must specify --secondary-android-abi with'
92 ' --secondary-native-libs')
93 return options
94
95
96def _SplitAssetPath(path):
97 """Returns (src, dest) given an asset path in the form src[:dest]."""
98 path_parts = path.split(':')
99 src_path = path_parts[0]
100 if len(path_parts) > 1:
101 dest_path = path_parts[1]
102 else:
103 dest_path = os.path.basename(src_path)
104 return src_path, dest_path
105
106
107def _ExpandPaths(paths):
108 """Converts src:dst into tuples and enumerates files within directories.
109
110 Args:
111 paths: Paths in the form "src_path:dest_path"
112
113 Returns:
114 A list of (src_path, dest_path) tuples sorted by dest_path (for stable
115 ordering within output .apk).
116 """
117 ret = []
118 for path in paths:
119 src_path, dest_path = _SplitAssetPath(path)
120 if os.path.isdir(src_path):
121 for f in build_utils.FindInDirectory(src_path, '*'):
122 ret.append((f, os.path.join(dest_path, f[len(src_path) + 1:])))
123 else:
124 ret.append((src_path, dest_path))
125 ret.sort(key=lambda t:t[1])
126 return ret
127
128
129def _AddAssets(apk, path_tuples, disable_compression=False):
130 """Adds the given paths to the apk.
131
132 Args:
133 apk: ZipFile to write to.
134 paths: List of paths (with optional :zipPath suffix) to add.
135 disable_compression: Whether to disable compression.
136 """
137 # Group all uncompressed assets together in the hope that it will increase
138 # locality of mmap'ed files.
139 for target_compress in (False, True):
140 for src_path, dest_path in path_tuples:
141
142 compress = not disable_compression and (
143 os.path.splitext(src_path)[1] not in _NO_COMPRESS_EXTENSIONS)
144 if target_compress == compress:
145 apk_path = 'assets/' + dest_path
146 try:
147 apk.getinfo(apk_path)
148 # Should never happen since write_build_config.py handles merging.
149 raise Exception('Multiple targets specified the asset path: %s' %
150 apk_path)
151 except KeyError:
152 build_utils.AddToZipHermetic(apk, apk_path, src_path=src_path,
153 compress=compress)
154
155
156def _CreateAssetsList(path_tuples):
157 """Returns a newline-separated list of asset paths for the given paths."""
158 dests = sorted(t[1] for t in path_tuples)
159 return '\n'.join(dests) + '\n'
160
161
162def _AddNativeLibraries(out_apk, native_libs, android_abi, uncompress):
163 """Add native libraries to APK."""
164 for path in native_libs:
165 basename = os.path.basename(path)
166 apk_path = 'lib/%s/%s' % (android_abi, basename)
167
168 compress = None
169 if (uncompress and os.path.splitext(basename)[1] == '.so'):
170 compress = False
171
172 build_utils.AddToZipHermetic(out_apk,
173 apk_path,
174 src_path=path,
175 compress=compress)
176
177
178def main(args):
179 args = build_utils.ExpandFileArgs(args)
180 options = _ParseArgs(args)
181
182 native_libs = sorted(options.native_libs)
183
184 input_paths = [options.resource_apk, __file__] + native_libs
185
186 secondary_native_libs = []
187 if options.secondary_native_libs:
188 secondary_native_libs = sorted(options.secondary_native_libs)
189 input_paths += secondary_native_libs
190
191 if options.dex_file:
192 input_paths.append(options.dex_file)
193
194 if options.emma_device_jar:
195 input_paths.append(options.emma_device_jar)
196
197 input_strings = [options.android_abi,
198 options.native_lib_placeholders,
199 options.uncompress_shared_libraries]
200
201 if options.secondary_android_abi:
202 input_strings.append(options.secondary_android_abi)
203
204 _assets = _ExpandPaths(options.assets)
205 _uncompressed_assets = _ExpandPaths(options.uncompressed_assets)
206
207 for src_path, dest_path in itertools.chain(_assets, _uncompressed_assets):
208 input_paths.append(src_path)
209 input_strings.append(dest_path)
210
211 def on_stale_md5():
212 tmp_apk = options.output_apk + '.tmp'
213 try:
214 # TODO(agrieve): It would be more efficient to combine this step
215 # with finalize_apk(), which sometimes aligns and uncompresses the
216 # native libraries.
217 with zipfile.ZipFile(options.resource_apk) as resource_apk, \
218 zipfile.ZipFile(tmp_apk, 'w', zipfile.ZIP_DEFLATED) as out_apk:
219 def copy_resource(zipinfo):
220 compress = zipinfo.compress_type != zipfile.ZIP_STORED
221 build_utils.AddToZipHermetic(out_apk, zipinfo.filename,
222 data=resource_apk.read(zipinfo.filename),
223 compress=compress)
224
225 # Make assets come before resources in order to maintain the same file
226 # ordering as GYP / aapt. http://crbug.com/561862
227 resource_infos = resource_apk.infolist()
228
229 # 1. AndroidManifest.xml
230 assert resource_infos[0].filename == 'AndroidManifest.xml'
231 copy_resource(resource_infos[0])
232
233 # 2. Assets
234 if options.write_asset_list:
235 data = _CreateAssetsList(
236 itertools.chain(_assets, _uncompressed_assets))
237 build_utils.AddToZipHermetic(out_apk, 'assets/assets_list', data=data)
238
239 _AddAssets(out_apk, _assets, disable_compression=False)
240 _AddAssets(out_apk, _uncompressed_assets, disable_compression=True)
241
242 # 3. Dex files
243 if options.dex_file and options.dex_file.endswith('.zip'):
244 with zipfile.ZipFile(options.dex_file, 'r') as dex_zip:
245 for dex in (d for d in dex_zip.namelist() if d.endswith('.dex')):
246 build_utils.AddToZipHermetic(out_apk, dex, data=dex_zip.read(dex))
247 elif options.dex_file:
248 build_utils.AddToZipHermetic(out_apk, 'classes.dex',
249 src_path=options.dex_file)
250
251 # 4. Native libraries.
252 _AddNativeLibraries(out_apk,
253 native_libs,
254 options.android_abi,
255 options.uncompress_shared_libraries)
256
257 if options.secondary_android_abi:
258 _AddNativeLibraries(out_apk,
259 secondary_native_libs,
260 options.secondary_android_abi,
261 options.uncompress_shared_libraries)
262
263 for name in sorted(options.native_lib_placeholders):
264 # Empty libs files are ignored by md5check, but rezip requires them
265 # to be empty in order to identify them as placeholders.
266 apk_path = 'lib/%s/%s' % (options.android_abi, name)
267 build_utils.AddToZipHermetic(out_apk, apk_path, data='')
268
269 # 5. Resources
270 for info in resource_infos[1:]:
271 copy_resource(info)
272
273 # 6. Java resources. Used only when coverage is enabled, so order
274 # doesn't matter).
275 if options.emma_device_jar:
276 # Add EMMA Java resources to APK.
277 with zipfile.ZipFile(options.emma_device_jar, 'r') as emma_device_jar:
278 for apk_path in emma_device_jar.namelist():
279 apk_path_lower = apk_path.lower()
280 if apk_path_lower.startswith('meta-inf/'):
281 continue
282
283 if apk_path_lower.endswith('/'):
284 continue
285
286 if apk_path_lower.endswith('.class'):
287 continue
288
289 build_utils.AddToZipHermetic(out_apk, apk_path,
290 data=emma_device_jar.read(apk_path))
291
292 shutil.move(tmp_apk, options.output_apk)
293 finally:
294 if os.path.exists(tmp_apk):
295 os.unlink(tmp_apk)
296
297 build_utils.CallAndWriteDepfileIfStale(
298 on_stale_md5,
299 options,
300 input_paths=input_paths,
301 input_strings=input_strings,
302 output_paths=[options.output_apk])
303
304
305if __name__ == '__main__':
306 main(sys.argv[1:])