• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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