OLD | NEW |
| (Empty) |
1 #!/usr/bin/env python | |
2 # | |
3 # Copyright 2014 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 # pylint: disable=C0301 | |
8 """Package resources into an apk. | |
9 | |
10 See https://android.googlesource.com/platform/tools/base/+/master/legacy/ant-tas
ks/src/main/java/com/android/ant/AaptExecTask.java | |
11 and | |
12 https://android.googlesource.com/platform/sdk/+/master/files/ant/build.xml | |
13 """ | |
14 # pylint: enable=C0301 | |
15 | |
16 import optparse | |
17 import os | |
18 import re | |
19 import shutil | |
20 import zipfile | |
21 | |
22 from util import build_utils | |
23 | |
24 | |
25 # List is generated from the chrome_apk.apk_intermediates.ap_ via: | |
26 # unzip -l $FILE_AP_ | cut -c31- | grep res/draw | cut -d'/' -f 2 | sort \ | |
27 # | uniq | grep -- -tvdpi- | cut -c10- | |
28 # and then manually sorted. | |
29 # Note that we can't just do a cross-product of dimentions because the filenames | |
30 # become too big and aapt fails to create the files. | |
31 # This leaves all default drawables (mdpi) in the main apk. Android gets upset | |
32 # though if any drawables are missing from the default drawables/ directory. | |
33 DENSITY_SPLITS = { | |
34 'hdpi': ( | |
35 'hdpi-v4', # Order matters for output file names. | |
36 'ldrtl-hdpi-v4', | |
37 'sw600dp-hdpi-v13', | |
38 'ldrtl-hdpi-v17', | |
39 'ldrtl-sw600dp-hdpi-v17', | |
40 'hdpi-v21', | |
41 ), | |
42 'xhdpi': ( | |
43 'xhdpi-v4', | |
44 'ldrtl-xhdpi-v4', | |
45 'sw600dp-xhdpi-v13', | |
46 'ldrtl-xhdpi-v17', | |
47 'ldrtl-sw600dp-xhdpi-v17', | |
48 'xhdpi-v21', | |
49 ), | |
50 'xxhdpi': ( | |
51 'xxhdpi-v4', | |
52 'ldrtl-xxhdpi-v4', | |
53 'sw600dp-xxhdpi-v13', | |
54 'ldrtl-xxhdpi-v17', | |
55 'ldrtl-sw600dp-xxhdpi-v17', | |
56 'xxhdpi-v21', | |
57 ), | |
58 'xxxhdpi': ( | |
59 'xxxhdpi-v4', | |
60 'ldrtl-xxxhdpi-v4', | |
61 'sw600dp-xxxhdpi-v13', | |
62 'ldrtl-xxxhdpi-v17', | |
63 'ldrtl-sw600dp-xxxhdpi-v17', | |
64 'xxxhdpi-v21', | |
65 ), | |
66 'tvdpi': ( | |
67 'tvdpi-v4', | |
68 'sw600dp-tvdpi-v13', | |
69 'ldrtl-sw600dp-tvdpi-v17', | |
70 ), | |
71 } | |
72 | |
73 | |
74 def ParseArgs(): | |
75 """Parses command line options. | |
76 | |
77 Returns: | |
78 An options object as from optparse.OptionsParser.parse_args() | |
79 """ | |
80 parser = optparse.OptionParser() | |
81 build_utils.AddDepfileOption(parser) | |
82 parser.add_option('--android-sdk', help='path to the Android SDK folder') | |
83 parser.add_option('--aapt-path', | |
84 help='path to the Android aapt tool') | |
85 | |
86 parser.add_option('--configuration-name', | |
87 help='Gyp\'s configuration name (Debug or Release).') | |
88 | |
89 parser.add_option('--android-manifest', help='AndroidManifest.xml path') | |
90 parser.add_option('--version-code', help='Version code for apk.') | |
91 parser.add_option('--version-name', help='Version name for apk.') | |
92 parser.add_option( | |
93 '--shared-resources', | |
94 action='store_true', | |
95 help='Make a resource package that can be loaded by a different' | |
96 'application at runtime to access the package\'s resources.') | |
97 parser.add_option('--resource-zips', | |
98 help='zip files containing resources to be packaged') | |
99 parser.add_option('--asset-dir', | |
100 help='directories containing assets to be packaged') | |
101 parser.add_option('--no-compress', help='disables compression for the ' | |
102 'given comma separated list of extensions') | |
103 parser.add_option( | |
104 '--create-density-splits', | |
105 action='store_true', | |
106 help='Enables density splits') | |
107 parser.add_option('--language-splits', | |
108 help='GYP list of languages to create splits for') | |
109 | |
110 parser.add_option('--apk-path', | |
111 help='Path to output (partial) apk.') | |
112 | |
113 (options, args) = parser.parse_args() | |
114 | |
115 if args: | |
116 parser.error('No positional arguments should be given.') | |
117 | |
118 # Check that required options have been provided. | |
119 required_options = ('android_sdk', 'aapt_path', 'configuration_name', | |
120 'android_manifest', 'version_code', 'version_name', | |
121 'apk_path') | |
122 | |
123 build_utils.CheckOptions(options, parser, required=required_options) | |
124 | |
125 return options | |
126 | |
127 | |
128 def MoveImagesToNonMdpiFolders(res_root): | |
129 """Move images from drawable-*-mdpi-* folders to drawable-* folders. | |
130 | |
131 Why? http://crbug.com/289843 | |
132 """ | |
133 for src_dir_name in os.listdir(res_root): | |
134 src_components = src_dir_name.split('-') | |
135 if src_components[0] != 'drawable' or 'mdpi' not in src_components: | |
136 continue | |
137 src_dir = os.path.join(res_root, src_dir_name) | |
138 if not os.path.isdir(src_dir): | |
139 continue | |
140 dst_components = [c for c in src_components if c != 'mdpi'] | |
141 assert dst_components != src_components | |
142 dst_dir_name = '-'.join(dst_components) | |
143 dst_dir = os.path.join(res_root, dst_dir_name) | |
144 build_utils.MakeDirectory(dst_dir) | |
145 for src_file_name in os.listdir(src_dir): | |
146 if not src_file_name.endswith('.png'): | |
147 continue | |
148 src_file = os.path.join(src_dir, src_file_name) | |
149 dst_file = os.path.join(dst_dir, src_file_name) | |
150 assert not os.path.lexists(dst_file) | |
151 shutil.move(src_file, dst_file) | |
152 | |
153 | |
154 def PackageArgsForExtractedZip(d): | |
155 """Returns the aapt args for an extracted resources zip. | |
156 | |
157 A resources zip either contains the resources for a single target or for | |
158 multiple targets. If it is multiple targets merged into one, the actual | |
159 resource directories will be contained in the subdirectories 0, 1, 2, ... | |
160 """ | |
161 subdirs = [os.path.join(d, s) for s in os.listdir(d)] | |
162 subdirs = [s for s in subdirs if os.path.isdir(s)] | |
163 is_multi = '0' in [os.path.basename(s) for s in subdirs] | |
164 if is_multi: | |
165 res_dirs = sorted(subdirs, key=lambda p : int(os.path.basename(p))) | |
166 else: | |
167 res_dirs = [d] | |
168 package_command = [] | |
169 for d in res_dirs: | |
170 MoveImagesToNonMdpiFolders(d) | |
171 package_command += ['-S', d] | |
172 return package_command | |
173 | |
174 | |
175 def RenameDensitySplits(apk_path): | |
176 """Renames all density splits to have shorter / predictable names.""" | |
177 for density, config in DENSITY_SPLITS.iteritems(): | |
178 src_path = '%s_%s' % (apk_path, '_'.join(config)) | |
179 dst_path = '%s_%s' % (apk_path, density) | |
180 if src_path != dst_path: | |
181 if os.path.exists(dst_path): | |
182 os.unlink(dst_path) | |
183 os.rename(src_path, dst_path) | |
184 | |
185 | |
186 def CheckForMissedConfigs(apk_path, check_density, languages): | |
187 """Raises an exception if apk_path contains any unexpected configs.""" | |
188 triggers = [] | |
189 if check_density: | |
190 triggers.extend(re.compile('-%s' % density) for density in DENSITY_SPLITS) | |
191 if languages: | |
192 triggers.extend(re.compile(r'-%s\b' % lang) for lang in languages) | |
193 with zipfile.ZipFile(apk_path) as main_apk_zip: | |
194 for name in main_apk_zip.namelist(): | |
195 for trigger in triggers: | |
196 if trigger.search(name) and not 'mipmap-' in name: | |
197 raise Exception(('Found config in main apk that should have been ' + | |
198 'put into a split: %s\nYou need to update ' + | |
199 'package_resources.py to include this new ' + | |
200 'config (trigger=%s)') % (name, trigger.pattern)) | |
201 | |
202 | |
203 def main(): | |
204 options = ParseArgs() | |
205 android_jar = os.path.join(options.android_sdk, 'android.jar') | |
206 aapt = options.aapt_path | |
207 | |
208 with build_utils.TempDir() as temp_dir: | |
209 package_command = [aapt, | |
210 'package', | |
211 '--version-code', options.version_code, | |
212 '--version-name', options.version_name, | |
213 '-M', options.android_manifest, | |
214 '--no-crunch', | |
215 '-f', | |
216 '--auto-add-overlay', | |
217 '-I', android_jar, | |
218 '-F', options.apk_path, | |
219 '--ignore-assets', build_utils.AAPT_IGNORE_PATTERN, | |
220 ] | |
221 | |
222 if options.no_compress: | |
223 for ext in options.no_compress.split(','): | |
224 package_command += ['-0', ext] | |
225 if options.shared_resources: | |
226 package_command.append('--shared-lib') | |
227 | |
228 if options.asset_dir and os.path.exists(options.asset_dir): | |
229 package_command += ['-A', options.asset_dir] | |
230 | |
231 if options.resource_zips: | |
232 dep_zips = build_utils.ParseGypList(options.resource_zips) | |
233 for z in dep_zips: | |
234 subdir = os.path.join(temp_dir, os.path.basename(z)) | |
235 if os.path.exists(subdir): | |
236 raise Exception('Resource zip name conflict: ' + os.path.basename(z)) | |
237 build_utils.ExtractAll(z, path=subdir) | |
238 package_command += PackageArgsForExtractedZip(subdir) | |
239 | |
240 if options.create_density_splits: | |
241 for config in DENSITY_SPLITS.itervalues(): | |
242 package_command.extend(('--split', ','.join(config))) | |
243 | |
244 language_splits = None | |
245 if options.language_splits: | |
246 language_splits = build_utils.ParseGypList(options.language_splits) | |
247 for lang in language_splits: | |
248 package_command.extend(('--split', lang)) | |
249 | |
250 if 'Debug' in options.configuration_name: | |
251 package_command += ['--debug-mode'] | |
252 | |
253 build_utils.CheckOutput( | |
254 package_command, print_stdout=False, print_stderr=False) | |
255 | |
256 if options.create_density_splits or language_splits: | |
257 CheckForMissedConfigs( | |
258 options.apk_path, options.create_density_splits, language_splits) | |
259 | |
260 if options.create_density_splits: | |
261 RenameDensitySplits(options.apk_path) | |
262 | |
263 if options.depfile: | |
264 build_utils.WriteDepfile( | |
265 options.depfile, | |
266 build_utils.GetPythonDependencies()) | |
267 | |
268 | |
269 if __name__ == '__main__': | |
270 main() | |
OLD | NEW |