| """Validates classes in the deployed jar have a max java language level . |
| |
| Usage: |
| python validate-jar-language-level.py <jar-file> <max-java-language-level> |
| """ |
| |
| import re |
| import shutil |
| import subprocess |
| import sys |
| import tempfile |
| import zipfile |
| |
| |
| _LANGUAGE_LEVEL_PATTERN = re.compile(r'major version: (\d+)') |
| |
| |
| def main(argv): |
| if len(argv) > 3: |
| raise ValueError( |
| 'Expected only two arguments but got {0}'.format(len(argv)) |
| ) |
| |
| jar_file, expected_language_level = argv[-2:] |
| print( |
| 'Processing {0} with expected language level {1}...'.format( |
| jar_file, |
| expected_language_level |
| ) |
| ) |
| if jar_file.endswith('.jar'): |
| invalid_entries = _invalid_language_level(jar_file, expected_language_level) |
| elif jar_file.endswith('.aar'): |
| dirpath = tempfile.mkdtemp() |
| with zipfile.ZipFile(jar_file, 'r') as zip_file: |
| class_file = zip_file.extract('classes.jar', dirpath) |
| invalid_entries = _invalid_language_level( |
| class_file, |
| expected_language_level |
| ) |
| shutil.rmtree(dirpath) |
| else: |
| raise ValueError('Invalid jar file: {0}'.format(jar_file)) |
| |
| if invalid_entries: |
| raise ValueError( |
| 'Found invalid entries in {0} that do not match the expected java' |
| ' language level ({1}):\n {2}'.format( |
| jar_file, expected_language_level, '\n '.join(invalid_entries) |
| ) |
| ) |
| |
| |
| def _invalid_language_level(jar_file, expected_language_level): |
| """Returns a list of jar entries with invalid language levels.""" |
| invalid_entries = [] |
| with zipfile.ZipFile(jar_file, 'r') as zip_file: |
| class_infolist = [ |
| info for info in zip_file.infolist() |
| if ( |
| not info.is_dir() |
| and info.filename.endswith('.class') |
| and not is_shaded_class(info.filename) |
| ) |
| ] |
| num_classes = len(class_infolist) |
| for i, info in enumerate(class_infolist): |
| cmd = 'javap -cp {0} -v {1}'.format(jar_file, info.filename[:-6]) |
| output1 = subprocess.run( |
| cmd.split(), |
| stdout=subprocess.PIPE, |
| text=True, |
| check=True, |
| ) |
| matches = _LANGUAGE_LEVEL_PATTERN.findall(output1.stdout) |
| if len(matches) != 1: |
| raise ValueError('Expected exactly one match but found: %s' % matches) |
| class_language_level = matches[0] |
| if class_language_level != expected_language_level: |
| invalid_entries.append( |
| '{0}: {1}'.format(info.filename, class_language_level) |
| ) |
| # This can take a while so print an update. |
| print( |
| ' ({0} of {1}) Found language level {2}: {3}'.format( |
| i + 1, |
| num_classes, |
| class_language_level, |
| info.filename, |
| ) |
| ) |
| |
| return invalid_entries |
| |
| |
| def is_shaded_class(filename): |
| # Ignore the shaded deps because we don't really control these classes. |
| shaded_prefixes = [ |
| 'dagger/spi/internal/shaded/', |
| 'dagger/grpc/shaded/', |
| ] |
| for shaded_prefix in shaded_prefixes: |
| if filename.startswith(shaded_prefix): |
| return True |
| return False |
| |
| |
| if __name__ == '__main__': |
| main(sys.argv) |