blob: e75ee95667059a155d50c75e3650d41167544252 [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
Spandan Das2c2219b2021-08-25 17:47:43 +000028def dict_reader(csvfile):
29 return csv.DictReader(
Paul Duffin1e18e982021-08-03 15:42:27 +010030 csvfile, delimiter=',', quotechar='|', fieldnames=['signature'])
Spandan Das2c2219b2021-08-25 17:47:43 +000031
Paul Duffin67b9d612021-07-21 17:38:47 +010032
Paul Duffin1e18e982021-08-03 15:42:27 +010033def dotPackageToSlashPackage(pkg):
34 return pkg.replace('.', '/')
35
36
37def slashPackageToDotPackage(pkg):
38 return pkg.replace('/', '.')
39
40
41def isSplitPackage(splitPackages, pkg):
42 return splitPackages and (pkg in splitPackages or '*' in splitPackages)
43
44
45def matchedByPackagePrefixPattern(packagePrefixes, prefix):
46 for packagePrefix in packagePrefixes:
47 if prefix == packagePrefix:
48 return packagePrefix
49 elif prefix.startswith(packagePrefix) and prefix[len(
50 packagePrefix)] == '/':
51 return packagePrefix
52 return False
53
54
55def validate_package_prefixes(splitPackages, packagePrefixes):
56 # If there are no package prefixes then there is no possible conflict
57 # between them and the split packages.
58 if len(packagePrefixes) == 0:
59 return
60
61 # Check to make sure that the split packages and package prefixes do not
62 # overlap.
63 errors = []
64 for splitPackage in splitPackages:
65 if splitPackage == '*':
66 # A package prefix matches a split package.
67 packagePrefixesForOutput = ', '.join(
68 map(slashPackageToDotPackage, packagePrefixes))
69 errors.append(
70 'split package "*" conflicts with all package prefixes %s\n'
71 ' add split_packages:[] to fix' % packagePrefixesForOutput)
72 else:
73 packagePrefix = matchedByPackagePrefixPattern(
74 packagePrefixes, splitPackage)
75 if packagePrefix:
76 # A package prefix matches a split package.
77 splitPackageForOutput = slashPackageToDotPackage(splitPackage)
78 packagePrefixForOutput = slashPackageToDotPackage(packagePrefix)
79 errors.append(
80 'split package %s is matched by package prefix %s' %
81 (splitPackageForOutput, packagePrefixForOutput))
82 return errors
83
84
85def validate_split_packages(splitPackages):
86 errors = []
87 if '*' in splitPackages and len(splitPackages) > 1:
88 errors.append('split packages are invalid as they contain both the'
89 ' wildcard (*) and specific packages, use the wildcard or'
90 ' specific packages, not a mixture')
91 return errors
92
93
94def produce_patterns_from_file(file, splitPackages=None, packagePrefixes=None):
Paul Duffin67b9d612021-07-21 17:38:47 +010095 with open(file, 'r') as f:
Paul Duffin1e18e982021-08-03 15:42:27 +010096 return produce_patterns_from_stream(f, splitPackages, packagePrefixes)
Paul Duffin67b9d612021-07-21 17:38:47 +010097
Spandan Das2c2219b2021-08-25 17:47:43 +000098
Paul Duffin1e18e982021-08-03 15:42:27 +010099def produce_patterns_from_stream(stream,
100 splitPackages=None,
101 packagePrefixes=None):
102 splitPackages = set(splitPackages or [])
103 packagePrefixes = list(packagePrefixes or [])
104 # Read in all the signatures into a list and remove any unnecessary class
105 # and member names.
Paul Duffin6ffdff82021-08-09 13:47:19 +0100106 patterns = set()
107 for row in dict_reader(stream):
Paul Duffin67b9d612021-07-21 17:38:47 +0100108 signature = row['signature']
Paul Duffin1e18e982021-08-03 15:42:27 +0100109 text = signature.removeprefix('L')
Paul Duffin6ffdff82021-08-09 13:47:19 +0100110 # Remove the class specific member signature
Paul Duffin1e18e982021-08-03 15:42:27 +0100111 pieces = text.split(';->')
Paul Duffin6ffdff82021-08-09 13:47:19 +0100112 qualifiedClassName = pieces[0]
Paul Duffin1e18e982021-08-03 15:42:27 +0100113 pieces = qualifiedClassName.rsplit('/', maxsplit=1)
114 pkg = pieces[0]
115 # If the package is split across multiple modules then it cannot be used
116 # to select the subset of the monolithic flags that this module
117 # produces. In that case we need to keep the name of the class but can
118 # discard any nested class names as an outer class cannot be split
119 # across modules.
120 #
121 # If the package is not split then every class in the package must be
122 # provided by this module so there is no need to list the classes
123 # explicitly so just use the package name instead.
124 if isSplitPackage(splitPackages, pkg):
125 # Remove inner class names.
126 pieces = qualifiedClassName.split('$', maxsplit=1)
127 pattern = pieces[0]
128 else:
129 # Add a * to ensure that the pattern matches the classes in that
130 # package.
131 pattern = pkg + '/*'
Paul Duffin6ffdff82021-08-09 13:47:19 +0100132 patterns.add(pattern)
133
Paul Duffin1e18e982021-08-03 15:42:27 +0100134 # Remove any patterns that would be matched by a package prefix pattern.
135 patterns = list(
136 filter(lambda p: not matchedByPackagePrefixPattern(packagePrefixes, p),
137 patterns))
138 # Add the package prefix patterns to the list. Add a ** to ensure that each
139 # package prefix pattern will match the classes in that package and all
140 # sub-packages.
141 patterns = patterns + list(map(lambda x: x + '/**', packagePrefixes))
142 # Sort the patterns.
Paul Duffin6ffdff82021-08-09 13:47:19 +0100143 patterns.sort()
Paul Duffin67b9d612021-07-21 17:38:47 +0100144 return patterns
145
Spandan Das2c2219b2021-08-25 17:47:43 +0000146
Paul Duffin67b9d612021-07-21 17:38:47 +0100147def main(args):
Spandan Das2c2219b2021-08-25 17:47:43 +0000148 args_parser = argparse.ArgumentParser(
149 description='Generate a set of signature patterns '
Paul Duffin1e18e982021-08-03 15:42:27 +0100150 'that select a subset of monolithic hidden API files.')
Spandan Das2c2219b2021-08-25 17:47:43 +0000151 args_parser.add_argument(
152 '--flags',
153 help='The stub flags file which contains an entry for every dex member',
154 )
Paul Duffin1e18e982021-08-03 15:42:27 +0100155 args_parser.add_argument(
156 '--split-package',
157 action='append',
158 help='A package that is split across multiple bootclasspath_fragment modules'
159 )
160 args_parser.add_argument(
161 '--package-prefix',
162 action='append',
163 help='A package prefix unique to this set of flags')
Paul Duffin67b9d612021-07-21 17:38:47 +0100164 args_parser.add_argument('--output', help='Generated signature prefixes')
165 args = args_parser.parse_args(args)
166
Paul Duffin1e18e982021-08-03 15:42:27 +0100167 splitPackages = set(map(dotPackageToSlashPackage, args.split_package or []))
168 errors = validate_split_packages(splitPackages)
169
170 packagePrefixes = list(
171 map(dotPackageToSlashPackage, args.package_prefix or []))
172
173 if not errors:
174 errors = validate_package_prefixes(splitPackages, packagePrefixes)
175
176 if errors:
177 for error in errors:
178 print(error)
179 sys.exit(1)
180
Paul Duffin67b9d612021-07-21 17:38:47 +0100181 # Read in all the patterns into a list.
Paul Duffin1e18e982021-08-03 15:42:27 +0100182 patterns = produce_patterns_from_file(args.flags, splitPackages,
183 packagePrefixes)
Paul Duffin67b9d612021-07-21 17:38:47 +0100184
185 # Write out all the patterns.
186 with open(args.output, 'w') as outputFile:
187 for pattern in patterns:
188 outputFile.write(pattern)
Paul Duffin1e18e982021-08-03 15:42:27 +0100189 outputFile.write('\n')
Paul Duffin67b9d612021-07-21 17:38:47 +0100190
Spandan Das2c2219b2021-08-25 17:47:43 +0000191
Paul Duffin1e18e982021-08-03 15:42:27 +0100192if __name__ == '__main__':
Paul Duffin67b9d612021-07-21 17:38:47 +0100193 main(sys.argv[1:])