1#!/usr/bin/env python3 2# 3# Copyright (C) 2021 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17"""Script to remove mainline APIs from the api-versions.xml.""" 18 19import argparse 20import re 21import xml.etree.ElementTree as ET 22import zipfile 23 24 25def read_classes(stubs): 26 """Read classes from the stubs file. 27 28 Args: 29 stubs: argument can be a path to a file (a string), a file-like object or a 30 path-like object 31 32 Returns: 33 a set of the classes found in the file (set of strings) 34 """ 35 classes = set() 36 with zipfile.ZipFile(stubs) as z: 37 for info in z.infolist(): 38 if (not info.is_dir() 39 and info.filename.endswith(".class") 40 and not info.filename.startswith("META-INF")): 41 # drop ".class" extension 42 classes.add(info.filename[:-6]) 43 return classes 44 45 46def filter_method_tag(method, classes_to_remove): 47 """Updates the signature of this method by calling filter_method_signature. 48 49 Updates the method passed into this function. 50 51 Args: 52 method: xml element that represents a method 53 classes_to_remove: set of classes you to remove 54 """ 55 filtered = filter_method_signature(method.get("name"), classes_to_remove) 56 method.set("name", filtered) 57 58 59def filter_method_signature(signature, classes_to_remove): 60 """Removes mentions of certain classes from this method signature. 61 62 Replaces any existing classes that need to be removed, with java/lang/Object 63 64 Args: 65 signature: string that is a java representation of a method signature 66 classes_to_remove: set of classes you to remove 67 """ 68 regex = re.compile("L.*?;") 69 start = signature.find("(") 70 matches = set(regex.findall(signature[start:])) 71 for m in matches: 72 # m[1:-1] to drop the leading `L` and `;` ending 73 if m[1:-1] in classes_to_remove: 74 signature = signature.replace(m, "Ljava/lang/Object;") 75 return signature 76 77 78def filter_lint_database(database, classes_to_remove, output): 79 """Reads a lint database and writes a filtered version without some classes. 80 81 Reads database from api-versions.xml and removes any references to classes 82 in the second argument. Writes the result (another xml with the same format 83 of the database) to output. 84 85 Args: 86 database: path to xml with lint database to read 87 classes_to_remove: iterable (ideally a set or similar for quick 88 lookups) that enumerates the classes that should be removed 89 output: path to write the filtered database 90 """ 91 xml = ET.parse(database) 92 root = xml.getroot() 93 for c in xml.findall("class"): 94 cname = c.get("name") 95 if cname in classes_to_remove: 96 root.remove(c) 97 else: 98 # find the <extends /> tag inside this class to see if the parent 99 # has been removed from the known classes (attribute called name) 100 super_classes = c.findall("extends") 101 for super_class in super_classes: 102 super_class_name = super_class.get("name") 103 if super_class_name in classes_to_remove: 104 super_class.set("name", "java/lang/Object") 105 interfaces = c.findall("implements") 106 for interface in interfaces: 107 interface_name = interface.get("name") 108 if interface_name in classes_to_remove: 109 c.remove(interface) 110 for method in c.findall("method"): 111 filter_method_tag(method, classes_to_remove) 112 xml.write(output) 113 114 115def main(): 116 """Run the program.""" 117 parser = argparse.ArgumentParser( 118 description= 119 ("Read a lint database (api-versions.xml) and many stubs jar files. " 120 "Produce another database file that doesn't include the classes present " 121 "in the stubs file(s).")) 122 parser.add_argument("output", help="Destination of the result (xml file).") 123 parser.add_argument( 124 "api_versions", 125 help="The lint database (api-versions.xml file) to read data from" 126 ) 127 parser.add_argument("stubs", nargs="+", help="The stubs jar file(s)") 128 parsed = parser.parse_args() 129 classes = set() 130 for stub in parsed.stubs: 131 classes.update(read_classes(stub)) 132 filter_lint_database(parsed.api_versions, classes, parsed.output) 133 134 135if __name__ == "__main__": 136 main() 137