1*d57664e9SAndroid Build Coastguard Worker#!/usr/bin/env python3 2*d57664e9SAndroid Build Coastguard Worker# 3*d57664e9SAndroid Build Coastguard Worker# Copyright (C) 2021 The Android Open Source Project 4*d57664e9SAndroid Build Coastguard Worker# 5*d57664e9SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 6*d57664e9SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 7*d57664e9SAndroid Build Coastguard Worker# You may obtain a copy of the License at 8*d57664e9SAndroid Build Coastguard Worker# 9*d57664e9SAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 10*d57664e9SAndroid Build Coastguard Worker# 11*d57664e9SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 12*d57664e9SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 13*d57664e9SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14*d57664e9SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 15*d57664e9SAndroid Build Coastguard Worker# limitations under the License. 16*d57664e9SAndroid Build Coastguard Worker 17*d57664e9SAndroid Build Coastguard Worker"""Script to merge annotation XML files (created by e.g. metalava).""" 18*d57664e9SAndroid Build Coastguard Worker 19*d57664e9SAndroid Build Coastguard Workerfrom pathlib import Path 20*d57664e9SAndroid Build Coastguard Workerimport sys 21*d57664e9SAndroid Build Coastguard Workerimport xml.etree.ElementTree as ET 22*d57664e9SAndroid Build Coastguard Workerimport zipfile 23*d57664e9SAndroid Build Coastguard Worker 24*d57664e9SAndroid Build Coastguard Worker 25*d57664e9SAndroid Build Coastguard Workerdef validate_xml_assumptions(root): 26*d57664e9SAndroid Build Coastguard Worker """Verify the format of the annotations XML matches expectations""" 27*d57664e9SAndroid Build Coastguard Worker prevName = "" 28*d57664e9SAndroid Build Coastguard Worker assert root.tag == 'root' 29*d57664e9SAndroid Build Coastguard Worker for child in root: 30*d57664e9SAndroid Build Coastguard Worker assert child.tag == 'item', 'unexpected tag: %s' % child.tag 31*d57664e9SAndroid Build Coastguard Worker assert list(child.attrib.keys()) == ['name'], 'unexpected attribs: %s' % child.attrib.keys() 32*d57664e9SAndroid Build Coastguard Worker assert prevName < child.get('name'), 'items unexpectedly not strictly sorted (possibly duplicate entries)' 33*d57664e9SAndroid Build Coastguard Worker prevName = child.get('name') 34*d57664e9SAndroid Build Coastguard Worker 35*d57664e9SAndroid Build Coastguard Worker 36*d57664e9SAndroid Build Coastguard Workerdef merge_xml(a, b): 37*d57664e9SAndroid Build Coastguard Worker """Merge two annotation xml files""" 38*d57664e9SAndroid Build Coastguard Worker for xml in [a, b]: 39*d57664e9SAndroid Build Coastguard Worker validate_xml_assumptions(xml) 40*d57664e9SAndroid Build Coastguard Worker a.extend(b[:]) 41*d57664e9SAndroid Build Coastguard Worker a[:] = sorted(a[:], key=lambda x: x.get('name')) 42*d57664e9SAndroid Build Coastguard Worker validate_xml_assumptions(a) 43*d57664e9SAndroid Build Coastguard Worker 44*d57664e9SAndroid Build Coastguard Worker 45*d57664e9SAndroid Build Coastguard Workerdef merge_zip_file(out_dir, zip_file): 46*d57664e9SAndroid Build Coastguard Worker """Merge the content of the zip_file into out_dir""" 47*d57664e9SAndroid Build Coastguard Worker for filename in zip_file.namelist(): 48*d57664e9SAndroid Build Coastguard Worker path = Path(out_dir, filename) 49*d57664e9SAndroid Build Coastguard Worker if path.exists(): 50*d57664e9SAndroid Build Coastguard Worker existing_xml = ET.parse(path) 51*d57664e9SAndroid Build Coastguard Worker with zip_file.open(filename) as other_file: 52*d57664e9SAndroid Build Coastguard Worker other_xml = ET.parse(other_file) 53*d57664e9SAndroid Build Coastguard Worker merge_xml(existing_xml.getroot(), other_xml.getroot()) 54*d57664e9SAndroid Build Coastguard Worker existing_xml.write(path, encoding='UTF-8', xml_declaration=True) 55*d57664e9SAndroid Build Coastguard Worker else: 56*d57664e9SAndroid Build Coastguard Worker zip_file.extract(filename, out_dir) 57*d57664e9SAndroid Build Coastguard Worker 58*d57664e9SAndroid Build Coastguard Worker 59*d57664e9SAndroid Build Coastguard Workerdef main(): 60*d57664e9SAndroid Build Coastguard Worker out_dir = Path(sys.argv[1]) 61*d57664e9SAndroid Build Coastguard Worker zip_filenames = sys.argv[2:] 62*d57664e9SAndroid Build Coastguard Worker 63*d57664e9SAndroid Build Coastguard Worker assert not out_dir.exists() 64*d57664e9SAndroid Build Coastguard Worker out_dir.mkdir() 65*d57664e9SAndroid Build Coastguard Worker for zip_filename in zip_filenames: 66*d57664e9SAndroid Build Coastguard Worker with zipfile.ZipFile(zip_filename) as zip_file: 67*d57664e9SAndroid Build Coastguard Worker merge_zip_file(out_dir, zip_file) 68*d57664e9SAndroid Build Coastguard Worker 69*d57664e9SAndroid Build Coastguard Worker 70*d57664e9SAndroid Build Coastguard Workerif __name__ == "__main__": 71*d57664e9SAndroid Build Coastguard Worker main() 72