- add sources.
[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 WarnIfDeprecatedAttribute(name, value, filename):
69   """print a warning message if the given attribute is deprecated."""
70   if name in ATTRIBUTES_TO_MAP_REVERSED:
71     print >> sys.stderr, ('warning: ' + filename + ' should use ' +
72                           ATTRIBUTES_TO_MAP_REVERSED[name] +
73                           ' instead of ' + name)
74   elif name in GRAVITY_ATTRIBUTES and ('left' in value or 'right' in value):
75     print >> sys.stderr, ('warning: ' + filename +
76                           ' should use start/end instead of left/right for ' +
77                           name)
78
79
80 def WriteDomToFile(dom, filename):
81   """Write the given dom to filename."""
82   build_utils.MakeDirectory(os.path.dirname(filename))
83   with open(filename, 'w') as f:
84     dom.writexml(f, '', '  ', '\n', encoding='utf-8')
85
86
87 def HasStyleResource(dom):
88   """Return True if the dom is a style resource, False otherwise."""
89   root_node = IterateXmlElements(dom).next()
90   return bool(root_node.nodeName == 'resources' and
91               list(root_node.getElementsByTagName('style')))
92
93
94 def ErrorIfStyleResourceExistsInDir(input_dir):
95   """If a style resource is in input_dir, exist with an error message."""
96   for input_filename in build_utils.FindInDirectory(input_dir, '*.xml'):
97     dom = minidom.parse(input_filename)
98     if HasStyleResource(dom):
99       raise Exception('error: style file ' + input_filename +
100                       ' should be under ' + input_dir +
101                       '-v17 directory. Please refer to '
102                       'http://crbug.com/243952 for the details.')
103
104
105 def GenerateV14LayoutResourceDom(dom, filename_for_warning):
106   """Convert layout resource to API 14 compatible layout resource.
107
108   Args:
109     dom: parsed minidom object to be modified.
110     filename_for_warning: file name to display in case we print warnings.
111                           If None, do not print warning.
112   Returns:
113     True if dom is modified, False otherwise.
114   """
115   is_modified = False
116
117   # Iterate all the elements' attributes to find attributes to convert.
118   for element in IterateXmlElements(dom):
119     for name, value in list(element.attributes.items()):
120       # Convert any API 17 Start/End attributes to Left/Right attributes.
121       # For example, from paddingStart="10dp" to paddingLeft="10dp"
122       # Note: gravity attributes are not necessary to convert because
123       # start/end values are backward-compatible. Explained at
124       # https://plus.sandbox.google.com/+RomanNurik/posts/huuJd8iVVXY?e=Showroom
125       if name in ATTRIBUTES_TO_MAP:
126         element.setAttribute(ATTRIBUTES_TO_MAP[name], value)
127         del element.attributes[name]
128         is_modified = True
129       elif filename_for_warning:
130         WarnIfDeprecatedAttribute(name, value, filename_for_warning)
131
132   return is_modified
133
134
135 def GenerateV14StyleResourceDom(dom, filename_for_warning):
136   """Convert style resource to API 14 compatible style resource.
137
138   Args:
139     dom: parsed minidom object to be modified.
140     filename_for_warning: file name to display in case we print warnings.
141                           If None, do not print warning.
142   Returns:
143     True if dom is modified, False otherwise.
144   """
145   is_modified = False
146
147   for style_element in dom.getElementsByTagName('style'):
148     for item_element in style_element.getElementsByTagName('item'):
149       name = item_element.attributes['name'].value
150       value = item_element.childNodes[0].nodeValue
151       if name in ATTRIBUTES_TO_MAP:
152         item_element.attributes['name'].value = ATTRIBUTES_TO_MAP[name]
153         is_modified = True
154       elif filename_for_warning:
155         WarnIfDeprecatedAttribute(name, value, filename_for_warning)
156
157   return is_modified
158
159
160 def GenerateV14LayoutResource(input_filename, output_v14_filename,
161                               output_v17_filename):
162   """Convert API 17 layout resource to API 14 compatible layout resource.
163
164   It's mostly a simple replacement, s/Start/Left s/End/Right,
165   on the attribute names.
166   If the generated resource is identical to the original resource,
167   don't do anything. If not, write the generated resource to
168   output_v14_filename, and copy the original resource to output_v17_filename.
169   """
170   dom = minidom.parse(input_filename)
171   is_modified = GenerateV14LayoutResourceDom(dom, input_filename)
172
173   if is_modified:
174     # Write the generated resource.
175     WriteDomToFile(dom, output_v14_filename)
176
177     # Copy the original resource.
178     build_utils.MakeDirectory(os.path.dirname(output_v17_filename))
179     shutil.copy2(input_filename, output_v17_filename)
180
181
182 def GenerateV14StyleResource(input_filename, output_v14_filename):
183   """Convert API 17 style resources to API 14 compatible style resource.
184
185   Write the generated style resource to output_v14_filename.
186   It's mostly a simple replacement, s/Start/Left s/End/Right,
187   on the attribute names.
188   """
189   dom = minidom.parse(input_filename)
190   GenerateV14StyleResourceDom(dom, input_filename)
191
192   # Write the generated resource.
193   WriteDomToFile(dom, output_v14_filename)
194
195
196 def GenerateV14LayoutResourcesInDir(input_dir, output_v14_dir, output_v17_dir):
197   """Convert layout resources to API 14 compatible resources in input_dir."""
198   for input_filename in build_utils.FindInDirectory(input_dir, '*.xml'):
199     rel_filename = os.path.relpath(input_filename, input_dir)
200     output_v14_filename = os.path.join(output_v14_dir, rel_filename)
201     output_v17_filename = os.path.join(output_v17_dir, rel_filename)
202     GenerateV14LayoutResource(input_filename, output_v14_filename,
203                               output_v17_filename)
204
205
206 def GenerateV14StyleResourcesInDir(input_dir, output_v14_dir):
207   """Convert style resources to API 14 compatible resources in input_dir."""
208   for input_filename in build_utils.FindInDirectory(input_dir, '*.xml'):
209     rel_filename = os.path.relpath(input_filename, input_dir)
210     output_v14_filename = os.path.join(output_v14_dir, rel_filename)
211     GenerateV14StyleResource(input_filename, output_v14_filename)
212
213
214 def VerifyV14ResourcesInDir(input_dir, resource_type):
215   """Verify that the resources in input_dir is compatible with v14, i.e., they
216   don't use attributes that cause crashes on certain devices. Print an error if
217   they have."""
218   for input_filename in build_utils.FindInDirectory(input_dir, '*.xml'):
219     exception_message = ('error : ' + input_filename + ' has an RTL attribute, '
220                         'i.e., attribute that has "start" or "end" in its name.'
221                         ' Pre-v17 resources should not include it because it '
222                         'can cause crashes on certain devices. Please refer to '
223                         'http://crbug.com/243952 for the details.')
224     dom = minidom.parse(input_filename)
225     if resource_type in ('layout', 'xml'):
226       if GenerateV14LayoutResourceDom(dom, None):
227         raise Exception(exception_message)
228     elif resource_type == 'values':
229       if GenerateV14StyleResourceDom(dom, None):
230         raise Exception(exception_message)
231
232
233 def WarnIfDeprecatedAttributeInDir(input_dir, resource_type):
234   """Print warning if resources in input_dir have deprecated attributes, e.g.,
235   paddingLeft, PaddingRight"""
236   for input_filename in build_utils.FindInDirectory(input_dir, '*.xml'):
237     dom = minidom.parse(input_filename)
238     if resource_type in ('layout', 'xml'):
239       GenerateV14LayoutResourceDom(dom, input_filename)
240     elif resource_type == 'values':
241       GenerateV14StyleResourceDom(dom, input_filename)
242
243
244 def ParseArgs():
245   """Parses command line options.
246
247   Returns:
248     An options object as from optparse.OptionsParser.parse_args()
249   """
250   parser = optparse.OptionParser()
251   parser.add_option('--res-dir',
252                     help='directory containing resources '
253                          'used to generate v14 compatible resources')
254   parser.add_option('--res-v14-compatibility-dir',
255                     help='output directory into which '
256                          'v14 compatible resources will be generated')
257   parser.add_option('--stamp', help='File to touch on success')
258   parser.add_option('--verify-only', action="store_true", help='Do not generate'
259       ' v14 resources. Instead, just verify that the resources are already '
260       "compatible with v14, i.e. they don't use attributes that cause crashes "
261       'on certain devices.')
262
263   options, args = parser.parse_args()
264
265   if args:
266     parser.error('No positional arguments should be given.')
267
268   # Check that required options have been provided.
269   required_options = ('res_dir', 'res_v14_compatibility_dir')
270   build_utils.CheckOptions(options, parser, required=required_options)
271   return options
272
273
274 def main(argv):
275   options = ParseArgs()
276
277   build_utils.DeleteDirectory(options.res_v14_compatibility_dir)
278   build_utils.MakeDirectory(options.res_v14_compatibility_dir)
279
280   for name in os.listdir(options.res_dir):
281     if not os.path.isdir(os.path.join(options.res_dir, name)):
282       continue
283
284     dir_pieces = name.split('-')
285     resource_type = dir_pieces[0]
286     qualifiers = dir_pieces[1:]
287
288     api_level_qualifier_index = -1
289     api_level_qualifier = ''
290     for index, qualifier in enumerate(qualifiers):
291       if re.match('v[0-9]+$', qualifier):
292         api_level_qualifier_index = index
293         api_level_qualifier = qualifier
294         break
295
296     # Android pre-v17 API doesn't support RTL. Skip.
297     if 'ldrtl' in qualifiers:
298       continue
299
300     input_dir = os.path.abspath(os.path.join(options.res_dir, name))
301
302     if options.verify_only:
303       if not api_level_qualifier or int(api_level_qualifier[1:]) < 17:
304         VerifyV14ResourcesInDir(input_dir, resource_type)
305       else:
306         WarnIfDeprecatedAttributeInDir(input_dir, resource_type)
307     else:
308       # We also need to copy the original v17 resource to *-v17 directory
309       # because the generated v14 resource will hide the original resource.
310       output_v14_dir = os.path.join(options.res_v14_compatibility_dir, name)
311       output_v17_dir = os.path.join(options.res_v14_compatibility_dir, name +
312                                                                        '-v17')
313
314       # We only convert layout resources under layout*/, xml*/,
315       # and style resources under values*/.
316       if resource_type in ('layout', 'xml'):
317         if not api_level_qualifier:
318           GenerateV14LayoutResourcesInDir(input_dir, output_v14_dir,
319                                           output_v17_dir)
320       elif resource_type == 'values':
321         if api_level_qualifier == 'v17':
322           output_qualifiers = qualifiers[:]
323           del output_qualifiers[api_level_qualifier_index]
324           output_v14_dir = os.path.join(options.res_v14_compatibility_dir,
325                                         '-'.join([resource_type] +
326                                                  output_qualifiers))
327           GenerateV14StyleResourcesInDir(input_dir, output_v14_dir)
328         elif not api_level_qualifier:
329           ErrorIfStyleResourceExistsInDir(input_dir)
330
331   if options.stamp:
332     build_utils.Touch(options.stamp)
333
334 if __name__ == '__main__':
335   sys.exit(main(sys.argv))
336