Home | History | Annotate | Download | only in gyp
      1 #!/usr/bin/env python
      2 # Copyright 2014 The Chromium Authors. All rights reserved.
      3 # Use of this source code is governed by a BSD-style license that can be
      4 # found in the LICENSE file.
      5 
      6 """Transforms direct Java class references in Android layout .xml files
      7 according to the specified JarJar rules."""
      8 
      9 import optparse
     10 import os
     11 import shutil
     12 import sys
     13 from xml.dom import minidom
     14 
     15 from util import build_utils
     16 
     17 
     18 class JarJarRules(object):
     19   def __init__(self, jarjar_rules):
     20     self._rules = []
     21     for line in jarjar_rules.splitlines():
     22       rule = line.split()
     23       if rule[0] != 'rule':
     24         continue
     25       _, src, dest = rule
     26       if src.endswith('**'):
     27         src_real_name = src[:-2]
     28       else:
     29         assert not '*' in src
     30         src_real_name = src
     31 
     32       if dest.endswith('@0'):
     33         self._rules.append((src, dest[:-2] + src_real_name))
     34       elif dest.endswith('@1'):
     35         assert '**' in src
     36         self._rules.append((src, dest[:-2]))
     37       else:
     38         assert not '@' in dest
     39         self._rules.append((src, dest))
     40 
     41   def RenameClass(self, class_name):
     42     for old, new in self._rules:
     43       if old.endswith('**') and old[:-2] in class_name:
     44         return class_name.replace(old[:-2], new, 1)
     45       if '*' not in old and class_name.endswith(old):
     46         return class_name.replace(old, new, 1)
     47     return class_name
     48 
     49 
     50 def RenameNodes(node, rules):
     51   if node.nodeType == node.ELEMENT_NODE:
     52     if node.tagName.lower() == 'view' and  node.attributes.has_key('class'):
     53       node.attributes['class'] = rules.RenameClass(node.attributes['class'])
     54     else:
     55       node.tagName = rules.RenameClass(node.tagName)
     56   for child in node.childNodes:
     57     RenameNodes(child, rules)
     58 
     59 
     60 def ProcessLayoutFile(path, rules):
     61   xmldoc = minidom.parse(path)
     62   RenameNodes(xmldoc.documentElement, rules)
     63   with open(path, 'w') as f:
     64     xmldoc.writexml(f)
     65 
     66 
     67 def LayoutFilesFilter(src, names):
     68   if os.path.basename(src).lower() != 'layout':
     69     return []
     70   else:
     71     return filter(lambda n: n.endswith('.xml'), names)
     72 
     73 
     74 def ProcessResources(options):
     75   with open(options.rules_path) as f:
     76     rules = JarJarRules(f.read())
     77 
     78   build_utils.DeleteDirectory(options.output_dir)
     79   for input_dir in options.input_dir:
     80     shutil.copytree(input_dir, options.output_dir)
     81 
     82   for root, _dirnames, filenames in os.walk(options.output_dir):
     83     layout_files = LayoutFilesFilter(root, filenames)
     84     for layout_file in layout_files:
     85       ProcessLayoutFile(os.path.join(root, layout_file), rules)
     86 
     87 
     88 def ParseArgs():
     89   parser = optparse.OptionParser()
     90   parser.add_option('--input-dir', action='append',
     91                     help='Path to the resources folder to process.')
     92   parser.add_option('--output-dir',
     93                     help=('Directory to hold processed resources. Note: the ' +
     94                           'directory will be clobbered on every invocation.'))
     95   parser.add_option('--rules-path',
     96                     help='Path to the jarjar rules file.')
     97   parser.add_option('--stamp', help='Path to touch on success.')
     98 
     99   options, args = parser.parse_args()
    100 
    101   if args:
    102     parser.error('No positional arguments should be given.')
    103 
    104   # Check that required options have been provided.
    105   required_options = ('input_dir', 'output_dir', 'rules_path')
    106   build_utils.CheckOptions(options, parser, required=required_options)
    107 
    108   return options
    109 
    110 
    111 def main():
    112   options = ParseArgs()
    113 
    114   ProcessResources(options)
    115 
    116   if options.stamp:
    117     build_utils.Touch(options.stamp)
    118 
    119 
    120 if __name__ == '__main__':
    121   sys.exit(main())
    122