blob: 5a82be7b24604976dedd799e5ef1cd1a50961eb0 [file] [log] [blame]
Paul Duffin67b9d612021-07-21 17:38:47 +01001#!/usr/bin/env python
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.
Paul Duffin1e18e982021-08-03 15:42:27 +010016"""Generate a set of signature patterns for a bootclasspath_fragment.
17
18The patterns are generated from the modular flags produced by the
19bootclasspath_fragment and are used to select a subset of the monolithic flags
Paul Duffin67b9d612021-07-21 17:38:47 +010020against which the modular flags can be compared.
21"""
22
23import argparse
24import csv
Spandan Das2c2219b2021-08-25 17:47:43 +000025import sys
Paul Duffin67b9d612021-07-21 17:38:47 +010026
Paul Duffin1e18e982021-08-03 15:42:27 +010027
Paul Duffin1024f1d2022-03-15 17:45:57 +000028def dict_reader(csv_file):
Spandan Das2c2219b2021-08-25 17:47:43 +000029 return csv.DictReader(
Paul Duffin1024f1d2022-03-15 17:45:57 +000030 csv_file, delimiter=',', quotechar='|', fieldnames=['signature'])
Spandan Das2c2219b2021-08-25 17:47:43 +000031
Paul Duffin67b9d612021-07-21 17:38:47 +010032
Paul Duffin1024f1d2022-03-15 17:45:57 +000033def dot_package_to_slash_package(pkg):
Paul Duffin1e18e982021-08-03 15:42:27 +010034 return pkg.replace('.', '/')
35
36
Paul Duffin1024f1d2022-03-15 17:45:57 +000037def dot_packages_to_slash_packages(pkgs):
38 return [dot_package_to_slash_package(p) for p in pkgs]
39
40
41def slash_package_to_dot_package(pkg):
Paul Duffin1e18e982021-08-03 15:42:27 +010042 return pkg.replace('/', '.')
43
44
Paul Duffin1024f1d2022-03-15 17:45:57 +000045def slash_packages_to_dot_packages(pkgs):
46 return [slash_package_to_dot_package(p) for p in pkgs]
Paul Duffin1e18e982021-08-03 15:42:27 +010047
48
Paul Duffin1024f1d2022-03-15 17:45:57 +000049def is_split_package(split_packages, pkg):
50 return split_packages and (pkg in split_packages or '*' in split_packages)
51
52
53def matched_by_package_prefix_pattern(package_prefixes, prefix):
54 for packagePrefix in package_prefixes:
Paul Duffin1e18e982021-08-03 15:42:27 +010055 if prefix == packagePrefix:
56 return packagePrefix
Paul Duffin1024f1d2022-03-15 17:45:57 +000057 if (prefix.startswith(packagePrefix) and
58 prefix[len(packagePrefix)] == '/'):
Paul Duffin1e18e982021-08-03 15:42:27 +010059 return packagePrefix
60 return False
61
62
Paul Duffin846beb72022-03-15 17:45:57 +000063def validate_package_is_not_matched_by_package_prefix(package_type, pkg,
64 package_prefixes):
65 package_prefix = matched_by_package_prefix_pattern(package_prefixes, pkg)
66 if package_prefix:
67 # A package prefix matches the package.
68 package_for_output = slash_package_to_dot_package(pkg)
69 package_prefix_for_output = slash_package_to_dot_package(package_prefix)
70 return [
71 f'{package_type} {package_for_output} is matched by '
72 f'package prefix {package_prefix_for_output}'
73 ]
74 return []
75
76
77def validate_package_prefixes(split_packages, single_packages,
78 package_prefixes):
Paul Duffin1e18e982021-08-03 15:42:27 +010079 # If there are no package prefixes then there is no possible conflict
80 # between them and the split packages.
Paul Duffin1024f1d2022-03-15 17:45:57 +000081 if len(package_prefixes) == 0:
82 return []
Paul Duffin1e18e982021-08-03 15:42:27 +010083
84 # Check to make sure that the split packages and package prefixes do not
85 # overlap.
86 errors = []
Paul Duffin1024f1d2022-03-15 17:45:57 +000087 for split_package in split_packages:
88 if split_package == '*':
Paul Duffin1e18e982021-08-03 15:42:27 +010089 # A package prefix matches a split package.
Paul Duffin1024f1d2022-03-15 17:45:57 +000090 package_prefixes_for_output = ', '.join(
91 slash_packages_to_dot_packages(package_prefixes))
Paul Duffin1e18e982021-08-03 15:42:27 +010092 errors.append(
Paul Duffin1024f1d2022-03-15 17:45:57 +000093 "split package '*' conflicts with all package prefixes "
94 f'{package_prefixes_for_output}\n'
95 ' add split_packages:[] to fix')
Paul Duffin1e18e982021-08-03 15:42:27 +010096 else:
Paul Duffin846beb72022-03-15 17:45:57 +000097 errs = validate_package_is_not_matched_by_package_prefix(
98 'split package', split_package, package_prefixes)
99 errors.extend(errs)
100
101 # Check to make sure that the single packages and package prefixes do not
102 # overlap.
103 for single_package in single_packages:
104 errs = validate_package_is_not_matched_by_package_prefix(
105 'single package', single_package, package_prefixes)
106 errors.extend(errs)
Paul Duffin1e18e982021-08-03 15:42:27 +0100107 return errors
108
109
Paul Duffin1024f1d2022-03-15 17:45:57 +0000110def validate_split_packages(split_packages):
Paul Duffin1e18e982021-08-03 15:42:27 +0100111 errors = []
Paul Duffin1024f1d2022-03-15 17:45:57 +0000112 if '*' in split_packages and len(split_packages) > 1:
Paul Duffin1e18e982021-08-03 15:42:27 +0100113 errors.append('split packages are invalid as they contain both the'
114 ' wildcard (*) and specific packages, use the wildcard or'
115 ' specific packages, not a mixture')
116 return errors
117
118
Paul Duffin846beb72022-03-15 17:45:57 +0000119def validate_single_packages(split_packages, single_packages):
120 overlaps = []
121 for single_package in single_packages:
122 if single_package in split_packages:
123 overlaps.append(single_package)
124 if overlaps:
125 indented = ''.join([f'\n {o}' for o in overlaps])
126 return [
127 f'single_packages and split_packages overlap, please ensure the '
128 f'following packages are only present in one:{indented}'
129 ]
130 return []
131
132
Paul Duffin1024f1d2022-03-15 17:45:57 +0000133def produce_patterns_from_file(file,
134 split_packages=None,
Paul Duffin846beb72022-03-15 17:45:57 +0000135 single_packages=None,
Paul Duffin1024f1d2022-03-15 17:45:57 +0000136 package_prefixes=None):
137 with open(file, 'r', encoding='utf8') as f:
Paul Duffin846beb72022-03-15 17:45:57 +0000138 return produce_patterns_from_stream(f, split_packages, single_packages,
139 package_prefixes)
Paul Duffin67b9d612021-07-21 17:38:47 +0100140
Spandan Das2c2219b2021-08-25 17:47:43 +0000141
Paul Duffin1e18e982021-08-03 15:42:27 +0100142def produce_patterns_from_stream(stream,
Paul Duffin1024f1d2022-03-15 17:45:57 +0000143 split_packages=None,
Paul Duffin846beb72022-03-15 17:45:57 +0000144 single_packages=None,
Paul Duffin1024f1d2022-03-15 17:45:57 +0000145 package_prefixes=None):
146 split_packages = set(split_packages or [])
Paul Duffin846beb72022-03-15 17:45:57 +0000147 single_packages = set(single_packages or [])
Paul Duffin1024f1d2022-03-15 17:45:57 +0000148 package_prefixes = list(package_prefixes or [])
Paul Duffin1e18e982021-08-03 15:42:27 +0100149 # Read in all the signatures into a list and remove any unnecessary class
150 # and member names.
Paul Duffin6ffdff82021-08-09 13:47:19 +0100151 patterns = set()
Paul Duffin846beb72022-03-15 17:45:57 +0000152 unmatched_packages = set()
Paul Duffin6ffdff82021-08-09 13:47:19 +0100153 for row in dict_reader(stream):
Paul Duffin67b9d612021-07-21 17:38:47 +0100154 signature = row['signature']
Paul Duffin1e18e982021-08-03 15:42:27 +0100155 text = signature.removeprefix('L')
Paul Duffin6ffdff82021-08-09 13:47:19 +0100156 # Remove the class specific member signature
Paul Duffin1e18e982021-08-03 15:42:27 +0100157 pieces = text.split(';->')
Paul Duffin1024f1d2022-03-15 17:45:57 +0000158 qualified_class_name = pieces[0]
159 pieces = qualified_class_name.rsplit('/', maxsplit=1)
Paul Duffin1e18e982021-08-03 15:42:27 +0100160 pkg = pieces[0]
161 # If the package is split across multiple modules then it cannot be used
162 # to select the subset of the monolithic flags that this module
163 # produces. In that case we need to keep the name of the class but can
164 # discard any nested class names as an outer class cannot be split
165 # across modules.
166 #
167 # If the package is not split then every class in the package must be
168 # provided by this module so there is no need to list the classes
169 # explicitly so just use the package name instead.
Paul Duffin1024f1d2022-03-15 17:45:57 +0000170 if is_split_package(split_packages, pkg):
Paul Duffin1e18e982021-08-03 15:42:27 +0100171 # Remove inner class names.
Paul Duffin1024f1d2022-03-15 17:45:57 +0000172 pieces = qualified_class_name.split('$', maxsplit=1)
Paul Duffin1e18e982021-08-03 15:42:27 +0100173 pattern = pieces[0]
Paul Duffin846beb72022-03-15 17:45:57 +0000174 patterns.add(pattern)
175 elif pkg in single_packages:
Paul Duffin1e18e982021-08-03 15:42:27 +0100176 # Add a * to ensure that the pattern matches the classes in that
177 # package.
178 pattern = pkg + '/*'
Paul Duffin846beb72022-03-15 17:45:57 +0000179 patterns.add(pattern)
180 else:
181 unmatched_packages.add(pkg)
182
183 # Remove any unmatched packages that would be matched by a package prefix
184 # pattern.
185 unmatched_packages = [
186 p for p in unmatched_packages
187 if not matched_by_package_prefix_pattern(package_prefixes, p)
188 ]
189 errors = []
190 if unmatched_packages:
191 unmatched_packages.sort()
192 indented = ''.join([
193 f'\n {slash_package_to_dot_package(p)}'
194 for p in unmatched_packages
195 ])
196 errors.append('The following packages were unexpected, please add them '
197 'to one of the hidden_api properties, split_packages, '
198 f'single_packages or package_prefixes:{indented}')
Paul Duffin6ffdff82021-08-09 13:47:19 +0100199
Paul Duffin1e18e982021-08-03 15:42:27 +0100200 # Remove any patterns that would be matched by a package prefix pattern.
Paul Duffin1024f1d2022-03-15 17:45:57 +0000201 patterns = [
202 p for p in patterns
203 if not matched_by_package_prefix_pattern(package_prefixes, p)
204 ]
Paul Duffin1e18e982021-08-03 15:42:27 +0100205 # Add the package prefix patterns to the list. Add a ** to ensure that each
206 # package prefix pattern will match the classes in that package and all
207 # sub-packages.
Paul Duffin1024f1d2022-03-15 17:45:57 +0000208 patterns = patterns + [f'{p}/**' for p in package_prefixes]
Paul Duffin1e18e982021-08-03 15:42:27 +0100209 # Sort the patterns.
Paul Duffin6ffdff82021-08-09 13:47:19 +0100210 patterns.sort()
Paul Duffin846beb72022-03-15 17:45:57 +0000211 return patterns, errors
212
213
214def print_and_exit(errors):
215 for error in errors:
216 print(error)
217 sys.exit(1)
Paul Duffin67b9d612021-07-21 17:38:47 +0100218
Spandan Das2c2219b2021-08-25 17:47:43 +0000219
Paul Duffin67b9d612021-07-21 17:38:47 +0100220def main(args):
Spandan Das2c2219b2021-08-25 17:47:43 +0000221 args_parser = argparse.ArgumentParser(
222 description='Generate a set of signature patterns '
Paul Duffin1e18e982021-08-03 15:42:27 +0100223 'that select a subset of monolithic hidden API files.')
Spandan Das2c2219b2021-08-25 17:47:43 +0000224 args_parser.add_argument(
225 '--flags',
226 help='The stub flags file which contains an entry for every dex member',
227 )
Paul Duffin1e18e982021-08-03 15:42:27 +0100228 args_parser.add_argument(
229 '--split-package',
230 action='append',
Paul Duffin1024f1d2022-03-15 17:45:57 +0000231 help='A package that is split across multiple bootclasspath_fragment '
232 'modules')
Paul Duffin1e18e982021-08-03 15:42:27 +0100233 args_parser.add_argument(
234 '--package-prefix',
235 action='append',
236 help='A package prefix unique to this set of flags')
Paul Duffin846beb72022-03-15 17:45:57 +0000237 args_parser.add_argument(
238 '--single-package',
239 action='append',
240 help='A single package unique to this set of flags')
Paul Duffin67b9d612021-07-21 17:38:47 +0100241 args_parser.add_argument('--output', help='Generated signature prefixes')
242 args = args_parser.parse_args(args)
243
Paul Duffin1024f1d2022-03-15 17:45:57 +0000244 split_packages = set(
245 dot_packages_to_slash_packages(args.split_package or []))
246 errors = validate_split_packages(split_packages)
Paul Duffin846beb72022-03-15 17:45:57 +0000247 if errors:
248 print_and_exit(errors)
249
250 single_packages = list(
251 dot_packages_to_slash_packages(args.single_package or []))
252
253 errors = validate_single_packages(split_packages, single_packages)
254 if errors:
255 print_and_exit(errors)
Paul Duffin1e18e982021-08-03 15:42:27 +0100256
Paul Duffin1024f1d2022-03-15 17:45:57 +0000257 package_prefixes = dot_packages_to_slash_packages(args.package_prefix or [])
Paul Duffin1e18e982021-08-03 15:42:27 +0100258
Paul Duffin846beb72022-03-15 17:45:57 +0000259 errors = validate_package_prefixes(split_packages, single_packages,
260 package_prefixes)
261 if errors:
262 print_and_exit(errors)
263
264 patterns = []
265 # Read in all the patterns into a list.
266 patterns, errors = produce_patterns_from_file(args.flags, split_packages,
267 single_packages,
268 package_prefixes)
Paul Duffin1e18e982021-08-03 15:42:27 +0100269
270 if errors:
Paul Duffin846beb72022-03-15 17:45:57 +0000271 print_and_exit(errors)
Paul Duffin67b9d612021-07-21 17:38:47 +0100272
273 # Write out all the patterns.
Paul Duffin1024f1d2022-03-15 17:45:57 +0000274 with open(args.output, 'w', encoding='utf8') as outputFile:
Paul Duffin67b9d612021-07-21 17:38:47 +0100275 for pattern in patterns:
276 outputFile.write(pattern)
Paul Duffin1e18e982021-08-03 15:42:27 +0100277 outputFile.write('\n')
Paul Duffin67b9d612021-07-21 17:38:47 +0100278
Spandan Das2c2219b2021-08-25 17:47:43 +0000279
Paul Duffin1e18e982021-08-03 15:42:27 +0100280if __name__ == '__main__':
Paul Duffin67b9d612021-07-21 17:38:47 +0100281 main(sys.argv[1:])