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