217 lines
7.1 KiB
Python
Executable File
217 lines
7.1 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
#
|
|
# Copyright 2021 The Chromium Authors
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
"""Contains helper class for processing javac output."""
|
|
|
|
import dataclasses
|
|
import os
|
|
import pathlib
|
|
import re
|
|
import sys
|
|
import traceback
|
|
from typing import List
|
|
|
|
from util import build_utils
|
|
|
|
sys.path.insert(
|
|
0,
|
|
os.path.join(build_utils.DIR_SOURCE_ROOT, 'third_party', 'colorama', 'src'))
|
|
import colorama
|
|
sys.path.insert(
|
|
0,
|
|
os.path.join(build_utils.DIR_SOURCE_ROOT, 'tools', 'android',
|
|
'modularization', 'convenience'))
|
|
import lookup_dep
|
|
|
|
|
|
def ReplaceGmsPackageIfNeeded(target_name: str) -> str:
|
|
if target_name.startswith(
|
|
('//third_party/android_deps:google_play_services_',
|
|
'//clank/third_party/google3:google_play_services_')):
|
|
return f'$google_play_services_package:{target_name.split(":")[1]}'
|
|
return target_name
|
|
|
|
|
|
def _DisambiguateDeps(class_entries: List[lookup_dep.ClassEntry]):
|
|
def filter_if_not_empty(entries, filter_func):
|
|
filtered_entries = [e for e in entries if filter_func(e)]
|
|
return filtered_entries or entries
|
|
|
|
# When some deps are preferred, ignore all other potential deps.
|
|
class_entries = filter_if_not_empty(class_entries, lambda e: e.preferred_dep)
|
|
|
|
# E.g. javax_annotation_jsr250_api_java.
|
|
class_entries = filter_if_not_empty(class_entries,
|
|
lambda e: 'jsr' in e.target)
|
|
|
|
# Avoid suggesting subtargets when regular targets exist.
|
|
class_entries = filter_if_not_empty(class_entries,
|
|
lambda e: '__' not in e.target)
|
|
|
|
# Swap out GMS package names if needed.
|
|
class_entries = [
|
|
dataclasses.replace(e, target=ReplaceGmsPackageIfNeeded(e.target))
|
|
for e in class_entries
|
|
]
|
|
|
|
# Convert to dict and then use list to get the keys back to remove dups and
|
|
# keep order the same as before.
|
|
class_entries = list({e: True for e in class_entries})
|
|
|
|
return class_entries
|
|
|
|
|
|
class JavacOutputProcessor:
|
|
def __init__(self, target_name):
|
|
self._target_name = self._RemoveSuffixesIfPresent(
|
|
["__compile_java", "__errorprone", "__header"], target_name)
|
|
self._suggested_deps = set()
|
|
|
|
# Example: ../../ui/android/java/src/org/chromium/ui/base/Clipboard.java:45:
|
|
fileline_prefix = (
|
|
r'(?P<fileline>(?P<file>[-.\w/\\]+.java):(?P<line>[0-9]+):)')
|
|
|
|
self._warning_re = re.compile(
|
|
fileline_prefix + r'(?P<full_message> warning: (?P<message>.*))$')
|
|
self._error_re = re.compile(fileline_prefix +
|
|
r'(?P<full_message> (?P<message>.*))$')
|
|
self._marker_re = re.compile(r'\s*(?P<marker>\^)\s*$')
|
|
|
|
self._symbol_not_found_re_list = [
|
|
# Example:
|
|
# error: package org.chromium.components.url_formatter does not exist
|
|
re.compile(fileline_prefix +
|
|
r'( error: package [\w.]+ does not exist)$'),
|
|
# Example: error: cannot find symbol
|
|
re.compile(fileline_prefix + r'( error: cannot find symbol)$'),
|
|
# Example: error: symbol not found org.chromium.url.GURL
|
|
re.compile(fileline_prefix + r'( error: symbol not found [\w.]+)$'),
|
|
]
|
|
|
|
# Example: import org.chromium.url.GURL;
|
|
self._import_re = re.compile(r'\s*import (?P<imported_class>[\w\.]+);$')
|
|
|
|
self._warning_color = [
|
|
'full_message', colorama.Fore.YELLOW + colorama.Style.DIM
|
|
]
|
|
self._error_color = [
|
|
'full_message', colorama.Fore.MAGENTA + colorama.Style.BRIGHT
|
|
]
|
|
self._marker_color = ['marker', colorama.Fore.BLUE + colorama.Style.BRIGHT]
|
|
|
|
self._class_lookup_index = None
|
|
|
|
colorama.init()
|
|
|
|
def Process(self, lines):
|
|
""" Processes javac output.
|
|
|
|
- Applies colors to output.
|
|
- Suggests GN dep to add for 'unresolved symbol in Java import' errors.
|
|
"""
|
|
lines = self._ElaborateLinesForUnknownSymbol(iter(lines))
|
|
for line in lines:
|
|
yield self._ApplyColors(line)
|
|
if self._suggested_deps:
|
|
|
|
def yellow(text):
|
|
return colorama.Fore.YELLOW + text + colorama.Fore.RESET
|
|
|
|
# Show them in quotes so they can be copy/pasted into BUILD.gn files.
|
|
yield yellow('Hint:') + ' One or more errors due to missing GN deps.'
|
|
yield (yellow('Hint:') + ' Try adding the following to ' +
|
|
yellow(self._target_name))
|
|
for dep in sorted(self._suggested_deps):
|
|
yield ' "{}",'.format(dep)
|
|
|
|
def _ElaborateLinesForUnknownSymbol(self, lines):
|
|
""" Elaborates passed-in javac output for unresolved symbols.
|
|
|
|
Looks for unresolved symbols in imports.
|
|
Adds:
|
|
- Line with GN target which cannot compile.
|
|
- Mention of unresolved class if not present in error message.
|
|
- Line with suggestion of GN dep to add.
|
|
|
|
Args:
|
|
lines: Generator with javac input.
|
|
Returns:
|
|
Generator with processed output.
|
|
"""
|
|
previous_line = next(lines, None)
|
|
line = next(lines, None)
|
|
while previous_line != None:
|
|
try:
|
|
self._LookForUnknownSymbol(previous_line, line)
|
|
except Exception:
|
|
elaborated_lines = ['Error in _LookForUnknownSymbol ---']
|
|
elaborated_lines += traceback.format_exc().splitlines()
|
|
elaborated_lines += ['--- end _LookForUnknownSymbol error']
|
|
for elaborated_line in elaborated_lines:
|
|
yield elaborated_line
|
|
|
|
yield previous_line
|
|
previous_line = line
|
|
line = next(lines, None)
|
|
|
|
def _ApplyColors(self, line):
|
|
"""Adds colors to passed-in line and returns processed line."""
|
|
if self._warning_re.match(line):
|
|
line = self._Colorize(line, self._warning_re, self._warning_color)
|
|
elif self._error_re.match(line):
|
|
line = self._Colorize(line, self._error_re, self._error_color)
|
|
elif self._marker_re.match(line):
|
|
line = self._Colorize(line, self._marker_re, self._marker_color)
|
|
return line
|
|
|
|
def _LookForUnknownSymbol(self, line, next_line):
|
|
if not next_line:
|
|
return
|
|
|
|
import_re_match = self._import_re.match(next_line)
|
|
if not import_re_match:
|
|
return
|
|
|
|
for regex in self._symbol_not_found_re_list:
|
|
if regex.match(line):
|
|
break
|
|
else:
|
|
return
|
|
|
|
if self._class_lookup_index is None:
|
|
self._class_lookup_index = lookup_dep.ClassLookupIndex(
|
|
pathlib.Path(os.getcwd()),
|
|
should_build=False,
|
|
)
|
|
|
|
class_to_lookup = import_re_match.group('imported_class')
|
|
suggested_deps = self._class_lookup_index.match(class_to_lookup)
|
|
|
|
if not suggested_deps:
|
|
return
|
|
|
|
suggested_deps = _DisambiguateDeps(suggested_deps)
|
|
suggested_deps_str = ', '.join(s.target for s in suggested_deps)
|
|
|
|
if len(suggested_deps) > 1:
|
|
suggested_deps_str = 'one of: ' + suggested_deps_str
|
|
|
|
self._suggested_deps.add(suggested_deps_str)
|
|
|
|
@staticmethod
|
|
def _RemoveSuffixesIfPresent(suffixes, text):
|
|
for suffix in suffixes:
|
|
if text.endswith(suffix):
|
|
return text[:-len(suffix)]
|
|
return text
|
|
|
|
@staticmethod
|
|
def _Colorize(line, regex, color):
|
|
match = regex.match(line)
|
|
start = match.start(color[0])
|
|
end = match.end(color[0])
|
|
return (line[:start] + color[1] + line[start:end] + colorama.Fore.RESET +
|
|
colorama.Style.RESET_ALL + line[end:])
|