diff options
Diffstat (limited to 'scripts')
| -rwxr-xr-x | scripts/hiddenapi/signature_patterns.py | 154 | ||||
| -rwxr-xr-x | scripts/hiddenapi/signature_patterns_test.py | 92 |
2 files changed, 211 insertions, 35 deletions
diff --git a/scripts/hiddenapi/signature_patterns.py b/scripts/hiddenapi/signature_patterns.py index 0acb2a004..e75ee9566 100755 --- a/scripts/hiddenapi/signature_patterns.py +++ b/scripts/hiddenapi/signature_patterns.py @@ -13,8 +13,10 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -"""Generate a set of signature patterns from the modular flags generated by a -bootclasspath_fragment that can be used to select a subset of monolithic flags +"""Generate a set of signature patterns for a bootclasspath_fragment. + +The patterns are generated from the modular flags produced by the +bootclasspath_fragment and are used to select a subset of the monolithic flags against which the modular flags can be compared. """ @@ -22,33 +24,122 @@ import argparse import csv import sys + def dict_reader(csvfile): return csv.DictReader( - csvfile, delimiter=',', quotechar='|', fieldnames=['signature'] - ) + csvfile, delimiter=',', quotechar='|', fieldnames=['signature']) + + +def dotPackageToSlashPackage(pkg): + return pkg.replace('.', '/') + + +def slashPackageToDotPackage(pkg): + return pkg.replace('/', '.') + + +def isSplitPackage(splitPackages, pkg): + return splitPackages and (pkg in splitPackages or '*' in splitPackages) + + +def matchedByPackagePrefixPattern(packagePrefixes, prefix): + for packagePrefix in packagePrefixes: + if prefix == packagePrefix: + return packagePrefix + elif prefix.startswith(packagePrefix) and prefix[len( + packagePrefix)] == '/': + return packagePrefix + return False + +def validate_package_prefixes(splitPackages, packagePrefixes): + # If there are no package prefixes then there is no possible conflict + # between them and the split packages. + if len(packagePrefixes) == 0: + return -def produce_patterns_from_file(file): + # Check to make sure that the split packages and package prefixes do not + # overlap. + errors = [] + for splitPackage in splitPackages: + if splitPackage == '*': + # A package prefix matches a split package. + packagePrefixesForOutput = ', '.join( + map(slashPackageToDotPackage, packagePrefixes)) + errors.append( + 'split package "*" conflicts with all package prefixes %s\n' + ' add split_packages:[] to fix' % packagePrefixesForOutput) + else: + packagePrefix = matchedByPackagePrefixPattern( + packagePrefixes, splitPackage) + if packagePrefix: + # A package prefix matches a split package. + splitPackageForOutput = slashPackageToDotPackage(splitPackage) + packagePrefixForOutput = slashPackageToDotPackage(packagePrefix) + errors.append( + 'split package %s is matched by package prefix %s' % + (splitPackageForOutput, packagePrefixForOutput)) + return errors + + +def validate_split_packages(splitPackages): + errors = [] + if '*' in splitPackages and len(splitPackages) > 1: + errors.append('split packages are invalid as they contain both the' + ' wildcard (*) and specific packages, use the wildcard or' + ' specific packages, not a mixture') + return errors + + +def produce_patterns_from_file(file, splitPackages=None, packagePrefixes=None): with open(file, 'r') as f: - return produce_patterns_from_stream(f) + return produce_patterns_from_stream(f, splitPackages, packagePrefixes) -def produce_patterns_from_stream(stream): - # Read in all the signatures into a list and remove member names. +def produce_patterns_from_stream(stream, + splitPackages=None, + packagePrefixes=None): + splitPackages = set(splitPackages or []) + packagePrefixes = list(packagePrefixes or []) + # Read in all the signatures into a list and remove any unnecessary class + # and member names. patterns = set() for row in dict_reader(stream): signature = row['signature'] - text = signature.removeprefix("L") + text = signature.removeprefix('L') # Remove the class specific member signature - pieces = text.split(";->") + pieces = text.split(';->') qualifiedClassName = pieces[0] - # Remove inner class names as they cannot be separated - # from the containing outer class. - pieces = qualifiedClassName.split("$", maxsplit=1) - pattern = pieces[0] + pieces = qualifiedClassName.rsplit('/', maxsplit=1) + pkg = pieces[0] + # If the package is split across multiple modules then it cannot be used + # to select the subset of the monolithic flags that this module + # produces. In that case we need to keep the name of the class but can + # discard any nested class names as an outer class cannot be split + # across modules. + # + # If the package is not split then every class in the package must be + # provided by this module so there is no need to list the classes + # explicitly so just use the package name instead. + if isSplitPackage(splitPackages, pkg): + # Remove inner class names. + pieces = qualifiedClassName.split('$', maxsplit=1) + pattern = pieces[0] + else: + # Add a * to ensure that the pattern matches the classes in that + # package. + pattern = pkg + '/*' patterns.add(pattern) - patterns = list(patterns) #pylint: disable=redefined-variable-type + # Remove any patterns that would be matched by a package prefix pattern. + patterns = list( + filter(lambda p: not matchedByPackagePrefixPattern(packagePrefixes, p), + patterns)) + # Add the package prefix patterns to the list. Add a ** to ensure that each + # package prefix pattern will match the classes in that package and all + # sub-packages. + patterns = patterns + list(map(lambda x: x + '/**', packagePrefixes)) + # Sort the patterns. patterns.sort() return patterns @@ -56,24 +147,47 @@ def produce_patterns_from_stream(stream): def main(args): args_parser = argparse.ArgumentParser( description='Generate a set of signature patterns ' - 'that select a subset of monolithic hidden API files.' - ) + 'that select a subset of monolithic hidden API files.') args_parser.add_argument( '--flags', help='The stub flags file which contains an entry for every dex member', ) + args_parser.add_argument( + '--split-package', + action='append', + help='A package that is split across multiple bootclasspath_fragment modules' + ) + args_parser.add_argument( + '--package-prefix', + action='append', + help='A package prefix unique to this set of flags') args_parser.add_argument('--output', help='Generated signature prefixes') args = args_parser.parse_args(args) + splitPackages = set(map(dotPackageToSlashPackage, args.split_package or [])) + errors = validate_split_packages(splitPackages) + + packagePrefixes = list( + map(dotPackageToSlashPackage, args.package_prefix or [])) + + if not errors: + errors = validate_package_prefixes(splitPackages, packagePrefixes) + + if errors: + for error in errors: + print(error) + sys.exit(1) + # Read in all the patterns into a list. - patterns = produce_patterns_from_file(args.flags) + patterns = produce_patterns_from_file(args.flags, splitPackages, + packagePrefixes) # Write out all the patterns. with open(args.output, 'w') as outputFile: for pattern in patterns: outputFile.write(pattern) - outputFile.write("\n") + outputFile.write('\n') -if __name__ == "__main__": +if __name__ == '__main__': main(sys.argv[1:]) diff --git a/scripts/hiddenapi/signature_patterns_test.py b/scripts/hiddenapi/signature_patterns_test.py index 3babe5420..b59dfd71b 100755 --- a/scripts/hiddenapi/signature_patterns_test.py +++ b/scripts/hiddenapi/signature_patterns_test.py @@ -13,37 +13,99 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - """Unit tests for signature_patterns.py.""" import io import unittest -from signature_patterns import * #pylint: disable=unused-wildcard-import,wildcard-import +from signature_patterns import * #pylint: disable=unused-wildcard-import,wildcard-import class TestGeneratedPatterns(unittest.TestCase): - def produce_patterns_from_string(self, csvdata): - with io.StringIO(csvdata) as f: - return produce_patterns_from_stream(f) - def test_generate(self): - #pylint: disable=line-too-long - patterns = self.produce_patterns_from_string( - ''' + csvFlags = """ Ljava/lang/ProcessBuilder$Redirect$1;-><init>()V,blocked Ljava/lang/Character$UnicodeScript;->of(I)Ljava/lang/Character$UnicodeScript;,public-api Ljava/lang/Object;->hashCode()I,public-api,system-api,test-api Ljava/lang/Object;->toString()Ljava/lang/String;,blocked -''' - ) - #pylint: enable=line-too-long +""" + + def produce_patterns_from_string(self, + csv, + splitPackages=None, + packagePrefixes=None): + with io.StringIO(csv) as f: + return produce_patterns_from_stream(f, splitPackages, + packagePrefixes) + + def test_generate_default(self): + patterns = self.produce_patterns_from_string( + TestGeneratedPatterns.csvFlags) + expected = [ + 'java/lang/*', + ] + self.assertEqual(expected, patterns) + + def test_generate_split_package(self): + patterns = self.produce_patterns_from_string( + TestGeneratedPatterns.csvFlags, splitPackages={'java/lang'}) + expected = [ + 'java/lang/Character', + 'java/lang/Object', + 'java/lang/ProcessBuilder', + ] + self.assertEqual(expected, patterns) + + def test_generate_split_package_wildcard(self): + patterns = self.produce_patterns_from_string( + TestGeneratedPatterns.csvFlags, splitPackages={'*'}) + expected = [ + 'java/lang/Character', + 'java/lang/Object', + 'java/lang/ProcessBuilder', + ] + self.assertEqual(expected, patterns) + + def test_generate_package_prefix(self): + patterns = self.produce_patterns_from_string( + TestGeneratedPatterns.csvFlags, packagePrefixes={'java/lang'}) expected = [ - "java/lang/Character", - "java/lang/Object", - "java/lang/ProcessBuilder", + 'java/lang/**', ] self.assertEqual(expected, patterns) + def test_generate_package_prefix_top_package(self): + patterns = self.produce_patterns_from_string( + TestGeneratedPatterns.csvFlags, packagePrefixes={'java'}) + expected = [ + 'java/**', + ] + self.assertEqual(expected, patterns) + + def test_split_package_wildcard_conflicts_with_other_split_packages(self): + errors = validate_split_packages({'*', 'java'}) + expected = [ + 'split packages are invalid as they contain both the wildcard (*)' + ' and specific packages, use the wildcard or specific packages,' + ' not a mixture' + ] + self.assertEqual(expected, errors) + + def test_split_package_wildcard_conflicts_with_package_prefixes(self): + errors = validate_package_prefixes({'*'}, packagePrefixes={'java'}) + expected = [ + 'split package "*" conflicts with all package prefixes java\n' + ' add split_packages:[] to fix', + ] + self.assertEqual(expected, errors) + + def test_split_package_conflict(self): + errors = validate_package_prefixes({'java/split'}, + packagePrefixes={'java'}) + expected = [ + 'split package java.split is matched by package prefix java', + ] + self.assertEqual(expected, errors) + if __name__ == '__main__': unittest.main(verbosity=2) |