xref: /aosp_15_r20/tools/platform-compat/build/process_compat_config.py (revision b7bfe76a3376ed14ad4000514e7f5aa4f0ee949d)
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