Pedro Loureiro | b91f45d | 2021-05-26 15:50:07 +0000 | [diff] [blame] | 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 | |
| 19 | import argparse |
| 20 | import re |
| 21 | import xml.etree.ElementTree as ET |
| 22 | import zipfile |
| 23 | |
| 24 | |
| 25 | def 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 | |
| 46 | def 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 | |
| 59 | def 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 | |
| 78 | def 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 | |
| 115 | def 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 | |
| 135 | if __name__ == "__main__": |
| 136 | main() |