blob: 67b510ba577fac81ab9d310a320e336c5e804cb6 [file] [log] [blame]
#!/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())