Paul Duffin | 67b9d61 | 2021-07-21 17:38:47 +0100 | [diff] [blame] | 1 | #!/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 Duffin | 1e18e98 | 2021-08-03 15:42:27 +0100 | [diff] [blame^] | 16 | """Generate a set of signature patterns for a bootclasspath_fragment. |
| 17 | |
| 18 | The patterns are generated from the modular flags produced by the |
| 19 | bootclasspath_fragment and are used to select a subset of the monolithic flags |
Paul Duffin | 67b9d61 | 2021-07-21 17:38:47 +0100 | [diff] [blame] | 20 | against which the modular flags can be compared. |
| 21 | """ |
| 22 | |
| 23 | import argparse |
| 24 | import csv |
Spandan Das | 2c2219b | 2021-08-25 17:47:43 +0000 | [diff] [blame] | 25 | import sys |
Paul Duffin | 67b9d61 | 2021-07-21 17:38:47 +0100 | [diff] [blame] | 26 | |
Paul Duffin | 1e18e98 | 2021-08-03 15:42:27 +0100 | [diff] [blame^] | 27 | |
Spandan Das | 2c2219b | 2021-08-25 17:47:43 +0000 | [diff] [blame] | 28 | def dict_reader(csvfile): |
| 29 | return csv.DictReader( |
Paul Duffin | 1e18e98 | 2021-08-03 15:42:27 +0100 | [diff] [blame^] | 30 | csvfile, delimiter=',', quotechar='|', fieldnames=['signature']) |
Spandan Das | 2c2219b | 2021-08-25 17:47:43 +0000 | [diff] [blame] | 31 | |
Paul Duffin | 67b9d61 | 2021-07-21 17:38:47 +0100 | [diff] [blame] | 32 | |
Paul Duffin | 1e18e98 | 2021-08-03 15:42:27 +0100 | [diff] [blame^] | 33 | def dotPackageToSlashPackage(pkg): |
| 34 | return pkg.replace('.', '/') |
| 35 | |
| 36 | |
| 37 | def slashPackageToDotPackage(pkg): |
| 38 | return pkg.replace('/', '.') |
| 39 | |
| 40 | |
| 41 | def isSplitPackage(splitPackages, pkg): |
| 42 | return splitPackages and (pkg in splitPackages or '*' in splitPackages) |
| 43 | |
| 44 | |
| 45 | def 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 | |
| 55 | def 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 | |
| 85 | def 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 | |
| 94 | def produce_patterns_from_file(file, splitPackages=None, packagePrefixes=None): |
Paul Duffin | 67b9d61 | 2021-07-21 17:38:47 +0100 | [diff] [blame] | 95 | with open(file, 'r') as f: |
Paul Duffin | 1e18e98 | 2021-08-03 15:42:27 +0100 | [diff] [blame^] | 96 | return produce_patterns_from_stream(f, splitPackages, packagePrefixes) |
Paul Duffin | 67b9d61 | 2021-07-21 17:38:47 +0100 | [diff] [blame] | 97 | |
Spandan Das | 2c2219b | 2021-08-25 17:47:43 +0000 | [diff] [blame] | 98 | |
Paul Duffin | 1e18e98 | 2021-08-03 15:42:27 +0100 | [diff] [blame^] | 99 | def 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 Duffin | 6ffdff8 | 2021-08-09 13:47:19 +0100 | [diff] [blame] | 106 | patterns = set() |
| 107 | for row in dict_reader(stream): |
Paul Duffin | 67b9d61 | 2021-07-21 17:38:47 +0100 | [diff] [blame] | 108 | signature = row['signature'] |
Paul Duffin | 1e18e98 | 2021-08-03 15:42:27 +0100 | [diff] [blame^] | 109 | text = signature.removeprefix('L') |
Paul Duffin | 6ffdff8 | 2021-08-09 13:47:19 +0100 | [diff] [blame] | 110 | # Remove the class specific member signature |
Paul Duffin | 1e18e98 | 2021-08-03 15:42:27 +0100 | [diff] [blame^] | 111 | pieces = text.split(';->') |
Paul Duffin | 6ffdff8 | 2021-08-09 13:47:19 +0100 | [diff] [blame] | 112 | qualifiedClassName = pieces[0] |
Paul Duffin | 1e18e98 | 2021-08-03 15:42:27 +0100 | [diff] [blame^] | 113 | 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 Duffin | 6ffdff8 | 2021-08-09 13:47:19 +0100 | [diff] [blame] | 132 | patterns.add(pattern) |
| 133 | |
Paul Duffin | 1e18e98 | 2021-08-03 15:42:27 +0100 | [diff] [blame^] | 134 | # 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 Duffin | 6ffdff8 | 2021-08-09 13:47:19 +0100 | [diff] [blame] | 143 | patterns.sort() |
Paul Duffin | 67b9d61 | 2021-07-21 17:38:47 +0100 | [diff] [blame] | 144 | return patterns |
| 145 | |
Spandan Das | 2c2219b | 2021-08-25 17:47:43 +0000 | [diff] [blame] | 146 | |
Paul Duffin | 67b9d61 | 2021-07-21 17:38:47 +0100 | [diff] [blame] | 147 | def main(args): |
Spandan Das | 2c2219b | 2021-08-25 17:47:43 +0000 | [diff] [blame] | 148 | args_parser = argparse.ArgumentParser( |
| 149 | description='Generate a set of signature patterns ' |
Paul Duffin | 1e18e98 | 2021-08-03 15:42:27 +0100 | [diff] [blame^] | 150 | 'that select a subset of monolithic hidden API files.') |
Spandan Das | 2c2219b | 2021-08-25 17:47:43 +0000 | [diff] [blame] | 151 | args_parser.add_argument( |
| 152 | '--flags', |
| 153 | help='The stub flags file which contains an entry for every dex member', |
| 154 | ) |
Paul Duffin | 1e18e98 | 2021-08-03 15:42:27 +0100 | [diff] [blame^] | 155 | 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 Duffin | 67b9d61 | 2021-07-21 17:38:47 +0100 | [diff] [blame] | 164 | args_parser.add_argument('--output', help='Generated signature prefixes') |
| 165 | args = args_parser.parse_args(args) |
| 166 | |
Paul Duffin | 1e18e98 | 2021-08-03 15:42:27 +0100 | [diff] [blame^] | 167 | 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 Duffin | 67b9d61 | 2021-07-21 17:38:47 +0100 | [diff] [blame] | 181 | # Read in all the patterns into a list. |
Paul Duffin | 1e18e98 | 2021-08-03 15:42:27 +0100 | [diff] [blame^] | 182 | patterns = produce_patterns_from_file(args.flags, splitPackages, |
| 183 | packagePrefixes) |
Paul Duffin | 67b9d61 | 2021-07-21 17:38:47 +0100 | [diff] [blame] | 184 | |
| 185 | # Write out all the patterns. |
| 186 | with open(args.output, 'w') as outputFile: |
| 187 | for pattern in patterns: |
| 188 | outputFile.write(pattern) |
Paul Duffin | 1e18e98 | 2021-08-03 15:42:27 +0100 | [diff] [blame^] | 189 | outputFile.write('\n') |
Paul Duffin | 67b9d61 | 2021-07-21 17:38:47 +0100 | [diff] [blame] | 190 | |
Spandan Das | 2c2219b | 2021-08-25 17:47:43 +0000 | [diff] [blame] | 191 | |
Paul Duffin | 1e18e98 | 2021-08-03 15:42:27 +0100 | [diff] [blame^] | 192 | if __name__ == '__main__': |
Paul Duffin | 67b9d61 | 2021-07-21 17:38:47 +0100 | [diff] [blame] | 193 | main(sys.argv[1:]) |