| #!/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 <extends /> 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() |