summaryrefslogtreecommitdiffstats
path: root/build/android/gyp/jarjar_resources.py
blob: 67b510ba577fac81ab9d310a320e336c5e804cb6 (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
#!/usr/bin/env python
# Copyright 2014 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.

"""Transforms direct Java class references in Android layout .xml files
according to the specified JarJar rules."""

import optparse
import os
import shutil
import sys
from xml.dom import minidom

from util import build_utils


class JarJarRules(object):
  def __init__(self, jarjar_rules):
    self._rules = []
    for line in jarjar_rules.splitlines():
      rule = line.split()
      if rule[0] != 'rule':
        continue
      _, src, dest = rule
      if src.endswith('**'):
        src_real_name = src[:-2]
      else:
        assert not '*' in src
        src_real_name = src

      if dest.endswith('@0'):
        self._rules.append((src, dest[:-2] + src_real_name))
      elif dest.endswith('@1'):
        assert '**' in src
        self._rules.append((src, dest[:-2]))
      else:
        assert not '@' in dest
        self._rules.append((src, dest))

  def RenameClass(self, class_name):
    for old, new in self._rules:
      if old.endswith('**') and old[:-2] in class_name:
        return class_name.replace(old[:-2], new, 1)
      if '*' not in old and class_name.endswith(old):
        return class_name.replace(old, new, 1)
    return class_name


def RenameNodes(node, rules):
  if node.nodeType == node.ELEMENT_NODE:
    if node.tagName.lower() == 'view' and  node.attributes.has_key('class'):
      node.attributes['class'] = rules.RenameClass(node.attributes['class'])
    else:
      node.tagName = rules.RenameClass(node.tagName)
  for child in node.childNodes:
    RenameNodes(child, rules)


def ProcessLayoutFile(path, rules):
  xmldoc = minidom.parse(path)
  RenameNodes(xmldoc.documentElement, rules)
  with open(path, 'w') as f:
    xmldoc.writexml(f)


def LayoutFilesFilter(src, names):
  if os.path.basename(src).lower() != 'layout':
    return []
  else:
    return filter(lambda n: n.endswith('.xml'), names)


def ProcessResources(options):
  with open(options.rules_path) as f:
    rules = JarJarRules(f.read())

  build_utils.DeleteDirectory(options.output_dir)
  for input_dir in options.input_dir:
    shutil.copytree(input_dir, options.output_dir)

  for root, _dirnames, filenames in os.walk(options.output_dir):
    layout_files = LayoutFilesFilter(root, filenames)
    for layout_file in layout_files:
      ProcessLayoutFile(os.path.join(root, layout_file), rules)


def ParseArgs():
  parser = optparse.OptionParser()
  parser.add_option('--input-dir', action='append',
                    help='Path to the resources folder to process.')
  parser.add_option('--output-dir',
                    help=('Directory to hold processed resources. Note: the ' +
                          'directory will be clobbered on every invocation.'))
  parser.add_option('--rules-path',
                    help='Path to the jarjar rules file.')
  parser.add_option('--stamp', help='Path to touch on success.')

  options, args = parser.parse_args()

  if args:
    parser.error('No positional arguments should be given.')

  # Check that required options have been provided.
  required_options = ('input_dir', 'output_dir', 'rules_path')
  build_utils.CheckOptions(options, parser, required=required_options)

  return options


def main():
  options = ParseArgs()

  ProcessResources(options)

  if options.stamp:
    build_utils.Touch(options.stamp)


if __name__ == '__main__':
  sys.exit(main())