xref: /aosp_15_r20/external/angle/build/android/gyp/javac_output_processor.py (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
1#!/usr/bin/env python3
2#
3# Copyright 2021 The Chromium Authors
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6"""Contains helper class for processing javac output."""
7
8import os
9import pathlib
10import re
11import shlex
12import sys
13import traceback
14
15from util import build_utils
16from util import dep_utils
17
18sys.path.insert(
19    0,
20    os.path.join(build_utils.DIR_SOURCE_ROOT, 'third_party', 'colorama', 'src'))
21import colorama
22
23
24class JavacOutputProcessor:
25  def __init__(self, target_name):
26    self._target_name = self._RemoveSuffixesIfPresent(
27        ["__compile_java", "__errorprone", "__header"], target_name)
28    self._suggested_targets_list = set()
29
30    # Example: ../../ui/android/java/src/org/chromium/ui/base/Clipboard.java:45:
31    fileline_prefix = (
32        r'(?P<fileline>(?P<file>[-.\w/\\]+.java):(?P<line>[0-9]+):)')
33
34    self._warning_re = re.compile(
35        fileline_prefix + r'(?P<full_message> warning: (?P<message>.*))$')
36    self._error_re = re.compile(fileline_prefix +
37                                r'(?P<full_message> (?P<message>.*))$')
38    self._marker_re = re.compile(r'\s*(?P<marker>\^)\s*$')
39
40    self._symbol_not_found_re_list = [
41        # Example:
42        # error: package org.chromium.components.url_formatter does not exist
43        re.compile(fileline_prefix +
44                   r'( error: package [\w.]+ does not exist)$'),
45        # Example: error: cannot find symbol
46        re.compile(fileline_prefix + r'( error: cannot find symbol)$'),
47        # Example: error: symbol not found org.chromium.url.GURL
48        re.compile(fileline_prefix + r'( error: symbol not found [\w.]+)$'),
49    ]
50
51    # Example: import org.chromium.url.GURL;
52    self._import_re = re.compile(r'\s*import (?P<imported_class>[\w\.]+);$')
53
54    self._warning_color = [
55        'full_message', colorama.Fore.YELLOW + colorama.Style.DIM
56    ]
57    self._error_color = [
58        'full_message', colorama.Fore.MAGENTA + colorama.Style.BRIGHT
59    ]
60    self._marker_color = ['marker', colorama.Fore.BLUE + colorama.Style.BRIGHT]
61
62    self._class_lookup_index = None
63
64    colorama.init()
65
66  def Process(self, lines):
67    """ Processes javac output.
68
69      - Applies colors to output.
70      - Suggests GN dep to add for 'unresolved symbol in Java import' errors.
71      """
72    lines = self._ElaborateLinesForUnknownSymbol(iter(lines))
73    for line in lines:
74      yield self._ApplyColors(line)
75    if self._suggested_targets_list:
76
77      def yellow(text):
78        return colorama.Fore.YELLOW + text + colorama.Fore.RESET
79
80      # Show them in quotes so they can be copy/pasted into BUILD.gn files.
81      yield yellow('Hint:') + ' One or more errors due to missing GN deps.'
82      yield (yellow('Hint:') + ' Try adding the following to ' +
83             yellow(self._target_name))
84
85      for targets in sorted(self._suggested_targets_list):
86        if len(targets) > 1:
87          suggested_targets_str = 'one of: ' + ', '.join(targets)
88        else:
89          suggested_targets_str = targets[0]
90        yield '    "{}",'.format(suggested_targets_str)
91
92      yield ''
93      yield yellow('Hint:') + (' Run the following command to add the missing '
94                               'deps:')
95      missing_targets = {targets[0] for targets in self._suggested_targets_list}
96      cmd = dep_utils.CreateAddDepsCommand(self._target_name,
97                                           sorted(missing_targets))
98      yield f'    {shlex.join(cmd)}\n '  # Extra space necessary for new line.
99
100  def _ElaborateLinesForUnknownSymbol(self, lines):
101    """ Elaborates passed-in javac output for unresolved symbols.
102
103    Looks for unresolved symbols in imports.
104    Adds:
105    - Line with GN target which cannot compile.
106    - Mention of unresolved class if not present in error message.
107    - Line with suggestion of GN dep to add.
108
109    Args:
110      lines: Generator with javac input.
111    Returns:
112      Generator with processed output.
113    """
114    previous_line = next(lines, None)
115    line = next(lines, None)
116    while previous_line != None:
117      try:
118        self._LookForUnknownSymbol(previous_line, line)
119      except Exception:
120        elaborated_lines = ['Error in _LookForUnknownSymbol ---']
121        elaborated_lines += traceback.format_exc().splitlines()
122        elaborated_lines += ['--- end _LookForUnknownSymbol error']
123        for elaborated_line in elaborated_lines:
124          yield elaborated_line
125
126      yield previous_line
127      previous_line = line
128      line = next(lines, None)
129
130  def _ApplyColors(self, line):
131    """Adds colors to passed-in line and returns processed line."""
132    if self._warning_re.match(line):
133      line = self._Colorize(line, self._warning_re, self._warning_color)
134    elif self._error_re.match(line):
135      line = self._Colorize(line, self._error_re, self._error_color)
136    elif self._marker_re.match(line):
137      line = self._Colorize(line, self._marker_re, self._marker_color)
138    return line
139
140  def _LookForUnknownSymbol(self, line, next_line):
141    if not next_line:
142      return
143
144    import_re_match = self._import_re.match(next_line)
145    if not import_re_match:
146      return
147
148    for regex in self._symbol_not_found_re_list:
149      if regex.match(line):
150        break
151    else:
152      return
153
154    if self._class_lookup_index is None:
155      self._class_lookup_index = dep_utils.ClassLookupIndex(
156          pathlib.Path(os.getcwd()),
157          should_build=False,
158      )
159
160    class_to_lookup = import_re_match.group('imported_class')
161    suggested_deps = self._class_lookup_index.match(class_to_lookup)
162
163    if not suggested_deps:
164      print(f'No suggested deps for {class_to_lookup}')
165      return
166
167    suggested_deps = dep_utils.DisambiguateDeps(suggested_deps)
168    self._suggested_targets_list.add(tuple(d.target for d in suggested_deps))
169
170  @staticmethod
171  def _RemoveSuffixesIfPresent(suffixes, text):
172    for suffix in suffixes:
173      if text.endswith(suffix):
174        return text[:-len(suffix)]
175    return text
176
177  @staticmethod
178  def _Colorize(line, regex, color):
179    match = regex.match(line)
180    start = match.start(color[0])
181    end = match.end(color[0])
182    return (line[:start] + color[1] + line[start:end] + colorama.Fore.RESET +
183            colorama.Style.RESET_ALL + line[end:])
184