blob: ef8afed19a525c393416b05b31f1d7463dfab497 [file] [log] [blame] [edit]
"""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)