Upstream version 11.40.277.0
[platform/framework/web/crosswalk.git] / src / build / android / gyp / generate_v14_compatible_resources.py
1 #!/usr/bin/env python
2 #
3 # Copyright 2013 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 """Convert Android xml resources to API 14 compatible.
8
9 There are two reasons that we cannot just use API 17 attributes,
10 so we are generating another set of resources by this script.
11
12 1. paddingStart attribute can cause a crash on Galaxy Tab 2.
13 2. There is a bug that paddingStart does not override paddingLeft on
14    JB-MR1. This is fixed on JB-MR2.
15
16 Therefore, this resource generation script can be removed when
17 we drop the support for JB-MR1.
18
19 Please refer to http://crbug.com/235118 for the details.
20 """
21
22 import optparse
23 import os
24 import re
25 import shutil
26 import sys
27 import xml.dom.minidom as minidom
28
29 from util import build_utils
30
31 # Note that we are assuming 'android:' is an alias of
32 # the namespace 'http://schemas.android.com/apk/res/android'.
33
34 GRAVITY_ATTRIBUTES = ('android:gravity', 'android:layout_gravity')
35
36 # Almost all the attributes that has "Start" or "End" in
37 # its name should be mapped.
38 ATTRIBUTES_TO_MAP = {'paddingStart' : 'paddingLeft',
39                      'drawableStart' : 'drawableLeft',
40                      'layout_alignStart' : 'layout_alignLeft',
41                      'layout_marginStart' : 'layout_marginLeft',
42                      'layout_alignParentStart' : 'layout_alignParentLeft',
43                      'layout_toStartOf' : 'layout_toLeftOf',
44                      'paddingEnd' : 'paddingRight',
45                      'drawableEnd' : 'drawableRight',
46                      'layout_alignEnd' : 'layout_alignRight',
47                      'layout_marginEnd' : 'layout_marginRight',
48                      'layout_alignParentEnd' : 'layout_alignParentRight',
49                      'layout_toEndOf' : 'layout_toRightOf'}
50
51 ATTRIBUTES_TO_MAP = dict(['android:' + k, 'android:' + v] for k, v
52                          in ATTRIBUTES_TO_MAP.iteritems())
53
54 ATTRIBUTES_TO_MAP_REVERSED = dict([v, k] for k, v
55                                   in ATTRIBUTES_TO_MAP.iteritems())
56
57
58 def IterateXmlElements(node):
59   """minidom helper function that iterates all the element nodes.
60   Iteration order is pre-order depth-first."""
61   if node.nodeType == node.ELEMENT_NODE:
62     yield node
63   for child_node in node.childNodes:
64     for child_node_element in IterateXmlElements(child_node):
65       yield child_node_element
66
67
68 def ParseAndReportErrors(filename):
69   try:
70     return minidom.parse(filename)
71   except Exception:
72     import traceback
73     traceback.print_exc()
74     sys.stderr.write('Failed to parse XML file: %s\n' % filename)
75     sys.exit(1)
76
77
78 def AssertNotDeprecatedAttribute(name, value, filename):
79   """Raises an exception if the given attribute is deprecated."""
80   msg = None
81   if name in ATTRIBUTES_TO_MAP_REVERSED:
82     msg = '{0} should use {1} instead of {2}'.format(filename,
83         ATTRIBUTES_TO_MAP_REVERSED[name], name)
84   elif name in GRAVITY_ATTRIBUTES and ('left' in value or 'right' in value):
85     msg = '{0} should use start/end instead of left/right for {1}'.format(
86         filename, name)
87
88   if msg:
89     msg += ('\nFor background, see: http://android-developers.blogspot.com/'
90             '2013/03/native-rtl-support-in-android-42.html\n'
91             'If you have a legitimate need for this attribute, discuss with '
92             'kkimlabs@chromium.org or newt@chromium.org')
93     raise Exception(msg)
94
95
96 def WriteDomToFile(dom, filename):
97   """Write the given dom to filename."""
98   build_utils.MakeDirectory(os.path.dirname(filename))
99   with open(filename, 'w') as f:
100     dom.writexml(f, '', '  ', '\n', encoding='utf-8')
101
102
103 def HasStyleResource(dom):
104   """Return True if the dom is a style resource, False otherwise."""
105   root_node = IterateXmlElements(dom).next()
106   return bool(root_node.nodeName == 'resources' and
107               list(root_node.getElementsByTagName('style')))
108
109
110 def ErrorIfStyleResourceExistsInDir(input_dir):
111   """If a style resource is in input_dir, raises an exception."""
112   for input_filename in build_utils.FindInDirectory(input_dir, '*.xml'):
113     dom = ParseAndReportErrors(input_filename)
114     if HasStyleResource(dom):
115       raise Exception('error: style file ' + input_filename +
116                       ' should be under ' + input_dir +
117                       '-v17 directory. Please refer to '
118                       'http://crbug.com/243952 for the details.')
119
120
121 def GenerateV14LayoutResourceDom(dom, filename, assert_not_deprecated=True):
122   """Convert layout resource to API 14 compatible layout resource.
123
124   Args:
125     dom: Parsed minidom object to be modified.
126     filename: Filename that the DOM was parsed from.
127     assert_not_deprecated: Whether deprecated attributes (e.g. paddingLeft) will
128                            cause an exception to be thrown.
129
130   Returns:
131     True if dom is modified, False otherwise.
132   """
133   is_modified = False
134
135   # Iterate all the elements' attributes to find attributes to convert.
136   for element in IterateXmlElements(dom):
137     for name, value in list(element.attributes.items()):
138       # Convert any API 17 Start/End attributes to Left/Right attributes.
139       # For example, from paddingStart="10dp" to paddingLeft="10dp"
140       # Note: gravity attributes are not necessary to convert because
141       # start/end values are backward-compatible. Explained at
142       # https://plus.sandbox.google.com/+RomanNurik/posts/huuJd8iVVXY?e=Showroom
143       if name in ATTRIBUTES_TO_MAP:
144         element.setAttribute(ATTRIBUTES_TO_MAP[name], value)
145         del element.attributes[name]
146         is_modified = True
147       elif assert_not_deprecated:
148         AssertNotDeprecatedAttribute(name, value, filename)
149
150   return is_modified
151
152
153 def GenerateV14StyleResourceDom(dom, filename, assert_not_deprecated=True):
154   """Convert style resource to API 14 compatible style resource.
155
156   Args:
157     dom: Parsed minidom object to be modified.
158     filename: Filename that the DOM was parsed from.
159     assert_not_deprecated: Whether deprecated attributes (e.g. paddingLeft) will
160                            cause an exception to be thrown.
161
162   Returns:
163     True if dom is modified, False otherwise.
164   """
165   is_modified = False
166
167   for style_element in dom.getElementsByTagName('style'):
168     for item_element in style_element.getElementsByTagName('item'):
169       name = item_element.attributes['name'].value
170       value = item_element.childNodes[0].nodeValue
171       if name in ATTRIBUTES_TO_MAP:
172         item_element.attributes['name'].value = ATTRIBUTES_TO_MAP[name]
173         is_modified = True
174       elif assert_not_deprecated:
175         AssertNotDeprecatedAttribute(name, value, filename)
176
177   return is_modified
178
179
180 def GenerateV14LayoutResource(input_filename, output_v14_filename,
181                               output_v17_filename):
182   """Convert API 17 layout resource to API 14 compatible layout resource.
183
184   It's mostly a simple replacement, s/Start/Left s/End/Right,
185   on the attribute names.
186   If the generated resource is identical to the original resource,
187   don't do anything. If not, write the generated resource to
188   output_v14_filename, and copy the original resource to output_v17_filename.
189   """
190   dom = ParseAndReportErrors(input_filename)
191   is_modified = GenerateV14LayoutResourceDom(dom, input_filename)
192
193   if is_modified:
194     # Write the generated resource.
195     WriteDomToFile(dom, output_v14_filename)
196
197     # Copy the original resource.
198     build_utils.MakeDirectory(os.path.dirname(output_v17_filename))
199     shutil.copy2(input_filename, output_v17_filename)
200
201
202 def GenerateV14StyleResource(input_filename, output_v14_filename):
203   """Convert API 17 style resources to API 14 compatible style resource.
204
205   Write the generated style resource to output_v14_filename.
206   It's mostly a simple replacement, s/Start/Left s/End/Right,
207   on the attribute names.
208   """
209   dom = ParseAndReportErrors(input_filename)
210   GenerateV14StyleResourceDom(dom, input_filename)
211
212   # Write the generated resource.
213   WriteDomToFile(dom, output_v14_filename)
214
215
216 def GenerateV14LayoutResourcesInDir(input_dir, output_v14_dir, output_v17_dir):
217   """Convert layout resources to API 14 compatible resources in input_dir."""
218   for input_filename in build_utils.FindInDirectory(input_dir, '*.xml'):
219     rel_filename = os.path.relpath(input_filename, input_dir)
220     output_v14_filename = os.path.join(output_v14_dir, rel_filename)
221     output_v17_filename = os.path.join(output_v17_dir, rel_filename)
222     GenerateV14LayoutResource(input_filename, output_v14_filename,
223                               output_v17_filename)
224
225
226 def GenerateV14StyleResourcesInDir(input_dir, output_v14_dir):
227   """Convert style resources to API 14 compatible resources in input_dir."""
228   for input_filename in build_utils.FindInDirectory(input_dir, '*.xml'):
229     rel_filename = os.path.relpath(input_filename, input_dir)
230     output_v14_filename = os.path.join(output_v14_dir, rel_filename)
231     GenerateV14StyleResource(input_filename, output_v14_filename)
232
233
234 def VerifyV14ResourcesInDir(input_dir, resource_type):
235   """Verify that the resources in input_dir is compatible with v14, i.e., they
236   don't use attributes that cause crashes on certain devices. Print an error if
237   they have."""
238   for input_filename in build_utils.FindInDirectory(input_dir, '*.xml'):
239     exception_message = ('error : ' + input_filename + ' has an RTL attribute, '
240                         'i.e., attribute that has "start" or "end" in its name.'
241                         ' Pre-v17 resources should not include it because it '
242                         'can cause crashes on certain devices. Please refer to '
243                         'http://crbug.com/243952 for the details.')
244     dom = ParseAndReportErrors(input_filename)
245     if resource_type in ('layout', 'xml'):
246       if GenerateV14LayoutResourceDom(dom, input_filename, False):
247         raise Exception(exception_message)
248     elif resource_type == 'values':
249       if GenerateV14StyleResourceDom(dom, input_filename, False):
250         raise Exception(exception_message)
251
252
253 def AssertNoDeprecatedAttributesInDir(input_dir, resource_type):
254   """Raises an exception if resources in input_dir have deprecated attributes,
255   e.g., paddingLeft, paddingRight"""
256   for input_filename in build_utils.FindInDirectory(input_dir, '*.xml'):
257     dom = ParseAndReportErrors(input_filename)
258     if resource_type in ('layout', 'xml'):
259       GenerateV14LayoutResourceDom(dom, input_filename)
260     elif resource_type == 'values':
261       GenerateV14StyleResourceDom(dom, input_filename)
262
263
264 def ParseArgs():
265   """Parses command line options.
266
267   Returns:
268     An options object as from optparse.OptionsParser.parse_args()
269   """
270   parser = optparse.OptionParser()
271   parser.add_option('--res-dir',
272                     help='directory containing resources '
273                          'used to generate v14 compatible resources')
274   parser.add_option('--res-v14-compatibility-dir',
275                     help='output directory into which '
276                          'v14 compatible resources will be generated')
277   parser.add_option('--stamp', help='File to touch on success')
278   parser.add_option('--verify-only', action="store_true", help='Do not generate'
279       ' v14 resources. Instead, just verify that the resources are already '
280       "compatible with v14, i.e. they don't use attributes that cause crashes "
281       'on certain devices.')
282
283   options, args = parser.parse_args()
284
285   if args:
286     parser.error('No positional arguments should be given.')
287
288   # Check that required options have been provided.
289   required_options = ('res_dir', 'res_v14_compatibility_dir')
290   build_utils.CheckOptions(options, parser, required=required_options)
291   return options
292
293 def GenerateV14Resources(res_dir, res_v14_dir, verify_only):
294   for name in os.listdir(res_dir):
295     if not os.path.isdir(os.path.join(res_dir, name)):
296       continue
297
298     dir_pieces = name.split('-')
299     resource_type = dir_pieces[0]
300     qualifiers = dir_pieces[1:]
301
302     api_level_qualifier_index = -1
303     api_level_qualifier = ''
304     for index, qualifier in enumerate(qualifiers):
305       if re.match('v[0-9]+$', qualifier):
306         api_level_qualifier_index = index
307         api_level_qualifier = qualifier
308         break
309
310     # Android pre-v17 API doesn't support RTL. Skip.
311     if 'ldrtl' in qualifiers:
312       continue
313
314     input_dir = os.path.abspath(os.path.join(res_dir, name))
315
316     if verify_only:
317       if not api_level_qualifier or int(api_level_qualifier[1:]) < 17:
318         VerifyV14ResourcesInDir(input_dir, resource_type)
319       else:
320         AssertNoDeprecatedAttributesInDir(input_dir, resource_type)
321     else:
322       # We also need to copy the original v17 resource to *-v17 directory
323       # because the generated v14 resource will hide the original resource.
324       output_v14_dir = os.path.join(res_v14_dir, name)
325       output_v17_dir = os.path.join(res_v14_dir, name + '-v17')
326
327       # We only convert layout resources under layout*/, xml*/,
328       # and style resources under values*/.
329       if resource_type in ('layout', 'xml'):
330         if not api_level_qualifier:
331           GenerateV14LayoutResourcesInDir(input_dir, output_v14_dir,
332                                           output_v17_dir)
333       elif resource_type == 'values':
334         if api_level_qualifier == 'v17':
335           output_qualifiers = qualifiers[:]
336           del output_qualifiers[api_level_qualifier_index]
337           output_v14_dir = os.path.join(res_v14_dir,
338                                         '-'.join([resource_type] +
339                                                  output_qualifiers))
340           GenerateV14StyleResourcesInDir(input_dir, output_v14_dir)
341         elif not api_level_qualifier:
342           ErrorIfStyleResourceExistsInDir(input_dir)
343
344 def main():
345   options = ParseArgs()
346
347   res_v14_dir = options.res_v14_compatibility_dir
348
349   build_utils.DeleteDirectory(res_v14_dir)
350   build_utils.MakeDirectory(res_v14_dir)
351
352   GenerateV14Resources(options.res_dir, res_v14_dir, options.verify_only)
353
354   if options.stamp:
355     build_utils.Touch(options.stamp)
356
357 if __name__ == '__main__':
358   sys.exit(main())
359