summaryrefslogtreecommitdiff
path: root/deps/v8/build/android/gyp/generate_v14_compatible_resources.py
blob: f9e8a3783a6b620bd4bd1913025d13005573081b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
#!/usr/bin/env python
#
# Copyright 2013 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Convert Android xml resources to API 14 compatible.

There are two reasons that we cannot just use API 17 attributes,
so we are generating another set of resources by this script.

1. paddingStart attribute can cause a crash on Galaxy Tab 2.
2. There is a bug that paddingStart does not override paddingLeft on
   JB-MR1. This is fixed on JB-MR2. b/8654490

Therefore, this resource generation script can be removed when
we drop the support for JB-MR1.

Please refer to http://crbug.com/235118 for the details.
"""

import codecs
import os
import re
import shutil
import sys
import xml.dom.minidom as minidom

from util import build_utils

# Note that we are assuming 'android:' is an alias of
# the namespace 'http://schemas.android.com/apk/res/android'.

GRAVITY_ATTRIBUTES = ('android:gravity', 'android:layout_gravity')

# Almost all the attributes that has "Start" or "End" in
# its name should be mapped.
ATTRIBUTES_TO_MAP = {'paddingStart' : 'paddingLeft',
                     'drawableStart' : 'drawableLeft',
                     'layout_alignStart' : 'layout_alignLeft',
                     'layout_marginStart' : 'layout_marginLeft',
                     'layout_alignParentStart' : 'layout_alignParentLeft',
                     'layout_toStartOf' : 'layout_toLeftOf',
                     'paddingEnd' : 'paddingRight',
                     'drawableEnd' : 'drawableRight',
                     'layout_alignEnd' : 'layout_alignRight',
                     'layout_marginEnd' : 'layout_marginRight',
                     'layout_alignParentEnd' : 'layout_alignParentRight',
                     'layout_toEndOf' : 'layout_toRightOf'}

ATTRIBUTES_TO_MAP = dict(['android:' + k, 'android:' + v] for k, v
                         in ATTRIBUTES_TO_MAP.iteritems())

ATTRIBUTES_TO_MAP_REVERSED = dict([v, k] for k, v
                                  in ATTRIBUTES_TO_MAP.iteritems())


def IterateXmlElements(node):
  """minidom helper function that iterates all the element nodes.
  Iteration order is pre-order depth-first."""
  if node.nodeType == node.ELEMENT_NODE:
    yield node
  for child_node in node.childNodes:
    for child_node_element in IterateXmlElements(child_node):
      yield child_node_element


def ParseAndReportErrors(filename):
  try:
    return minidom.parse(filename)
  except Exception: # pylint: disable=broad-except
    import traceback
    traceback.print_exc()
    sys.stderr.write('Failed to parse XML file: %s\n' % filename)
    sys.exit(1)


def AssertNotDeprecatedAttribute(name, value, filename):
  """Raises an exception if the given attribute is deprecated."""
  msg = None
  if name in ATTRIBUTES_TO_MAP_REVERSED:
    msg = '{0} should use {1} instead of {2}'.format(filename,
        ATTRIBUTES_TO_MAP_REVERSED[name], name)
  elif name in GRAVITY_ATTRIBUTES and ('left' in value or 'right' in value):
    msg = '{0} should use start/end instead of left/right for {1}'.format(
        filename, name)

  if msg:
    msg += ('\nFor background, see: http://android-developers.blogspot.com/'
            '2013/03/native-rtl-support-in-android-42.html\n'
            'If you have a legitimate need for this attribute, discuss with '
            'kkimlabs@chromium.org or newt@chromium.org')
    raise Exception(msg)


def WriteDomToFile(dom, filename):
  """Write the given dom to filename."""
  build_utils.MakeDirectory(os.path.dirname(filename))
  with codecs.open(filename, 'w', 'utf-8') as f:
    dom.writexml(f, '', '  ', '\n', encoding='utf-8')


def HasStyleResource(dom):
  """Return True if the dom is a style resource, False otherwise."""
  root_node = IterateXmlElements(dom).next()
  return bool(root_node.nodeName == 'resources' and
              list(root_node.getElementsByTagName('style')))


def ErrorIfStyleResourceExistsInDir(input_dir):
  """If a style resource is in input_dir, raises an exception."""
  for input_filename in build_utils.FindInDirectory(input_dir, '*.xml'):
    dom = ParseAndReportErrors(input_filename)
    if HasStyleResource(dom):
      # Allow style file in third_party to exist in non-v17 directories so long
      # as they do not contain deprecated attributes.
      if not 'third_party' in input_dir or (
          GenerateV14StyleResourceDom(dom, input_filename)):
        raise Exception('error: style file ' + input_filename +
                        ' should be under ' + input_dir +
                        '-v17 directory. Please refer to '
                        'http://crbug.com/243952 for the details.')


def GenerateV14LayoutResourceDom(dom, filename, assert_not_deprecated=True):
  """Convert layout resource to API 14 compatible layout resource.

  Args:
    dom: Parsed minidom object to be modified.
    filename: Filename that the DOM was parsed from.
    assert_not_deprecated: Whether deprecated attributes (e.g. paddingLeft) will
                           cause an exception to be thrown.

  Returns:
    True if dom is modified, False otherwise.
  """
  is_modified = False

  # Iterate all the elements' attributes to find attributes to convert.
  for element in IterateXmlElements(dom):
    for name, value in list(element.attributes.items()):
      # Convert any API 17 Start/End attributes to Left/Right attributes.
      # For example, from paddingStart="10dp" to paddingLeft="10dp"
      # Note: gravity attributes are not necessary to convert because
      # start/end values are backward-compatible. Explained at
      # https://plus.sandbox.google.com/+RomanNurik/posts/huuJd8iVVXY?e=Showroom
      if name in ATTRIBUTES_TO_MAP:
        element.setAttribute(ATTRIBUTES_TO_MAP[name], value)
        del element.attributes[name]
        is_modified = True
      elif assert_not_deprecated:
        AssertNotDeprecatedAttribute(name, value, filename)

  return is_modified


def GenerateV14StyleResourceDom(dom, filename, assert_not_deprecated=True):
  """Convert style resource to API 14 compatible style resource.

  Args:
    dom: Parsed minidom object to be modified.
    filename: Filename that the DOM was parsed from.
    assert_not_deprecated: Whether deprecated attributes (e.g. paddingLeft) will
                           cause an exception to be thrown.

  Returns:
    True if dom is modified, False otherwise.
  """
  is_modified = False

  for style_element in dom.getElementsByTagName('style'):
    for item_element in style_element.getElementsByTagName('item'):
      name = item_element.attributes['name'].value
      value = item_element.childNodes[0].nodeValue
      if name in ATTRIBUTES_TO_MAP:
        item_element.attributes['name'].value = ATTRIBUTES_TO_MAP[name]
        is_modified = True
      elif assert_not_deprecated:
        AssertNotDeprecatedAttribute(name, value, filename)

  return is_modified


def GenerateV14LayoutResource(input_filename, output_v14_filename,
                              output_v17_filename):
  """Convert API 17 layout resource to API 14 compatible layout resource.

  It's mostly a simple replacement, s/Start/Left s/End/Right,
  on the attribute names.
  If the generated resource is identical to the original resource,
  don't do anything. If not, write the generated resource to
  output_v14_filename, and copy the original resource to output_v17_filename.
  """
  dom = ParseAndReportErrors(input_filename)
  is_modified = GenerateV14LayoutResourceDom(dom, input_filename)

  if is_modified:
    # Write the generated resource.
    WriteDomToFile(dom, output_v14_filename)

    # Copy the original resource.
    build_utils.MakeDirectory(os.path.dirname(output_v17_filename))
    shutil.copy2(input_filename, output_v17_filename)


def GenerateV14StyleResource(input_filename, output_v14_filename):
  """Convert API 17 style resources to API 14 compatible style resource.

  Write the generated style resource to output_v14_filename.
  It's mostly a simple replacement, s/Start/Left s/End/Right,
  on the attribute names.
  """
  dom = ParseAndReportErrors(input_filename)
  GenerateV14StyleResourceDom(dom, input_filename)

  # Write the generated resource.
  WriteDomToFile(dom, output_v14_filename)


def GenerateV14LayoutResourcesInDir(input_dir, output_v14_dir, output_v17_dir):
  """Convert layout resources to API 14 compatible resources in input_dir."""
  for input_filename in build_utils.FindInDirectory(input_dir, '*.xml'):
    rel_filename = os.path.relpath(input_filename, input_dir)
    output_v14_filename = os.path.join(output_v14_dir, rel_filename)
    output_v17_filename = os.path.join(output_v17_dir, rel_filename)
    GenerateV14LayoutResource(input_filename, output_v14_filename,
                              output_v17_filename)


def GenerateV14StyleResourcesInDir(input_dir, output_v14_dir):
  """Convert style resources to API 14 compatible resources in input_dir."""
  for input_filename in build_utils.FindInDirectory(input_dir, '*.xml'):
    rel_filename = os.path.relpath(input_filename, input_dir)
    output_v14_filename = os.path.join(output_v14_dir, rel_filename)
    GenerateV14StyleResource(input_filename, output_v14_filename)


def GenerateV14Resources(res_dir, res_v14_dir):
  for name in os.listdir(res_dir):
    if not os.path.isdir(os.path.join(res_dir, name)):
      continue

    dir_pieces = name.split('-')
    resource_type = dir_pieces[0]
    qualifiers = dir_pieces[1:]

    api_level_qualifier_index = -1
    api_level_qualifier = ''
    for index, qualifier in enumerate(qualifiers):
      if re.match('v[0-9]+$', qualifier):
        api_level_qualifier_index = index
        api_level_qualifier = qualifier
        break

    # Android pre-v17 API doesn't support RTL. Skip.
    if 'ldrtl' in qualifiers:
      continue

    input_dir = os.path.abspath(os.path.join(res_dir, name))

    # We also need to copy the original v17 resource to *-v17 directory
    # because the generated v14 resource will hide the original resource.
    output_v14_dir = os.path.join(res_v14_dir, name)
    output_v17_dir = os.path.join(res_v14_dir, name + '-v17')

    # We only convert layout resources under layout*/, xml*/,
    # and style resources under values*/.
    if resource_type in ('layout', 'xml'):
      if not api_level_qualifier:
        GenerateV14LayoutResourcesInDir(input_dir, output_v14_dir,
                                        output_v17_dir)
    elif resource_type == 'values':
      if api_level_qualifier == 'v17':
        output_qualifiers = qualifiers[:]
        del output_qualifiers[api_level_qualifier_index]
        output_v14_dir = os.path.join(res_v14_dir,
                                      '-'.join([resource_type] +
                                               output_qualifiers))
        GenerateV14StyleResourcesInDir(input_dir, output_v14_dir)
      elif not api_level_qualifier:
        ErrorIfStyleResourceExistsInDir(input_dir)