1#!/usr/bin/env python3 2# 3# Copyright 2021 The Chromium Authors. All rights reserved. 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 sys 12 13from util import build_utils 14 15sys.path.insert( 16 0, 17 os.path.join(build_utils.DIR_SOURCE_ROOT, 'third_party', 'colorama', 'src')) 18import colorama 19sys.path.insert( 20 0, 21 os.path.join(build_utils.DIR_SOURCE_ROOT, 'tools', 'android', 22 'modularization', 'convenience')) 23import lookup_dep 24 25 26class JavacOutputProcessor: 27 def __init__(self, target_name): 28 self._target_name = target_name 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 # Matches output modification performed by _ElaborateLineForUnknownSymbol() 41 # so that it can be colorized. 42 # Example: org.chromium.base.Log found in dep //base:base_java. 43 self._please_add_dep_re = re.compile( 44 r'(?P<full_message>Please add //[\w/:]+ dep to //[\w/:]+.*)$') 45 46 # First element in pair is bool which indicates whether the missing 47 # class/package is part of the error message. 48 self._symbol_not_found_re_list = [ 49 # Example: 50 # error: package org.chromium.components.url_formatter does not exist 51 (True, 52 re.compile(fileline_prefix + 53 r'( error: package [\w.]+ does not exist)$')), 54 # Example: error: cannot find symbol 55 (False, re.compile(fileline_prefix + r'( error: cannot find symbol)$')), 56 # Example: error: symbol not found org.chromium.url.GURL 57 (True, 58 re.compile(fileline_prefix + r'( error: symbol not found [\w.]+)$')), 59 ] 60 61 # Example: import org.chromium.url.GURL; 62 self._import_re = re.compile(r'\s*import (?P<imported_class>[\w\.]+);$') 63 64 self._warning_color = [ 65 'full_message', colorama.Fore.YELLOW + colorama.Style.DIM 66 ] 67 self._error_color = [ 68 'full_message', colorama.Fore.MAGENTA + colorama.Style.BRIGHT 69 ] 70 self._marker_color = ['marker', colorama.Fore.BLUE + colorama.Style.BRIGHT] 71 72 self._class_lookup_index = None 73 74 colorama.init() 75 76 def Process(self, lines): 77 """ Processes javac output. 78 79 - Applies colors to output. 80 - Suggests GN dep to add for 'unresolved symbol in Java import' errors. 81 """ 82 lines = self._ElaborateLinesForUnknownSymbol(iter(lines)) 83 return (self._ApplyColors(l) for l in lines) 84 85 def _ElaborateLinesForUnknownSymbol(self, lines): 86 """ Elaborates passed-in javac output for unresolved symbols. 87 88 Looks for unresolved symbols in imports. 89 Adds: 90 - Line with GN target which cannot compile. 91 - Mention of unresolved class if not present in error message. 92 - Line with suggestion of GN dep to add. 93 94 Args: 95 lines: Generator with javac input. 96 Returns: 97 Generator with processed output. 98 """ 99 previous_line = next(lines, None) 100 line = next(lines, None) 101 while previous_line != None: 102 elaborated_lines = self._ElaborateLineForUnknownSymbol( 103 previous_line, line) 104 for elaborated_line in elaborated_lines: 105 yield elaborated_line 106 107 previous_line = line 108 line = next(lines, None) 109 110 def _ApplyColors(self, line): 111 """Adds colors to passed-in line and returns processed line.""" 112 if self._warning_re.match(line): 113 line = self._Colorize(line, self._warning_re, self._warning_color) 114 elif self._error_re.match(line): 115 line = self._Colorize(line, self._error_re, self._error_color) 116 elif self._please_add_dep_re.match(line): 117 line = self._Colorize(line, self._please_add_dep_re, self._error_color) 118 elif self._marker_re.match(line): 119 line = self._Colorize(line, self._marker_re, self._marker_color) 120 return line 121 122 def _ElaborateLineForUnknownSymbol(self, line, next_line): 123 if not next_line: 124 return [line] 125 126 import_re_match = self._import_re.match(next_line) 127 if not import_re_match: 128 return [line] 129 130 symbol_missing = False 131 has_missing_symbol_in_error_msg = False 132 for symbol_in_error_msg, regex in self._symbol_not_found_re_list: 133 if regex.match(line): 134 symbol_missing = True 135 has_missing_symbol_in_error_msg = symbol_in_error_msg 136 break 137 138 if not symbol_missing: 139 return [line] 140 141 class_to_lookup = import_re_match.group('imported_class') 142 if self._class_lookup_index == None: 143 self._class_lookup_index = lookup_dep.ClassLookupIndex(pathlib.Path( 144 os.getcwd()), 145 should_build=False) 146 suggested_deps = self._class_lookup_index.match(class_to_lookup) 147 148 if len(suggested_deps) != 1: 149 suggested_deps = self._FindFactoryDep(suggested_deps) 150 if len(suggested_deps) != 1: 151 return [line] 152 153 suggested_target = suggested_deps[0].target 154 155 target_name = self._RemoveSuffixesIfPresent( 156 ["__compile_java", "__errorprone", "__header"], self._target_name) 157 if not has_missing_symbol_in_error_msg: 158 line = "{} {}".format(line, class_to_lookup) 159 160 return [ 161 line, 162 "Please add {} dep to {}. ".format(suggested_target, target_name) + 163 "File a crbug if this suggestion is incorrect.", 164 ] 165 166 @staticmethod 167 def _FindFactoryDep(class_entries): 168 """Find the android_library_factory() GN target.""" 169 if len(class_entries) != 2: 170 return [] 171 172 # android_library_factory() targets set low_classpath_priority=true. 173 # This logic is correct if GN targets other than android_library_factory() 174 # set low_classpath_priority=true. low_classpath_priority=true indicates 175 # that the target is depended on (and overridden) by other targets which 176 # contain the same class. We want to recommend the leaf target. 177 if class_entries[0].low_classpath_priority == class_entries[ 178 1].low_classpath_priority: 179 return [] 180 181 if class_entries[0].low_classpath_priority: 182 return [class_entries[0]] 183 return [class_entries[1]] 184 185 @staticmethod 186 def _RemoveSuffixesIfPresent(suffixes, text): 187 for suffix in suffixes: 188 if text.endswith(suffix): 189 return text[:-len(suffix)] 190 return text 191 192 @staticmethod 193 def _Colorize(line, regex, color): 194 match = regex.match(line) 195 start = match.start(color[0]) 196 end = match.end(color[0]) 197 return (line[:start] + color[1] + line[start:end] + colorama.Fore.RESET + 198 colorama.Style.RESET_ALL + line[end:]) 199