#!/usr/bin/env python3 # # Copyright (C) 2021 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. """Script to remove mainline APIs from the api-versions.xml.""" import argparse import re import xml.etree.ElementTree as ET import zipfile def read_classes(stubs): """Read classes from the stubs file. Args: stubs: argument can be a path to a file (a string), a file-like object or a path-like object Returns: a set of the classes found in the file (set of strings) """ classes = set() with zipfile.ZipFile(stubs) as z: for info in z.infolist(): if (not info.is_dir() and info.filename.endswith(".class") and not info.filename.startswith("META-INF")): # drop ".class" extension classes.add(info.filename[:-6]) return classes def filter_method_tag(method, classes_to_remove): """Updates the signature of this method by calling filter_method_signature. Updates the method passed into this function. Args: method: xml element that represents a method classes_to_remove: set of classes you to remove """ filtered = filter_method_signature(method.get("name"), classes_to_remove) method.set("name", filtered) def filter_method_signature(signature, classes_to_remove): """Removes mentions of certain classes from this method signature. Replaces any existing classes that need to be removed, with java/lang/Object Args: signature: string that is a java representation of a method signature classes_to_remove: set of classes you to remove """ regex = re.compile("L.*?;") start = signature.find("(") matches = set(regex.findall(signature[start:])) for m in matches: # m[1:-1] to drop the leading `L` and `;` ending if m[1:-1] in classes_to_remove: signature = signature.replace(m, "Ljava/lang/Object;") return signature def filter_lint_database(database, classes_to_remove, output): """Reads a lint database and writes a filtered version without some classes. Reads database from api-versions.xml and removes any references to classes in the second argument. Writes the result (another xml with the same format of the database) to output. Args: database: path to xml with lint database to read classes_to_remove: iterable (ideally a set or similar for quick lookups) that enumerates the classes that should be removed output: path to write the filtered database """ xml = ET.parse(database) root = xml.getroot() for c in xml.findall("class"): cname = c.get("name") if cname in classes_to_remove: root.remove(c) else: # find the tag inside this class to see if the parent # has been removed from the known classes (attribute called name) super_classes = c.findall("extends") for super_class in super_classes: super_class_name = super_class.get("name") if super_class_name in classes_to_remove: super_class.set("name", "java/lang/Object") interfaces = c.findall("implements") for interface in interfaces: interface_name = interface.get("name") if interface_name in classes_to_remove: c.remove(interface) for method in c.findall("method"): filter_method_tag(method, classes_to_remove) xml.write(output) def main(): """Run the program.""" parser = argparse.ArgumentParser( description= ("Read a lint database (api-versions.xml) and many stubs jar files. " "Produce another database file that doesn't include the classes present " "in the stubs file(s).")) parser.add_argument("output", help="Destination of the result (xml file).") parser.add_argument( "api_versions", help="The lint database (api-versions.xml file) to read data from" ) parser.add_argument("stubs", nargs="+", help="The stubs jar file(s)") parsed = parser.parse_args() classes = set() for stub in parsed.stubs: classes.update(read_classes(stub)) filter_lint_database(parsed.api_versions, classes, parsed.output) if __name__ == "__main__": main()