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