Merge branch 'bug/interactive_hints_v5.0' into 'release/v5.0'

tools: fix hints processing in interactive mode (v5.0)

See merge request espressif/esp-idf!23804
This commit is contained in:
Roland Dobai 2023-05-19 15:45:04 +08:00
commit 128435993e
2 changed files with 68 additions and 41 deletions

View File

@ -18,8 +18,8 @@ from click.core import Context
from idf_py_actions.constants import GENERATORS, PREVIEW_TARGETS, SUPPORTED_TARGETS, URL_TO_DOC from idf_py_actions.constants import GENERATORS, PREVIEW_TARGETS, SUPPORTED_TARGETS, URL_TO_DOC
from idf_py_actions.errors import FatalError from idf_py_actions.errors import FatalError
from idf_py_actions.global_options import global_options from idf_py_actions.global_options import global_options
from idf_py_actions.tools import (PropertyDict, TargetChoice, ensure_build_directory, get_target, idf_version, from idf_py_actions.tools import (PropertyDict, TargetChoice, ensure_build_directory, generate_hints, get_target,
merge_action_lists, print_hints, run_target) idf_version, merge_action_lists, run_target, yellow_print)
def action_extensions(base_actions: Dict, project_path: str) -> Any: def action_extensions(base_actions: Dict, project_path: str) -> Any:
@ -42,7 +42,8 @@ def action_extensions(base_actions: Dict, project_path: str) -> Any:
""" """
def tool_error_handler(e: int, stdout: str, stderr: str) -> None: def tool_error_handler(e: int, stdout: str, stderr: str) -> None:
print_hints(stdout, stderr) for hint in generate_hints(stdout, stderr):
yellow_print(hint)
ensure_build_directory(args, ctx.info_name) ensure_build_directory(args, ctx.info_name)
run_target('all', args, force_progression=GENERATORS[args.generator].get('force_progression', False), run_target('all', args, force_progression=GENERATORS[args.generator].get('force_progression', False),

View File

@ -8,7 +8,7 @@ import sys
from asyncio.subprocess import Process from asyncio.subprocess import Process
from io import open from io import open
from types import FunctionType from types import FunctionType
from typing import Any, Dict, List, Match, Optional, TextIO, Tuple, Union from typing import Any, Dict, Generator, List, Match, Optional, TextIO, Tuple, Union
import click import click
import yaml import yaml
@ -107,47 +107,57 @@ def debug_print_idf_version() -> None:
print_warning(f'ESP-IDF {idf_version() or "version unknown"}') print_warning(f'ESP-IDF {idf_version() or "version unknown"}')
def print_hints(*filenames: str) -> None: def load_hints() -> Any:
"""Getting output files and printing hints on how to resolve errors based on the output.""" """Helper function to load hints yml file"""
with open(os.path.join(os.path.dirname(__file__), 'hints.yml'), 'r') as file: with open(os.path.join(os.path.dirname(__file__), 'hints.yml'), 'r') as file:
hints = yaml.safe_load(file) hints = yaml.safe_load(file)
return hints
def generate_hints_buffer(output: str, hints: list) -> Generator:
"""Helper function to process hints within a string buffer"""
for hint in hints:
variables_list = hint.get('variables')
hint_list, hint_vars, re_vars = [], [], []
match: Optional[Match[str]] = None
try:
if variables_list:
for variables in variables_list:
hint_vars = variables['hint_variables']
re_vars = variables['re_variables']
regex = hint['re'].format(*re_vars)
if re.compile(regex).search(output):
try:
hint_list.append(hint['hint'].format(*hint_vars))
except KeyError as e:
red_print('Argument {} missing in {}. Check hints.yml file.'.format(e, hint))
sys.exit(1)
else:
match = re.compile(hint['re']).search(output)
except KeyError as e:
red_print('Argument {} missing in {}. Check hints.yml file.'.format(e, hint))
sys.exit(1)
except re.error as e:
red_print('{} from hints.yml have {} problem. Check hints.yml file.'.format(hint['re'], e))
sys.exit(1)
if hint_list:
for message in hint_list:
yield ' '.join(['HINT:', message])
elif match:
extra_info = ', '.join(match.groups()) if hint.get('match_to_output', '') else ''
try:
yield ' '.join(['HINT:', hint['hint'].format(extra_info)])
except KeyError:
raise KeyError("Argument 'hint' missing in {}. Check hints.yml file.".format(hint))
def generate_hints(*filenames: str) -> Generator:
"""Getting output files and printing hints on how to resolve errors based on the output."""
hints = load_hints()
for file_name in filenames: for file_name in filenames:
with open(file_name, 'r') as file: with open(file_name, 'r') as file:
output = ' '.join(line.strip() for line in file if line.strip()) output = ' '.join(line.strip() for line in file if line.strip())
for hint in hints: yield from generate_hints_buffer(output, hints)
variables_list = hint.get('variables')
hint_list, hint_vars, re_vars = [], [], []
match: Optional[Match[str]] = None
try:
if variables_list:
for variables in variables_list:
hint_vars = variables['hint_variables']
re_vars = variables['re_variables']
regex = hint['re'].format(*re_vars)
if re.compile(regex).search(output):
try:
hint_list.append(hint['hint'].format(*hint_vars))
except KeyError as e:
red_print('Argument {} missing in {}. Check hints.yml file.'.format(e, hint))
sys.exit(1)
else:
match = re.compile(hint['re']).search(output)
except KeyError as e:
red_print('Argument {} missing in {}. Check hints.yml file.'.format(e, hint))
sys.exit(1)
except re.error as e:
red_print('{} from hints.yml have {} problem. Check hints.yml file.'.format(hint['re'], e))
sys.exit(1)
if hint_list:
for message in hint_list:
yellow_print('HINT:', message)
elif match:
extra_info = ', '.join(match.groups()) if hint.get('match_to_output', '') else ''
try:
yellow_print(' '.join(['HINT:', hint['hint'].format(extra_info)]))
except KeyError as e:
red_print('Argument {} missing in {}. Check hints.yml file.'.format(e, hint))
sys.exit(1)
def fit_text_in_terminal(out: str) -> str: def fit_text_in_terminal(out: str) -> str:
@ -210,7 +220,10 @@ class RunTool:
return return
if stderr_output_file and stdout_output_file: if stderr_output_file and stdout_output_file:
print_hints(stderr_output_file, stdout_output_file) # hints in interactive mode were already processed, don't print them again
if not self.interactive:
for hint in generate_hints(stderr_output_file, stdout_output_file):
yellow_print(hint)
raise FatalError('{} failed with exit code {}, output of the command is in the {} and {}'.format(self.tool_name, process.returncode, raise FatalError('{} failed with exit code {}, output of the command is in the {} and {}'.format(self.tool_name, process.returncode,
stderr_output_file, stdout_output_file)) stderr_output_file, stdout_output_file))
@ -281,6 +294,10 @@ class RunTool:
# use ANSI color converter for Monitor on Windows # use ANSI color converter for Monitor on Windows
output_converter = get_ansi_converter(output_stream) if self.convert_output else output_stream output_converter = get_ansi_converter(output_stream) if self.convert_output else output_stream
# used in interactive mode to print hints after matched line
hints = load_hints()
last_line = ''
try: try:
with open(output_filename, 'w', encoding='utf8') as output_file: with open(output_filename, 'w', encoding='utf8') as output_file:
while True: while True:
@ -290,6 +307,7 @@ class RunTool:
output = await read_stream() output = await read_stream()
if not output: if not output:
break break
output_noescape = delete_ansi_escape(output) output_noescape = delete_ansi_escape(output)
# Always remove escape sequences when writing the build log. # Always remove escape sequences when writing the build log.
output_file.write(output_noescape) output_file.write(output_noescape)
@ -305,6 +323,14 @@ class RunTool:
else: else:
output_converter.write(output) output_converter.write(output)
output_converter.flush() output_converter.flush()
# process hints for last line and print them right away
if self.interactive:
last_line += output
if last_line[-1] == '\n':
for hint in generate_hints_buffer(last_line, hints):
yellow_print(hint)
last_line = ''
except (RuntimeError, EnvironmentError) as e: except (RuntimeError, EnvironmentError) as e:
yellow_print('WARNING: The exception {} was raised and we can\'t capture all your {} and ' yellow_print('WARNING: The exception {} was raised and we can\'t capture all your {} and '
'hints on how to resolve errors can be not accurate.'.format(e, output_stream.name.strip('<>'))) 'hints on how to resolve errors can be not accurate.'.format(e, output_stream.name.strip('<>')))