1*b7bfe76aSAndroid Build Coastguard Worker#!/usr/bin/env python3 2*b7bfe76aSAndroid Build Coastguard Worker# 3*b7bfe76aSAndroid Build Coastguard Worker# Copyright (C) 2020 The Android Open Source Project 4*b7bfe76aSAndroid Build Coastguard Worker# 5*b7bfe76aSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 6*b7bfe76aSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 7*b7bfe76aSAndroid Build Coastguard Worker# You may obtain a copy of the License at 8*b7bfe76aSAndroid Build Coastguard Worker# 9*b7bfe76aSAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 10*b7bfe76aSAndroid Build Coastguard Worker# 11*b7bfe76aSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 12*b7bfe76aSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 13*b7bfe76aSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14*b7bfe76aSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 15*b7bfe76aSAndroid Build Coastguard Worker# limitations under the License. 16*b7bfe76aSAndroid Build Coastguard Worker 17*b7bfe76aSAndroid Build Coastguard Worker""" 18*b7bfe76aSAndroid Build Coastguard WorkerExtracts compat_config.xml from built jar files and merges them into a single 19*b7bfe76aSAndroid Build Coastguard WorkerXML file. 20*b7bfe76aSAndroid Build Coastguard Worker""" 21*b7bfe76aSAndroid Build Coastguard Worker 22*b7bfe76aSAndroid Build Coastguard Workerimport argparse 23*b7bfe76aSAndroid Build Coastguard Workerimport collections 24*b7bfe76aSAndroid Build Coastguard Workerimport sys 25*b7bfe76aSAndroid Build Coastguard Workerimport xml.etree.ElementTree as ET 26*b7bfe76aSAndroid Build Coastguard Workerfrom zipfile import ZipFile 27*b7bfe76aSAndroid Build Coastguard Worker 28*b7bfe76aSAndroid Build Coastguard WorkerXmlContent = collections.namedtuple('XmlContent', ['xml', 'source']) 29*b7bfe76aSAndroid Build Coastguard Worker 30*b7bfe76aSAndroid Build Coastguard Workerdef extract_compat_config(jarfile): 31*b7bfe76aSAndroid Build Coastguard Worker """ 32*b7bfe76aSAndroid Build Coastguard Worker Reads all compat_config.xml files from a jarfile. 33*b7bfe76aSAndroid Build Coastguard Worker 34*b7bfe76aSAndroid Build Coastguard Worker Yields: XmlContent for each XML file found. 35*b7bfe76aSAndroid Build Coastguard Worker """ 36*b7bfe76aSAndroid Build Coastguard Worker with ZipFile(jarfile, 'r') as jar: 37*b7bfe76aSAndroid Build Coastguard Worker for info in jar.infolist(): 38*b7bfe76aSAndroid Build Coastguard Worker if info.filename.endswith("_compat_config.xml"): 39*b7bfe76aSAndroid Build Coastguard Worker with jar.open(info.filename, 'r') as xml: 40*b7bfe76aSAndroid Build Coastguard Worker yield XmlContent(xml, info.filename) 41*b7bfe76aSAndroid Build Coastguard Worker 42*b7bfe76aSAndroid Build Coastguard Workerdef change_element_tostring(element): 43*b7bfe76aSAndroid Build Coastguard Worker s = "%s(%s)" % (element.attrib['name'], element.attrib['id']) 44*b7bfe76aSAndroid Build Coastguard Worker metadata = element.find('meta-data') 45*b7bfe76aSAndroid Build Coastguard Worker if metadata is not None: 46*b7bfe76aSAndroid Build Coastguard Worker s += " defined in class %s at %s" % (metadata.attrib['definedIn'], metadata.attrib['sourcePosition']) 47*b7bfe76aSAndroid Build Coastguard Worker return s 48*b7bfe76aSAndroid Build Coastguard Worker 49*b7bfe76aSAndroid Build Coastguard Workerclass ChangeDefinition(collections.namedtuple('ChangeDefinition', ['source', 'element'])): 50*b7bfe76aSAndroid Build Coastguard Worker def __str__(self): 51*b7bfe76aSAndroid Build Coastguard Worker return " From: %s:\n %s" % (self.source, change_element_tostring(self.element)) 52*b7bfe76aSAndroid Build Coastguard Worker 53*b7bfe76aSAndroid Build Coastguard Workerclass ConfigMerger(object): 54*b7bfe76aSAndroid Build Coastguard Worker 55*b7bfe76aSAndroid Build Coastguard Worker def __init__(self, detect_conflicts): 56*b7bfe76aSAndroid Build Coastguard Worker self.tree = ET.ElementTree() 57*b7bfe76aSAndroid Build Coastguard Worker self.tree._setroot(ET.Element("config")) 58*b7bfe76aSAndroid Build Coastguard Worker self.detect_conflicts = detect_conflicts 59*b7bfe76aSAndroid Build Coastguard Worker self.changes_by_id = dict() 60*b7bfe76aSAndroid Build Coastguard Worker self.changes_by_name = dict() 61*b7bfe76aSAndroid Build Coastguard Worker self.errors = 0 62*b7bfe76aSAndroid Build Coastguard Worker self.write_errors_to = sys.stderr 63*b7bfe76aSAndroid Build Coastguard Worker 64*b7bfe76aSAndroid Build Coastguard Worker def merge(self, xmlFile, source): 65*b7bfe76aSAndroid Build Coastguard Worker xml = ET.parse(xmlFile) 66*b7bfe76aSAndroid Build Coastguard Worker for child in xml.getroot(): 67*b7bfe76aSAndroid Build Coastguard Worker if self.detect_conflicts: 68*b7bfe76aSAndroid Build Coastguard Worker id = child.attrib['id'] 69*b7bfe76aSAndroid Build Coastguard Worker name = child.attrib['name'] 70*b7bfe76aSAndroid Build Coastguard Worker this_change = ChangeDefinition(source, child) 71*b7bfe76aSAndroid Build Coastguard Worker if id in self.changes_by_id.keys(): 72*b7bfe76aSAndroid Build Coastguard Worker duplicate = self.changes_by_id[id] 73*b7bfe76aSAndroid Build Coastguard Worker self.write_errors_to.write( 74*b7bfe76aSAndroid Build Coastguard Worker "ERROR: Duplicate definitions for compat change with ID %s:\n%s\n%s\n" % ( 75*b7bfe76aSAndroid Build Coastguard Worker id, duplicate, this_change)) 76*b7bfe76aSAndroid Build Coastguard Worker self.errors += 1 77*b7bfe76aSAndroid Build Coastguard Worker if name in self.changes_by_name.keys(): 78*b7bfe76aSAndroid Build Coastguard Worker duplicate = self.changes_by_name[name] 79*b7bfe76aSAndroid Build Coastguard Worker self.write_errors_to.write( 80*b7bfe76aSAndroid Build Coastguard Worker "ERROR: Duplicate definitions for compat change with name %s:\n%s\n%s\n" % ( 81*b7bfe76aSAndroid Build Coastguard Worker name, duplicate, this_change)) 82*b7bfe76aSAndroid Build Coastguard Worker self.errors += 1 83*b7bfe76aSAndroid Build Coastguard Worker 84*b7bfe76aSAndroid Build Coastguard Worker self.changes_by_id[id] = this_change 85*b7bfe76aSAndroid Build Coastguard Worker self.changes_by_name[name] = this_change 86*b7bfe76aSAndroid Build Coastguard Worker self.tree.getroot().append(child) 87*b7bfe76aSAndroid Build Coastguard Worker 88*b7bfe76aSAndroid Build Coastguard Worker def _check_error(self): 89*b7bfe76aSAndroid Build Coastguard Worker if self.errors > 0: 90*b7bfe76aSAndroid Build Coastguard Worker raise Exception("Failed due to %d earlier errors" % self.errors) 91*b7bfe76aSAndroid Build Coastguard Worker 92*b7bfe76aSAndroid Build Coastguard Worker def write(self, filename): 93*b7bfe76aSAndroid Build Coastguard Worker self._check_error() 94*b7bfe76aSAndroid Build Coastguard Worker ET.indent(self.tree) 95*b7bfe76aSAndroid Build Coastguard Worker self.tree.write(filename, encoding='utf-8', xml_declaration=True) 96*b7bfe76aSAndroid Build Coastguard Worker 97*b7bfe76aSAndroid Build Coastguard Worker def write_device_config(self, filename): 98*b7bfe76aSAndroid Build Coastguard Worker self._check_error() 99*b7bfe76aSAndroid Build Coastguard Worker self.strip_config_for_device().write(filename, encoding='utf-8', xml_declaration=True) 100*b7bfe76aSAndroid Build Coastguard Worker 101*b7bfe76aSAndroid Build Coastguard Worker def strip_config_for_device(self): 102*b7bfe76aSAndroid Build Coastguard Worker new_tree = ET.ElementTree() 103*b7bfe76aSAndroid Build Coastguard Worker new_tree._setroot(ET.Element("config")) 104*b7bfe76aSAndroid Build Coastguard Worker for change in self.tree.getroot(): 105*b7bfe76aSAndroid Build Coastguard Worker new_change = ET.Element("compat-change") 106*b7bfe76aSAndroid Build Coastguard Worker new_change.attrib = change.attrib.copy() 107*b7bfe76aSAndroid Build Coastguard Worker new_tree.getroot().append(new_change) 108*b7bfe76aSAndroid Build Coastguard Worker return new_tree 109*b7bfe76aSAndroid Build Coastguard Worker 110*b7bfe76aSAndroid Build Coastguard Workerdef main(argv): 111*b7bfe76aSAndroid Build Coastguard Worker parser = argparse.ArgumentParser( 112*b7bfe76aSAndroid Build Coastguard Worker description="Processes compat config XML files") 113*b7bfe76aSAndroid Build Coastguard Worker parser.add_argument("--jar", type=argparse.FileType('rb'), action='append', 114*b7bfe76aSAndroid Build Coastguard Worker help="Specifies a jar file to extract compat_config.xml from.") 115*b7bfe76aSAndroid Build Coastguard Worker parser.add_argument("--xml", type=argparse.FileType('rb'), action='append', 116*b7bfe76aSAndroid Build Coastguard Worker help="Specifies an xml file to read compat_config from.") 117*b7bfe76aSAndroid Build Coastguard Worker parser.add_argument("--device-config", dest="device_config", type=argparse.FileType('wb'), 118*b7bfe76aSAndroid Build Coastguard Worker help="Specify where to write config for embedding on the device to. " 119*b7bfe76aSAndroid Build Coastguard Worker "Meta data not needed on the devivce is stripped from this.") 120*b7bfe76aSAndroid Build Coastguard Worker parser.add_argument("--merged-config", dest="merged_config", type=argparse.FileType('wb'), 121*b7bfe76aSAndroid Build Coastguard Worker help="Specify where to write merged config to. This will include metadata.") 122*b7bfe76aSAndroid Build Coastguard Worker parser.add_argument("--allow-duplicates", dest="allow_duplicates", action='store_true', 123*b7bfe76aSAndroid Build Coastguard Worker help="Allow duplicate changed IDs in the merged config.") 124*b7bfe76aSAndroid Build Coastguard Worker 125*b7bfe76aSAndroid Build Coastguard Worker args = parser.parse_args() 126*b7bfe76aSAndroid Build Coastguard Worker 127*b7bfe76aSAndroid Build Coastguard Worker config = ConfigMerger(detect_conflicts = not args.allow_duplicates) 128*b7bfe76aSAndroid Build Coastguard Worker if args.jar: 129*b7bfe76aSAndroid Build Coastguard Worker for jar in args.jar: 130*b7bfe76aSAndroid Build Coastguard Worker for xml_content in extract_compat_config(jar): 131*b7bfe76aSAndroid Build Coastguard Worker config.merge(xml_content.xml, "%s:%s" % (jar.name, xml_content.source)) 132*b7bfe76aSAndroid Build Coastguard Worker if args.xml: 133*b7bfe76aSAndroid Build Coastguard Worker for xml in args.xml: 134*b7bfe76aSAndroid Build Coastguard Worker config.merge(xml, xml.name) 135*b7bfe76aSAndroid Build Coastguard Worker 136*b7bfe76aSAndroid Build Coastguard Worker if args.device_config: 137*b7bfe76aSAndroid Build Coastguard Worker config.write_device_config(args.device_config) 138*b7bfe76aSAndroid Build Coastguard Worker 139*b7bfe76aSAndroid Build Coastguard Worker if args.merged_config: 140*b7bfe76aSAndroid Build Coastguard Worker config.write(args.merged_config) 141*b7bfe76aSAndroid Build Coastguard Worker 142*b7bfe76aSAndroid Build Coastguard Worker 143*b7bfe76aSAndroid Build Coastguard Worker 144*b7bfe76aSAndroid Build Coastguard Workerif __name__ == "__main__": 145*b7bfe76aSAndroid Build Coastguard Worker main(sys.argv) 146