blob: a0eca1a81d2cbb565d6fac32b6a22c8c1abd3106 [file] [log] [blame]
#!/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 functools
import os
import pathlib
import posixpath
import re
import shlex
import sys
import traceback
from util import dep_utils
_PACKAGE_RE = re.compile(r'^package\s+(.*?)(;|\s*$)', flags=re.MULTILINE)
@functools.cache
def _running_locally():
return os.path.exists('build.ninja')
@functools.cache # pylint: disable=method-cache-max-size-none
def _extract_package_name(path):
data = pathlib.Path(path).read_text('utf-8')
if m := _PACKAGE_RE.search(data):
return m.group(1)
return None
class JavacOutputProcessor:
def __init__(self, target_name):
self._target_name = self._RemoveSuffixesIfPresent(
["__compile_java", "__errorprone", "__header"], target_name)
self._missing_classes_short = set()
self._missing_classes_full = set()
self._suggested_targets = set()
self._unresolvable_classes = set()
# Examples:
# ../../path/to/Foo.java:54: error: cannot find symbol
# ClangProfiler.writeClangProfilingProfile();
# ^
# symbol: variable ClangProfiler
#
# ../../path/to/Foo.java:69: error: could not resolve Linker.LibInfo
# static Linker.LibInfo anyLibInfo() {
# ^
self._resolution_re = re.compile(
# ../../ui/android/java/src/org/chromium/ui/base/Clipboard.java:45:
r'^(?P<path>[-.\w/\\]+.java):(?:[0-9]+):'
# error: package org.chromium.components.url_formatter does not exist
r'(?: error: package [\w.]+ does not exist|'
# error: cannot find symbol
r' error: cannot find symbol|'
# error: could not resolve Linker.LibInfo
r' error: could not resolve (?P<unresolved>[A-Z]\w*).*|'
# error: symbol not found org.chromium.url.GURL
r' error: symbol not found [\w.]+)$'
r'\s*(?:'
# import org.chromium.url.GURL;
r'import (?P<import>[\w\.]+);|'
# import static org.chromium.url.GURL.method;
r'import static (?P<static_import>[\w\.]+)\.\S+;|'
r'.*)$'
r'\s*\^*$'
# symbol: variable ClangProfiler
r'\s*(symbol:\s+variable (?P<variable>[A-Z]\w+))?',
re.MULTILINE)
self._class_lookup_index = None
def Process(self, output):
self._LookForUnknownSymbols(output)
if not self._missing_classes_full:
return output
sb = []
sb.append('💡 One or more errors due to missing GN deps 💡')
if self._unresolvable_classes:
sb.append('Failed to find targets for the following classes:')
for class_name in sorted(self._unresolvable_classes):
sb.append(f'* {class_name}')
if self._suggested_targets:
sb.append('Hint: Try adding the following to ' + self._target_name)
for targets in sorted(self._suggested_targets):
if len(targets) > 1:
suggested_targets_str = 'one of: ' + ', '.join(targets)
else:
suggested_targets_str = targets[0]
# Show them in quotes so they can be copy/pasted into BUILD.gn files.
sb.append(f' "{suggested_targets_str}",')
sb.append('')
sb.append('Hint: Run the following command to add the missing deps:')
missing_targets = {targets[0] for targets in self._suggested_targets}
cmd = dep_utils.CreateAddDepsCommand(self._target_name,
sorted(missing_targets))
sb.append(f' {shlex.join(cmd)}')
sb.append('')
sb.append('Use tools/android/auto_fix_missing_java_deps.py'
' to auto-apply suggestions.')
elif not self._unresolvable_classes:
sb.append(
'Hint: Rebuild with -config no-remote-javac to show missing deps.')
sb.append('')
return output + '\n'.join(sb)
def _LookForUnknownSymbols(self, output):
for m in self._resolution_re.finditer(output):
full_class_name = m.group('import') or m.group('static_import')
if not full_class_name:
short_class_name = m.group('unresolved') or m.group('variable')
if not short_class_name:
continue
if short_class_name in self._missing_classes_short:
continue
path = m.group('path')
if os.path.exists(path):
package_name = _extract_package_name(path)
if not package_name:
continue
else:
# This can happen for .srcjars in turbine.py (which directly supports
# .srcjar files).
# E.g.: "org/chromium/network/mojom/ParsedHeaders.java"
package_name = posixpath.dirname(path).replace('/', '.')
full_class_name = f'{package_name}.{short_class_name}'
if full_class_name not in self._missing_classes_full:
short_class_name = full_class_name.split('.')[-1]
self._missing_classes_short.add(short_class_name)
self._missing_classes_full.add(full_class_name)
if _running_locally():
try:
self._AnalyzeMissingClass(full_class_name)
except Exception:
sys.stderr.write('Error in _AnalyzeMissingClass---\n' +
traceback.format_exc())
return
def _AnalyzeMissingClass(self, class_name):
if self._class_lookup_index is None:
self._class_lookup_index = dep_utils.ClassLookupIndex(
pathlib.Path(os.getcwd()),
should_build=False,
)
suggested_deps = self._class_lookup_index.match(class_name)
if not suggested_deps:
self._unresolvable_classes.add(class_name)
return
suggested_deps = dep_utils.DisambiguateDeps(suggested_deps)
self._suggested_targets.add(tuple(d.target for d in suggested_deps))
@staticmethod
def _RemoveSuffixesIfPresent(suffixes, text):
for suffix in suffixes:
if text.endswith(suffix):
return text[:-len(suffix)]
return text
def Process(target_name, output):
processor = JavacOutputProcessor(target_name)
return processor.Process(output)