Roll src/third_party/WebKit e0eac24:489c548 (svn 193311:193320)
[chromium-blink-merge.git] / build / android / gyp / process_resources.py
blob52cf1439b942e3de56d33bc4b5b5a02a3ef11151
1 #!/usr/bin/env python
3 # Copyright (c) 2012 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.
7 """Process Android resources to generate R.java, and prepare for packaging.
9 This will crunch images and generate v14 compatible resources
10 (see generate_v14_compatible_resources.py).
11 """
13 import optparse
14 import os
15 import re
16 import shutil
17 import sys
18 import zipfile
20 import generate_v14_compatible_resources
22 from util import build_utils
25 def ParseArgs(args):
26 """Parses command line options.
28 Returns:
29 An options object as from optparse.OptionsParser.parse_args()
30 """
31 parser = optparse.OptionParser()
32 build_utils.AddDepfileOption(parser)
34 parser.add_option('--android-sdk', help='path to the Android SDK folder')
35 parser.add_option('--android-sdk-tools',
36 help='path to the Android SDK build tools folder')
37 parser.add_option('--non-constant-id', action='store_true')
39 parser.add_option('--android-manifest', help='AndroidManifest.xml path')
40 parser.add_option('--custom-package', help='Java package for R.java')
41 parser.add_option(
42 '--shared-resources',
43 action='store_true',
44 help='Make a resource package that can be loaded by a different'
45 'application at runtime to access the package\'s resources.')
47 parser.add_option('--resource-dirs',
48 help='Directories containing resources of this target.')
49 parser.add_option('--dependencies-res-zips',
50 help='Resources from dependents.')
52 parser.add_option('--resource-zip-out',
53 help='Path for output zipped resources.')
55 parser.add_option('--R-dir',
56 help='directory to hold generated R.java.')
57 parser.add_option('--srcjar-out',
58 help='Path to srcjar to contain generated R.java.')
60 parser.add_option('--proguard-file',
61 help='Path to proguard.txt generated file')
63 parser.add_option(
64 '--v14-verify-only',
65 action='store_true',
66 help='Do not generate v14 resources. Instead, just verify that the '
67 'resources are already compatible with v14, i.e. they don\'t use '
68 'attributes that cause crashes on certain devices.')
70 parser.add_option(
71 '--extra-res-packages',
72 help='Additional package names to generate R.java files for')
73 # TODO(cjhopman): Actually use --extra-r-text-files. We currently include all
74 # the resources in all R.java files for a particular apk.
75 parser.add_option(
76 '--extra-r-text-files',
77 help='For each additional package, the R.txt file should contain a '
78 'list of resources to be included in the R.java file in the format '
79 'generated by aapt')
81 parser.add_option(
82 '--all-resources-zip-out',
83 help='Path for output of all resources. This includes resources in '
84 'dependencies.')
86 parser.add_option('--stamp', help='File to touch on success')
88 (options, args) = parser.parse_args(args)
90 if args:
91 parser.error('No positional arguments should be given.')
93 # Check that required options have been provided.
94 required_options = (
95 'android_sdk',
96 'android_sdk_tools',
97 'android_manifest',
98 'dependencies_res_zips',
99 'resource_dirs',
100 'resource_zip_out',
102 build_utils.CheckOptions(options, parser, required=required_options)
104 if (options.R_dir is None) == (options.srcjar_out is None):
105 raise Exception('Exactly one of --R-dir or --srcjar-out must be specified.')
107 return options
110 def CreateExtraRJavaFiles(r_dir, extra_packages):
111 java_files = build_utils.FindInDirectory(r_dir, "R.java")
112 if len(java_files) != 1:
113 return
114 r_java_file = java_files[0]
115 r_java_contents = open(r_java_file).read()
117 for package in extra_packages:
118 package_r_java_dir = os.path.join(r_dir, *package.split('.'))
119 build_utils.MakeDirectory(package_r_java_dir)
120 package_r_java_path = os.path.join(package_r_java_dir, 'R.java')
121 open(package_r_java_path, 'w').write(
122 re.sub(r'package [.\w]*;', 'package %s;' % package, r_java_contents))
123 # TODO(cjhopman): These extra package's R.java files should be filtered to
124 # only contain the resources listed in their R.txt files. At this point, we
125 # have already compiled those other libraries, so doing this would only
126 # affect how the code in this .apk target could refer to the resources.
129 def CrunchDirectory(aapt, input_dir, output_dir):
130 """Crunches the images in input_dir and its subdirectories into output_dir.
132 If an image is already optimized, crunching often increases image size. In
133 this case, the crunched image is overwritten with the original image.
135 aapt_cmd = [aapt,
136 'crunch',
137 '-C', output_dir,
138 '-S', input_dir,
139 '--ignore-assets', build_utils.AAPT_IGNORE_PATTERN]
140 build_utils.CheckOutput(aapt_cmd, stderr_filter=FilterCrunchStderr,
141 fail_func=DidCrunchFail)
143 # Check for images whose size increased during crunching and replace them
144 # with their originals (except for 9-patches, which must be crunched).
145 for dir_, _, files in os.walk(output_dir):
146 for crunched in files:
147 if crunched.endswith('.9.png'):
148 continue
149 if not crunched.endswith('.png'):
150 raise Exception('Unexpected file in crunched dir: ' + crunched)
151 crunched = os.path.join(dir_, crunched)
152 original = os.path.join(input_dir, os.path.relpath(crunched, output_dir))
153 original_size = os.path.getsize(original)
154 crunched_size = os.path.getsize(crunched)
155 if original_size < crunched_size:
156 shutil.copyfile(original, crunched)
159 def FilterCrunchStderr(stderr):
160 """Filters out lines from aapt crunch's stderr that can safely be ignored."""
161 filtered_lines = []
162 for line in stderr.splitlines(True):
163 # Ignore this libpng warning, which is a known non-error condition.
164 # http://crbug.com/364355
165 if ('libpng warning: iCCP: Not recognizing known sRGB profile that has '
166 + 'been edited' in line):
167 continue
168 filtered_lines.append(line)
169 return ''.join(filtered_lines)
172 def DidCrunchFail(returncode, stderr):
173 """Determines whether aapt crunch failed from its return code and output.
175 Because aapt's return code cannot be trusted, any output to stderr is
176 an indication that aapt has failed (http://crbug.com/314885).
178 return returncode != 0 or stderr
181 def ZipResources(resource_dirs, zip_path):
182 # Python zipfile does not provide a way to replace a file (it just writes
183 # another file with the same name). So, first collect all the files to put
184 # in the zip (with proper overriding), and then zip them.
185 files_to_zip = dict()
186 for d in resource_dirs:
187 for root, _, files in os.walk(d):
188 for f in files:
189 archive_path = os.path.join(os.path.relpath(root, d), f)
190 path = os.path.join(root, f)
191 files_to_zip[archive_path] = path
192 with zipfile.ZipFile(zip_path, 'w') as outzip:
193 for archive_path, path in files_to_zip.iteritems():
194 outzip.write(path, archive_path)
197 def CombineZips(zip_files, output_path):
198 # When packaging resources, if the top-level directories in the zip file are
199 # of the form 0, 1, ..., then each subdirectory will be passed to aapt as a
200 # resources directory. While some resources just clobber others (image files,
201 # etc), other resources (particularly .xml files) need to be more
202 # intelligently merged. That merging is left up to aapt.
203 with zipfile.ZipFile(output_path, 'w') as outzip:
204 for i, z in enumerate(zip_files):
205 with zipfile.ZipFile(z, 'r') as inzip:
206 for name in inzip.namelist():
207 new_name = '%d/%s' % (i, name)
208 outzip.writestr(new_name, inzip.read(name))
211 def main():
212 args = build_utils.ExpandFileArgs(sys.argv[1:])
214 options = ParseArgs(args)
215 android_jar = os.path.join(options.android_sdk, 'android.jar')
216 aapt = os.path.join(options.android_sdk_tools, 'aapt')
218 input_files = []
220 with build_utils.TempDir() as temp_dir:
221 deps_dir = os.path.join(temp_dir, 'deps')
222 build_utils.MakeDirectory(deps_dir)
223 v14_dir = os.path.join(temp_dir, 'v14')
224 build_utils.MakeDirectory(v14_dir)
226 gen_dir = os.path.join(temp_dir, 'gen')
227 build_utils.MakeDirectory(gen_dir)
229 input_resource_dirs = build_utils.ParseGypList(options.resource_dirs)
231 for resource_dir in input_resource_dirs:
232 generate_v14_compatible_resources.GenerateV14Resources(
233 resource_dir,
234 v14_dir,
235 options.v14_verify_only)
237 dep_zips = build_utils.ParseGypList(options.dependencies_res_zips)
238 input_files += dep_zips
239 dep_subdirs = []
240 for z in dep_zips:
241 subdir = os.path.join(deps_dir, os.path.basename(z))
242 if os.path.exists(subdir):
243 raise Exception('Resource zip name conflict: ' + os.path.basename(z))
244 build_utils.ExtractAll(z, path=subdir)
245 dep_subdirs.append(subdir)
247 # Generate R.java. This R.java contains non-final constants and is used only
248 # while compiling the library jar (e.g. chromium_content.jar). When building
249 # an apk, a new R.java file with the correct resource -> ID mappings will be
250 # generated by merging the resources from all libraries and the main apk
251 # project.
252 package_command = [aapt,
253 'package',
254 '-m',
255 '-M', options.android_manifest,
256 '--auto-add-overlay',
257 '-I', android_jar,
258 '--output-text-symbols', gen_dir,
259 '-J', gen_dir,
260 '--ignore-assets', build_utils.AAPT_IGNORE_PATTERN]
262 for d in input_resource_dirs:
263 package_command += ['-S', d]
265 for d in dep_subdirs:
266 package_command += ['-S', d]
268 if options.non_constant_id:
269 package_command.append('--non-constant-id')
270 if options.custom_package:
271 package_command += ['--custom-package', options.custom_package]
272 if options.proguard_file:
273 package_command += ['-G', options.proguard_file]
274 if options.shared_resources:
275 package_command.append('--shared-lib')
276 build_utils.CheckOutput(package_command, print_stderr=False)
278 if options.extra_res_packages:
279 CreateExtraRJavaFiles(
280 gen_dir,
281 build_utils.ParseGypList(options.extra_res_packages))
283 # This is the list of directories with resources to put in the final .zip
284 # file. The order of these is important so that crunched/v14 resources
285 # override the normal ones.
286 zip_resource_dirs = input_resource_dirs + [v14_dir]
288 base_crunch_dir = os.path.join(temp_dir, 'crunch')
290 # Crunch image resources. This shrinks png files and is necessary for
291 # 9-patch images to display correctly. 'aapt crunch' accepts only a single
292 # directory at a time and deletes everything in the output directory.
293 for idx, input_dir in enumerate(input_resource_dirs):
294 crunch_dir = os.path.join(base_crunch_dir, str(idx))
295 build_utils.MakeDirectory(crunch_dir)
296 zip_resource_dirs.append(crunch_dir)
297 CrunchDirectory(aapt, input_dir, crunch_dir)
299 ZipResources(zip_resource_dirs, options.resource_zip_out)
301 if options.all_resources_zip_out:
302 CombineZips([options.resource_zip_out] + dep_zips,
303 options.all_resources_zip_out)
305 if options.R_dir:
306 build_utils.DeleteDirectory(options.R_dir)
307 shutil.copytree(gen_dir, options.R_dir)
308 else:
309 build_utils.ZipDir(options.srcjar_out, gen_dir)
311 if options.depfile:
312 input_files += build_utils.GetPythonDependencies()
313 build_utils.WriteDepfile(options.depfile, input_files)
315 if options.stamp:
316 build_utils.Touch(options.stamp)
319 if __name__ == '__main__':
320 main()