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