You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
145 lines
5.5 KiB
145 lines
5.5 KiB
7 months ago
|
#!/usr/bin/env python
|
||
|
#
|
||
|
# Copyright (C) 2020 The Android Open Source Project
|
||
|
#
|
||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
# you may not use this file except in compliance with the License.
|
||
|
# You may obtain a copy of the License at
|
||
|
#
|
||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||
|
#
|
||
|
# Unless required by applicable law or agreed to in writing, software
|
||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
# See the License for the specific language governing permissions and
|
||
|
# limitations under the License.
|
||
|
|
||
|
"""
|
||
|
Extracts compat_config.xml from built jar files and merges them into a single
|
||
|
XML file.
|
||
|
"""
|
||
|
|
||
|
import argparse
|
||
|
import collections
|
||
|
import sys
|
||
|
import xml.etree.ElementTree as ET
|
||
|
from zipfile import ZipFile
|
||
|
|
||
|
XmlContent = collections.namedtuple('XmlContent', ['xml', 'source'])
|
||
|
|
||
|
def extract_compat_config(jarfile):
|
||
|
"""
|
||
|
Reads all compat_config.xml files from a jarfile.
|
||
|
|
||
|
Yields: XmlContent for each XML file found.
|
||
|
"""
|
||
|
with ZipFile(jarfile, 'r') as jar:
|
||
|
for info in jar.infolist():
|
||
|
if info.filename.endswith("_compat_config.xml"):
|
||
|
with jar.open(info.filename, 'r') as xml:
|
||
|
yield XmlContent(xml, info.filename)
|
||
|
|
||
|
def change_element_tostring(element):
|
||
|
s = "%s(%s)" % (element.attrib['name'], element.attrib['id'])
|
||
|
metadata = element.find('meta-data')
|
||
|
if metadata is not None:
|
||
|
s += " defined in class %s at %s" % (metadata.attrib['definedIn'], metadata.attrib['sourcePosition'])
|
||
|
return s
|
||
|
|
||
|
class ChangeDefinition(collections.namedtuple('ChangeDefinition', ['source', 'element'])):
|
||
|
def __str__(self):
|
||
|
return " From: %s:\n %s" % (self.source, change_element_tostring(self.element))
|
||
|
|
||
|
class ConfigMerger(object):
|
||
|
|
||
|
def __init__(self, detect_conflicts):
|
||
|
self.tree = ET.ElementTree()
|
||
|
self.tree._setroot(ET.Element("config"))
|
||
|
self.detect_conflicts = detect_conflicts
|
||
|
self.changes_by_id = dict()
|
||
|
self.changes_by_name = dict()
|
||
|
self.errors = 0
|
||
|
self.write_errors_to = sys.stderr
|
||
|
|
||
|
def merge(self, xmlFile, source):
|
||
|
xml = ET.parse(xmlFile)
|
||
|
for child in xml.getroot():
|
||
|
if self.detect_conflicts:
|
||
|
id = child.attrib['id']
|
||
|
name = child.attrib['name']
|
||
|
this_change = ChangeDefinition(source, child)
|
||
|
if id in self.changes_by_id.keys():
|
||
|
duplicate = self.changes_by_id[id]
|
||
|
self.write_errors_to.write(
|
||
|
"ERROR: Duplicate definitions for compat change with ID %s:\n%s\n%s\n" % (
|
||
|
id, duplicate, this_change))
|
||
|
self.errors += 1
|
||
|
if name in self.changes_by_name.keys():
|
||
|
duplicate = self.changes_by_name[name]
|
||
|
self.write_errors_to.write(
|
||
|
"ERROR: Duplicate definitions for compat change with name %s:\n%s\n%s\n" % (
|
||
|
name, duplicate, this_change))
|
||
|
self.errors += 1
|
||
|
|
||
|
self.changes_by_id[id] = this_change
|
||
|
self.changes_by_name[name] = this_change
|
||
|
self.tree.getroot().append(child)
|
||
|
|
||
|
def _check_error(self):
|
||
|
if self.errors > 0:
|
||
|
raise Exception("Failed due to %d earlier errors" % self.errors)
|
||
|
|
||
|
def write(self, filename):
|
||
|
self._check_error()
|
||
|
self.tree.write(filename, encoding='utf-8', xml_declaration=True)
|
||
|
|
||
|
def write_device_config(self, filename):
|
||
|
self._check_error()
|
||
|
self.strip_config_for_device().write(filename, encoding='utf-8', xml_declaration=True)
|
||
|
|
||
|
def strip_config_for_device(self):
|
||
|
new_tree = ET.ElementTree()
|
||
|
new_tree._setroot(ET.Element("config"))
|
||
|
for change in self.tree.getroot():
|
||
|
new_change = ET.Element("compat-change")
|
||
|
new_change.attrib = change.attrib.copy()
|
||
|
new_tree.getroot().append(new_change)
|
||
|
return new_tree
|
||
|
|
||
|
def main(argv):
|
||
|
parser = argparse.ArgumentParser(
|
||
|
description="Processes compat config XML files")
|
||
|
parser.add_argument("--jar", type=argparse.FileType('rb'), action='append',
|
||
|
help="Specifies a jar file to extract compat_config.xml from.")
|
||
|
parser.add_argument("--xml", type=argparse.FileType('rb'), action='append',
|
||
|
help="Specifies an xml file to read compat_config from.")
|
||
|
parser.add_argument("--device-config", dest="device_config", type=argparse.FileType('wb'),
|
||
|
help="Specify where to write config for embedding on the device to. "
|
||
|
"Meta data not needed on the devivce is stripped from this.")
|
||
|
parser.add_argument("--merged-config", dest="merged_config", type=argparse.FileType('wb'),
|
||
|
help="Specify where to write merged config to. This will include metadata.")
|
||
|
parser.add_argument("--allow-duplicates", dest="allow_duplicates", action='store_true',
|
||
|
help="Allow duplicate changed IDs in the merged config.")
|
||
|
|
||
|
args = parser.parse_args()
|
||
|
|
||
|
config = ConfigMerger(detect_conflicts = not args.allow_duplicates)
|
||
|
if args.jar:
|
||
|
for jar in args.jar:
|
||
|
for xml_content in extract_compat_config(jar):
|
||
|
config.merge(xml_content.xml, "%s:%s" % (jar.name, xml_content.source))
|
||
|
if args.xml:
|
||
|
for xml in args.xml:
|
||
|
config.merge(xml, xml.name)
|
||
|
|
||
|
if args.device_config:
|
||
|
config.write_device_config(args.device_config)
|
||
|
|
||
|
if args.merged_config:
|
||
|
config.write(args.merged_config)
|
||
|
|
||
|
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
main(sys.argv)
|