blob: 4cd7e63aca0828eb57660806785797b385b521e2 [file] [log] [blame]
Paul Duffindfa10832021-05-13 17:31:51 +01001#!/usr/bin/env python
2#
3# Copyright (C) 2018 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.
Spandan Das559132f2021-08-25 18:17:33 +000016"""Verify that one set of hidden API flags is a subset of another.
Paul Duffindfa10832021-05-13 17:31:51 +010017"""
18
19import argparse
20import csv
Paul Duffinc11f6672021-07-20 00:04:21 +010021import sys
Paul Duffin53a76072021-07-21 17:27:09 +010022from itertools import chain
Paul Duffindfa10832021-05-13 17:31:51 +010023
Spandan Das559132f2021-08-25 18:17:33 +000024#pylint: disable=line-too-long
Paul Duffinc11f6672021-07-20 00:04:21 +010025class InteriorNode:
Spandan Das559132f2021-08-25 18:17:33 +000026 """An interior node in a trie.
Paul Duffinc11f6672021-07-20 00:04:21 +010027
28 Each interior node has a dict that maps from an element of a signature to
29 either another interior node or a leaf. Each interior node represents either
30 a package, class or nested class. Class members are represented by a Leaf.
31
32 Associating the set of flags [public-api] with the signature
33 "Ljava/lang/Object;->String()Ljava/lang/String;" will cause the following
34 nodes to be created:
35 Node()
36 ^- package:java -> Node()
37 ^- package:lang -> Node()
38 ^- class:Object -> Node()
39 ^- member:String()Ljava/lang/String; -> Leaf([public-api])
40
41 Associating the set of flags [blocked,core-platform-api] with the signature
42 "Ljava/lang/Character$UnicodeScript;->of(I)Ljava/lang/Character$UnicodeScript;"
43 will cause the following nodes to be created:
44 Node()
45 ^- package:java -> Node()
46 ^- package:lang -> Node()
47 ^- class:Character -> Node()
48 ^- class:UnicodeScript -> Node()
49 ^- member:of(I)Ljava/lang/Character$UnicodeScript;
50 -> Leaf([blocked,core-platform-api])
51
52 Attributes:
53 nodes: a dict from an element of the signature to the Node/Leaf
Spandan Das559132f2021-08-25 18:17:33 +000054 containing the next element/value.
Paul Duffinc11f6672021-07-20 00:04:21 +010055 """
Spandan Das559132f2021-08-25 18:17:33 +000056 #pylint: enable=line-too-long
57
Paul Duffinc11f6672021-07-20 00:04:21 +010058 def __init__(self):
59 self.nodes = {}
60
Spandan Das559132f2021-08-25 18:17:33 +000061 #pylint: disable=line-too-long
Paul Duffinc11f6672021-07-20 00:04:21 +010062 def signatureToElements(self, signature):
Spandan Das559132f2021-08-25 18:17:33 +000063 """Split a signature or a prefix into a number of elements:
Paul Duffinc11f6672021-07-20 00:04:21 +010064 1. The packages (excluding the leading L preceding the first package).
65 2. The class names, from outermost to innermost.
66 3. The member signature.
Spandan Das559132f2021-08-25 18:17:33 +000067 e.g.
68 Ljava/lang/Character$UnicodeScript;->of(I)Ljava/lang/Character$UnicodeScript;
Paul Duffinc11f6672021-07-20 00:04:21 +010069 will be broken down into these elements:
70 1. package:java
71 2. package:lang
72 3. class:Character
73 4. class:UnicodeScript
74 5. member:of(I)Ljava/lang/Character$UnicodeScript;
75 """
76 # Remove the leading L.
77 # - java/lang/Character$UnicodeScript;->of(I)Ljava/lang/Character$UnicodeScript;
78 text = signature.removeprefix("L")
79 # Split the signature between qualified class name and the class member
80 # signature.
81 # 0 - java/lang/Character$UnicodeScript
82 # 1 - of(I)Ljava/lang/Character$UnicodeScript;
83 parts = text.split(";->")
84 member = parts[1:]
85 # Split the qualified class name into packages, and class name.
86 # 0 - java
87 # 1 - lang
88 # 2 - Character$UnicodeScript
89 elements = parts[0].split("/")
90 packages = elements[0:-1]
91 className = elements[-1]
Spandan Das559132f2021-08-25 18:17:33 +000092 if className in ("*" , "**"): #pylint: disable=no-else-return
Paul Duffinc11f6672021-07-20 00:04:21 +010093 # Cannot specify a wildcard and target a specific member
94 if len(member) != 0:
Spandan Das559132f2021-08-25 18:17:33 +000095 raise Exception(
96 "Invalid signature %s: contains wildcard %s and member " \
97 "signature %s"
98 % (signature, className, member[0]))
Paul Duffinc11f6672021-07-20 00:04:21 +010099 wildcard = [className]
100 # Assemble the parts into a single list, adding prefixes to identify
101 # the different parts.
102 # 0 - package:java
103 # 1 - package:lang
104 # 2 - *
Spandan Das559132f2021-08-25 18:17:33 +0000105 return list(
106 chain(["package:" + x for x in packages], wildcard))
Paul Duffinc11f6672021-07-20 00:04:21 +0100107 else:
108 # Split the class name into outer / inner classes
109 # 0 - Character
110 # 1 - UnicodeScript
111 classes = className.split("$")
112 # Assemble the parts into a single list, adding prefixes to identify
113 # the different parts.
114 # 0 - package:java
115 # 1 - package:lang
116 # 2 - class:Character
117 # 3 - class:UnicodeScript
118 # 4 - member:of(I)Ljava/lang/Character$UnicodeScript;
Spandan Das559132f2021-08-25 18:17:33 +0000119 return list(
120 chain(
121 ["package:" + x for x in packages],
122 ["class:" + x for x in classes],
123 ["member:" + x for x in member]))
124 #pylint: enable=line-too-long
Paul Duffinc11f6672021-07-20 00:04:21 +0100125
126 def add(self, signature, value):
Spandan Das559132f2021-08-25 18:17:33 +0000127 """Associate the value with the specific signature.
128
Paul Duffinc11f6672021-07-20 00:04:21 +0100129 :param signature: the member signature
130 :param value: the value to associated with the signature
131 :return: n/a
132 """
133 # Split the signature into elements.
134 elements = self.signatureToElements(signature)
135 # Find the Node associated with the deepest class.
136 node = self
137 for element in elements[:-1]:
138 if element in node.nodes:
139 node = node.nodes[element]
140 else:
Spandan Das559132f2021-08-25 18:17:33 +0000141 next_node = InteriorNode()
142 node.nodes[element] = next_node
143 node = next_node
Paul Duffinc11f6672021-07-20 00:04:21 +0100144 # Add a Leaf containing the value and associate it with the member
145 # signature within the class.
146 lastElement = elements[-1]
147 if not lastElement.startswith("member:"):
Spandan Das559132f2021-08-25 18:17:33 +0000148 raise Exception(
149 "Invalid signature: %s, does not identify a specific member" %
150 signature)
Paul Duffinc11f6672021-07-20 00:04:21 +0100151 if lastElement in node.nodes:
152 raise Exception("Duplicate signature: %s" % signature)
153 node.nodes[lastElement] = Leaf(value)
154
155 def getMatchingRows(self, pattern):
Spandan Das559132f2021-08-25 18:17:33 +0000156 """Get the values (plural) associated with the pattern.
Paul Duffinc11f6672021-07-20 00:04:21 +0100157
158 e.g. If the pattern is a full signature then this will return a list
159 containing the value associated with that signature.
160
161 If the pattern is a class then this will return a list containing the
162 values associated with all members of that class.
163
164 If the pattern is a package then this will return a list containing the
165 values associated with all the members of all the classes in that
166 package and sub-packages.
167
168 If the pattern ends with "*" then the preceding part is treated as a
169 package and this will return a list containing the values associated
170 with all the members of all the classes in that package.
171
172 If the pattern ends with "**" then the preceding part is treated
173 as a package and this will return a list containing the values
174 associated with all the members of all the classes in that package and
175 all sub-packages.
176
177 :param pattern: the pattern which could be a complete signature or a
178 class, or package wildcard.
179 :return: an iterable containing all the values associated with the
180 pattern.
181 """
182 elements = self.signatureToElements(pattern)
183 node = self
184 # Include all values from this node and all its children.
Spandan Das559132f2021-08-25 18:17:33 +0000185 selector = lambda x: True
Paul Duffinc11f6672021-07-20 00:04:21 +0100186 lastElement = elements[-1]
Spandan Das559132f2021-08-25 18:17:33 +0000187 if lastElement in ("*", "**"):
Paul Duffinc11f6672021-07-20 00:04:21 +0100188 elements = elements[:-1]
189 if lastElement == "*":
190 # Do not include values from sub-packages.
Spandan Das559132f2021-08-25 18:17:33 +0000191 selector = lambda x: not x.startswith("package:")
Paul Duffinc11f6672021-07-20 00:04:21 +0100192 for element in elements:
193 if element in node.nodes:
194 node = node.nodes[element]
195 else:
196 return []
197 return chain.from_iterable(node.values(selector))
198
199 def values(self, selector):
Spandan Das559132f2021-08-25 18:17:33 +0000200 """:param selector: a function that can be applied to a key in the nodes
Paul Duffinc11f6672021-07-20 00:04:21 +0100201 attribute to determine whether to return its values.
Spandan Das559132f2021-08-25 18:17:33 +0000202
203 :return: A list of iterables of all the values associated with
204 this node and its children.
Paul Duffinc11f6672021-07-20 00:04:21 +0100205 """
206 values = []
207 self.appendValues(values, selector)
208 return values
209
210 def appendValues(self, values, selector):
Spandan Das559132f2021-08-25 18:17:33 +0000211 """Append the values associated with this node and its children to the
Paul Duffinc11f6672021-07-20 00:04:21 +0100212 list.
213
214 For each item (key, child) in nodes the child node's values are returned
215 if and only if the selector returns True when called on its key. A child
216 node's values are all the values associated with it and all its
217 descendant nodes.
218
219 :param selector: a function that can be applied to a key in the nodes
220 attribute to determine whether to return its values.
221 :param values: a list of a iterables of values.
222 """
223 for key, node in self.nodes.items():
224 if selector(key):
Spandan Das559132f2021-08-25 18:17:33 +0000225 node.appendValues(values, lambda x: True)
226
Paul Duffinc11f6672021-07-20 00:04:21 +0100227
228class Leaf:
Spandan Das559132f2021-08-25 18:17:33 +0000229 """A leaf of the trie
Paul Duffinc11f6672021-07-20 00:04:21 +0100230
231 Attributes:
232 value: the value associated with this leaf.
233 """
Spandan Das559132f2021-08-25 18:17:33 +0000234
Paul Duffinc11f6672021-07-20 00:04:21 +0100235 def __init__(self, value):
236 self.value = value
237
Spandan Das559132f2021-08-25 18:17:33 +0000238 def values(self, selector): #pylint: disable=unused-argument
239 """:return: A list of a list of the value associated with this node.
Paul Duffinc11f6672021-07-20 00:04:21 +0100240 """
241 return [[self.value]]
242
Spandan Das559132f2021-08-25 18:17:33 +0000243 def appendValues(self, values, selector): #pylint: disable=unused-argument
244 """Appends a list of the value associated with this node to the list.
245
Paul Duffinc11f6672021-07-20 00:04:21 +0100246 :param values: a list of a iterables of values.
247 """
248 values.append([self.value])
249
Spandan Das559132f2021-08-25 18:17:33 +0000250
251def dict_reader(csvfile):
252 return csv.DictReader(
253 csvfile, delimiter=",", quotechar="|", fieldnames=["signature"])
254
Paul Duffindfa10832021-05-13 17:31:51 +0100255
Paul Duffinc11f6672021-07-20 00:04:21 +0100256def read_flag_trie_from_file(file):
Spandan Das559132f2021-08-25 18:17:33 +0000257 with open(file, "r") as stream:
Paul Duffinc11f6672021-07-20 00:04:21 +0100258 return read_flag_trie_from_stream(stream)
259
Spandan Das559132f2021-08-25 18:17:33 +0000260
Paul Duffinc11f6672021-07-20 00:04:21 +0100261def read_flag_trie_from_stream(stream):
262 trie = InteriorNode()
263 reader = dict_reader(stream)
264 for row in reader:
Spandan Das559132f2021-08-25 18:17:33 +0000265 signature = row["signature"]
Paul Duffinc11f6672021-07-20 00:04:21 +0100266 trie.add(signature, row)
267 return trie
268
Spandan Das559132f2021-08-25 18:17:33 +0000269
270def extract_subset_from_monolithic_flags_as_dict_from_file(
271 monolithicTrie, patternsFile):
272 """Extract a subset of flags from the dict containing all the monolithic
273 flags.
Paul Duffin53a76072021-07-21 17:27:09 +0100274
275 :param monolithicFlagsDict: the dict containing all the monolithic flags.
Paul Duffin67b9d612021-07-21 17:38:47 +0100276 :param patternsFile: a file containing a list of signature patterns that
277 define the subset.
278 :return: the dict from signature to row.
279 """
Spandan Das559132f2021-08-25 18:17:33 +0000280 with open(patternsFile, "r") as stream:
281 return extract_subset_from_monolithic_flags_as_dict_from_stream(
282 monolithicTrie, stream)
Paul Duffin67b9d612021-07-21 17:38:47 +0100283
Spandan Das559132f2021-08-25 18:17:33 +0000284
285def extract_subset_from_monolithic_flags_as_dict_from_stream(
286 monolithicTrie, stream):
287 """Extract a subset of flags from the trie containing all the monolithic
288 flags.
Paul Duffin67b9d612021-07-21 17:38:47 +0100289
Paul Duffinc11f6672021-07-20 00:04:21 +0100290 :param monolithicTrie: the trie containing all the monolithic flags.
Paul Duffin67b9d612021-07-21 17:38:47 +0100291 :param stream: a stream containing a list of signature patterns that define
292 the subset.
Paul Duffin53a76072021-07-21 17:27:09 +0100293 :return: the dict from signature to row.
294 """
Spandan Das559132f2021-08-25 18:17:33 +0000295 dict_signature_to_row = {}
Paul Duffinc11f6672021-07-20 00:04:21 +0100296 for pattern in stream:
297 pattern = pattern.rstrip()
298 rows = monolithicTrie.getMatchingRows(pattern)
299 for row in rows:
Spandan Das559132f2021-08-25 18:17:33 +0000300 signature = row["signature"]
301 dict_signature_to_row[signature] = row
302 return dict_signature_to_row
303
Paul Duffin53a76072021-07-21 17:27:09 +0100304
Paul Duffin428c6512021-07-21 15:33:22 +0100305def read_signature_csv_from_stream_as_dict(stream):
Spandan Das559132f2021-08-25 18:17:33 +0000306 """Read the csv contents from the stream into a dict. The first column is
307 assumed to be the signature and used as the key.
Paul Duffin428c6512021-07-21 15:33:22 +0100308
Spandan Das559132f2021-08-25 18:17:33 +0000309 The whole row is stored as the value.
Paul Duffin428c6512021-07-21 15:33:22 +0100310 :param stream: the csv contents to read
311 :return: the dict from signature to row.
312 """
Spandan Das559132f2021-08-25 18:17:33 +0000313 dict_signature_to_row = {}
Paul Duffin428c6512021-07-21 15:33:22 +0100314 reader = dict_reader(stream)
315 for row in reader:
Spandan Das559132f2021-08-25 18:17:33 +0000316 signature = row["signature"]
317 dict_signature_to_row[signature] = row
318 return dict_signature_to_row
319
Paul Duffindfa10832021-05-13 17:31:51 +0100320
Paul Duffin428c6512021-07-21 15:33:22 +0100321def read_signature_csv_from_file_as_dict(csvFile):
Spandan Das559132f2021-08-25 18:17:33 +0000322 """Read the csvFile into a dict. The first column is assumed to be the
323 signature and used as the key.
Paul Duffin428c6512021-07-21 15:33:22 +0100324
Spandan Das559132f2021-08-25 18:17:33 +0000325 The whole row is stored as the value.
Paul Duffin428c6512021-07-21 15:33:22 +0100326 :param csvFile: the csv file to read
327 :return: the dict from signature to row.
328 """
Spandan Das559132f2021-08-25 18:17:33 +0000329 with open(csvFile, "r") as f:
Paul Duffin428c6512021-07-21 15:33:22 +0100330 return read_signature_csv_from_stream_as_dict(f)
331
Spandan Das559132f2021-08-25 18:17:33 +0000332
Paul Duffin428c6512021-07-21 15:33:22 +0100333def compare_signature_flags(monolithicFlagsDict, modularFlagsDict):
Spandan Das559132f2021-08-25 18:17:33 +0000334 """Compare the signature flags between the two dicts.
Paul Duffin428c6512021-07-21 15:33:22 +0100335
336 :param monolithicFlagsDict: the dict containing the subset of the monolithic
337 flags that should be equal to the modular flags.
338 :param modularFlagsDict:the dict containing the flags produced by a single
339 bootclasspath_fragment module.
340 :return: list of mismatches., each mismatch is a tuple where the first item
341 is the signature, and the second and third items are lists of the flags from
342 modular dict, and monolithic dict respectively.
343 """
Paul Duffindfa10832021-05-13 17:31:51 +0100344 mismatchingSignatures = []
Paul Duffin53a76072021-07-21 17:27:09 +0100345 # Create a sorted set of all the signatures from both the monolithic and
346 # modular dicts.
Spandan Das559132f2021-08-25 18:17:33 +0000347 allSignatures = sorted(
348 set(chain(monolithicFlagsDict.keys(), modularFlagsDict.keys())))
Paul Duffin53a76072021-07-21 17:27:09 +0100349 for signature in allSignatures:
Paul Duffin428c6512021-07-21 15:33:22 +0100350 monolithicRow = monolithicFlagsDict.get(signature, {})
351 monolithicFlags = monolithicRow.get(None, [])
Paul Duffin280bae62021-07-20 18:03:53 +0100352 if signature in modularFlagsDict:
353 modularRow = modularFlagsDict.get(signature, {})
354 modularFlags = modularRow.get(None, [])
355 else:
356 modularFlags = ["blocked"]
Paul Duffin428c6512021-07-21 15:33:22 +0100357 if monolithicFlags != modularFlags:
Spandan Das559132f2021-08-25 18:17:33 +0000358 mismatchingSignatures.append(
359 (signature, modularFlags, monolithicFlags))
Paul Duffin428c6512021-07-21 15:33:22 +0100360 return mismatchingSignatures
Paul Duffindfa10832021-05-13 17:31:51 +0100361
Spandan Das559132f2021-08-25 18:17:33 +0000362
Paul Duffin428c6512021-07-21 15:33:22 +0100363def main(argv):
Spandan Das559132f2021-08-25 18:17:33 +0000364 args_parser = argparse.ArgumentParser(
365 description="Verify that sets of hidden API flags are each a subset of "
366 "the monolithic flag file."
367 )
368 args_parser.add_argument("monolithicFlags", help="The monolithic flag file")
369 args_parser.add_argument(
370 "modularFlags",
371 nargs=argparse.REMAINDER,
372 help="Flags produced by individual bootclasspath_fragment modules")
Paul Duffin428c6512021-07-21 15:33:22 +0100373 args = args_parser.parse_args(argv[1:])
Paul Duffindfa10832021-05-13 17:31:51 +0100374
Paul Duffinc11f6672021-07-20 00:04:21 +0100375 # Read in all the flags into the trie
Paul Duffin7be96332021-07-21 16:13:03 +0100376 monolithicFlagsPath = args.monolithicFlags
Paul Duffinc11f6672021-07-20 00:04:21 +0100377 monolithicTrie = read_flag_trie_from_file(monolithicFlagsPath)
Paul Duffindfa10832021-05-13 17:31:51 +0100378
Paul Duffin53a76072021-07-21 17:27:09 +0100379 # For each subset specified on the command line, create dicts for the flags
Paul Duffinc11f6672021-07-20 00:04:21 +0100380 # provided by the subset and the corresponding flags from the complete set
381 # of flags and compare them.
Paul Duffin428c6512021-07-21 15:33:22 +0100382 failed = False
Paul Duffin67b9d612021-07-21 17:38:47 +0100383 for modularPair in args.modularFlags:
384 parts = modularPair.split(":")
385 modularFlagsPath = parts[0]
386 modularPatternsPath = parts[1]
Spandan Das559132f2021-08-25 18:17:33 +0000387 modularFlagsDict = read_signature_csv_from_file_as_dict(
388 modularFlagsPath)
389 monolithicFlagsSubsetDict = \
390 extract_subset_from_monolithic_flags_as_dict_from_file(
391 monolithicTrie, modularPatternsPath)
392 mismatchingSignatures = compare_signature_flags(
393 monolithicFlagsSubsetDict, modularFlagsDict)
Paul Duffin428c6512021-07-21 15:33:22 +0100394 if mismatchingSignatures:
395 failed = True
396 print("ERROR: Hidden API flags are inconsistent:")
Paul Duffin7be96332021-07-21 16:13:03 +0100397 print("< " + modularFlagsPath)
398 print("> " + monolithicFlagsPath)
Paul Duffin428c6512021-07-21 15:33:22 +0100399 for mismatch in mismatchingSignatures:
400 signature = mismatch[0]
401 print()
Spandan Das559132f2021-08-25 18:17:33 +0000402 print("< " + ",".join([signature] + mismatch[1]))
403 print("> " + ",".join([signature] + mismatch[2]))
Paul Duffin428c6512021-07-21 15:33:22 +0100404
405 if failed:
406 sys.exit(1)
407
Spandan Das559132f2021-08-25 18:17:33 +0000408
Paul Duffin428c6512021-07-21 15:33:22 +0100409if __name__ == "__main__":
410 main(sys.argv)