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