mirror of
https://github.com/espressif/esp-idf.git
synced 2026-04-27 19:13:21 +00:00
Merge branch 'feat/split_hints' into 'master'
feat(tools): Extended `hints.yml` loading from components Closes IDF-10976 See merge request espressif/esp-idf!44398
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
-
|
||||
re: "error: invalid use of incomplete typedef 'esp_tls_t'"
|
||||
hint: "The struct 'esp_tls_t' has now been made private - its elements can be only be accessed/modified through respective getter/setter functions. Please refer to the migration guide for more information."
|
||||
-
|
||||
re: "fatal error: .*atca_mbedtls_wrap\\.h: No such file or directory"
|
||||
hint: "To use CONFIG_ESP_TLS_USE_SECURE_ELEMENT option, please install `esp-cryptoauthlib` using 'idf.py add-dependency espressif/esp-cryptoauthlib'"
|
||||
@@ -161,7 +161,7 @@ There are also some format specific options, which are listed below:
|
||||
Hints on How to Resolve Errors
|
||||
==============================
|
||||
|
||||
``idf.py`` will try to suggest hints on how to resolve errors. It works with a database of hints stored in :idf_file:`tools/idf_py_actions/hints.yml` and the hints will be printed if a match is found for the given error. The menuconfig target is not supported at the moment by automatic hints on resolving errors.
|
||||
``idf.py`` will try to suggest hints on how to resolve errors. It works with a database of hints stored in :idf_file:`tools/idf_py_actions/hints.yml`. In addition, it loads component-specific hints from ``hints.yml`` file located in the root directory of any ESP-IDF or project component. The hints will be printed if a match is found for the given error. The menuconfig target is not supported at the moment by automatic hints on resolving errors.
|
||||
|
||||
The ``--no-hints`` argument of ``idf.py`` can be used to turn the hints off in case they are not desired.
|
||||
|
||||
|
||||
@@ -161,7 +161,7 @@ uf2 二进制文件也可以通过 :ref:`idf.py uf2 <generate-uf2-binary>` 生
|
||||
错误处理提示
|
||||
==============================
|
||||
|
||||
``idf.py`` 使用存储在 :idf_file:`tools/idf_py_actions/hints.yml` 中的提示数据库,当找到与给定错误相匹配的提示时,``idf.py`` 会打印该提示以尝试提供解决方案。目前,错误处理提示不支持 menuconfig 对象。
|
||||
``idf.py`` 会尝试提示如何解决错误。它会使用存储在 :idf_file:`tools/idf_py_actions/hints.yml` 中的提示数据库。此外,它还会从任意 ESP-IDF 组件或项目组件根目录下的 ``hints.yml`` 文件中加载组件特定的提示。如果发现与给定错误匹配的内容,系统将会打印出相应的提示。目前,menuconfig 目标尚不支持该自动错误解决提示。
|
||||
|
||||
若无需该功能,可以通过 ``idf.py`` 的 ``--no-hints`` 参数关闭提示。
|
||||
|
||||
|
||||
@@ -208,6 +208,8 @@ def action_extensions(base_actions: dict, project_path: str) -> dict:
|
||||
time.sleep(0.5)
|
||||
except KeyboardInterrupt:
|
||||
print('Terminated -> exiting debug utility targets')
|
||||
if processes['allow_hints']:
|
||||
ensure_build_directory(args, ctx.info_name) # initialize build context for hints
|
||||
_terminate_async_target('openocd')
|
||||
_terminate_async_target('gdbgui')
|
||||
|
||||
|
||||
@@ -180,10 +180,6 @@
|
||||
re: "fatal error: esp_adc_cal.h: No such file or directory"
|
||||
hint: "``esp_adc_cal`` component is no longer supported. New adc calibration driver is in ``esp_adc``. Legacy adc calibration driver has been moved into ``esp_adc`` component. To use legacy ``esp_adc_cal`` driver APIs, you should add ``esp_adc`` component to the list of component requirements in CMakeLists.txt. For more information run 'idf.py docs -sp migration-guides/release-5.x/peripherals.html'."
|
||||
|
||||
-
|
||||
re: "fatal error: .*atca_mbedtls_wrap\\.h: No such file or directory"
|
||||
hint: "To use CONFIG_ESP_TLS_USE_SECURE_ELEMENT option, please install `esp-cryptoauthlib` using 'idf.py add-dependency espressif/esp-cryptoauthlib'"
|
||||
|
||||
-
|
||||
re: "The CMAKE_[A-Z]+_COMPILER: [\\w+-]+ is not a full path and was not found in the PATH\\."
|
||||
hint: "Try to reinstall the toolchain for the chip that you trying to use. \nFor more information run 'idf.py docs -sp get-started/#installation' and follow the instructions for your system"
|
||||
@@ -231,10 +227,6 @@
|
||||
re: "Failed to resolve component 'newlib'"
|
||||
hint: "newlib component has been renamed to esp_libc. Any `REQUIRES newlib` can simply be deleted as esp_libc is REQUIRED by default."
|
||||
|
||||
-
|
||||
re: "error: invalid use of incomplete typedef 'esp_tls_t'"
|
||||
hint: "The struct 'esp_tls_t' has now been made private - its elements can be only be accessed/modified through respective getter/setter functions. Please refer to the migration guide for more information."
|
||||
|
||||
-
|
||||
re: "error: enumeration value 'HTTP_EVENT_REDIRECT' not handled in switch"
|
||||
hint: "The event handler, specified in the 'event_handler' element, of the 'esp_http_client_config_t' struct now needs to handle the new 'HTTP_EVENT_REDIRECT' event case."
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
import asyncio
|
||||
import importlib
|
||||
@@ -14,6 +14,7 @@ from re import Match
|
||||
from types import FunctionType
|
||||
from typing import Any
|
||||
from typing import TextIO
|
||||
from typing import cast
|
||||
|
||||
import click
|
||||
import yaml
|
||||
@@ -183,34 +184,100 @@ def debug_print_idf_version() -> None:
|
||||
print_warning(f'ESP-IDF {idf_version() or "version unknown"}')
|
||||
|
||||
|
||||
def load_hints() -> dict:
|
||||
"""Helper function to load hints yml file"""
|
||||
hints: dict = {'yml': [], 'modules': []}
|
||||
def _load_hints_from_directory(directory: str) -> list:
|
||||
"""Load hints file in the given directory"""
|
||||
hints_file = os.path.join(directory, 'hints.yml')
|
||||
if not os.path.exists(hints_file):
|
||||
return []
|
||||
|
||||
current_module_dir = os.path.dirname(__file__)
|
||||
with open(os.path.join(current_module_dir, 'hints.yml'), encoding='utf-8') as file:
|
||||
hints['yml'] = yaml.safe_load(file)
|
||||
try:
|
||||
with open(hints_file, encoding='utf-8') as file:
|
||||
hints = yaml.safe_load(file)
|
||||
return hints if hints else []
|
||||
except (OSError, yaml.YAMLError):
|
||||
yellow_print(f'HINT WARNING: Failed to load hints from "{hints_file}"')
|
||||
return []
|
||||
|
||||
hint_modules_dir = os.path.join(current_module_dir, 'hint_modules')
|
||||
if not os.path.exists(hint_modules_dir):
|
||||
return hints
|
||||
|
||||
sys.path.append(hint_modules_dir)
|
||||
for _, name, _ in iter_modules([hint_modules_dir]):
|
||||
# Import modules for hint processing and add list of their 'generate_hint' functions into hint dict.
|
||||
# If the module doesn't have the function 'generate_hint', it will raise an exception
|
||||
try:
|
||||
hints['modules'].append(getattr(importlib.import_module(name), 'generate_hint'))
|
||||
except ModuleNotFoundError:
|
||||
red_print(f'Failed to import "{name}" from "{hint_modules_dir}" as a module')
|
||||
raise SystemExit(1)
|
||||
except AttributeError:
|
||||
red_print(f'Module "{name}" does not have function generate_hint.')
|
||||
raise SystemExit(1)
|
||||
def _load_hints_from_project_components(proj_desc: dict) -> list:
|
||||
"""
|
||||
Load hints from project components
|
||||
Excluding ESP-IDF components to avoid duplicates
|
||||
"""
|
||||
hints = []
|
||||
all_component_info = proj_desc.get('all_component_info', {})
|
||||
for comp_name, comp_info in proj_desc.get('build_component_info', {}).items():
|
||||
comp_dir = comp_info.get('dir')
|
||||
if comp_dir and os.path.isdir(comp_dir) and os.path.exists(os.path.join(comp_dir, 'hints.yml')):
|
||||
if comp_name in all_component_info and all_component_info[comp_name].get('source') != 'idf_components':
|
||||
hints.extend(_load_hints_from_directory(comp_dir))
|
||||
|
||||
return hints
|
||||
|
||||
|
||||
def _load_idf_hints() -> dict:
|
||||
"""Load global hints and IDF component hints"""
|
||||
hints: dict[str, list[Any]] = {'yml': [], 'modules': []}
|
||||
current_module_dir = os.path.dirname(__file__)
|
||||
# Load global hints
|
||||
hints['yml'] = _load_hints_from_directory(current_module_dir)
|
||||
|
||||
# Load hint modules
|
||||
hint_modules_dir = os.path.join(current_module_dir, 'hint_modules')
|
||||
if os.path.exists(hint_modules_dir):
|
||||
sys.path.append(hint_modules_dir)
|
||||
for _, name, _ in iter_modules([hint_modules_dir]):
|
||||
# Import modules for hint processing and add list of their 'generate_hint' functions into hint dict.
|
||||
# If the module doesn't have the function 'generate_hint', it will raise an exception
|
||||
try:
|
||||
hints['modules'].append(getattr(importlib.import_module(name), 'generate_hint'))
|
||||
except ModuleNotFoundError:
|
||||
red_print(f'Failed to import "{name}" from "{hint_modules_dir}" as a module')
|
||||
raise SystemExit(1)
|
||||
except AttributeError:
|
||||
red_print(f'Module "{name}" does not have function generate_hint.')
|
||||
raise SystemExit(1)
|
||||
|
||||
# Load ESP-IDF components
|
||||
idf_path = os.environ.get('IDF_PATH')
|
||||
if idf_path:
|
||||
components_dir = os.path.join(os.path.abspath(idf_path), 'components')
|
||||
if os.path.isdir(components_dir):
|
||||
try:
|
||||
for comp_name in os.listdir(components_dir):
|
||||
comp_dir = os.path.join(components_dir, comp_name)
|
||||
if os.path.isdir(comp_dir):
|
||||
hints['yml'].extend(_load_hints_from_directory(comp_dir))
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
return hints
|
||||
|
||||
|
||||
def load_hints(cache: dict = {}) -> dict:
|
||||
"""
|
||||
Helper function to load hints.yml files from global and component sources
|
||||
|
||||
Uses mutable default argument as cache - same dict (argument 'cache') persists its data across all calls.
|
||||
See: https://docs.python.org/3/reference/compound_stmts.html#function-definitions
|
||||
"""
|
||||
|
||||
# Initialize cache structure if first call with IDF hints (global + IDF components)
|
||||
if 'hints' not in cache:
|
||||
cache['hints'] = _load_idf_hints()
|
||||
cache['project_components_loaded'] = False
|
||||
|
||||
# Extend Cached hints with hints from project components if available
|
||||
if not cache.get('project_components_loaded'):
|
||||
proj_desc = get_build_context().get('proj_desc')
|
||||
if proj_desc:
|
||||
project_hints = _load_hints_from_project_components(proj_desc)
|
||||
cache['hints']['yml'].extend(project_hints)
|
||||
cache['project_components_loaded'] = True
|
||||
|
||||
return cast(dict, cache['hints'])
|
||||
|
||||
|
||||
def generate_hints_buffer(output: str, hints: dict) -> Generator:
|
||||
"""Helper function to process hints within a string buffer"""
|
||||
# Call modules for possible hints with unchanged output. Note that
|
||||
|
||||
@@ -339,3 +339,47 @@ def test_merge_bin_cmd(idf_py: IdfPyFunc, test_app_copy: Path) -> None:
|
||||
assert (test_app_copy / 'build' / 'merged-binary-2.bin').is_file()
|
||||
idf_py('merge-bin', '--format', 'hex')
|
||||
assert (test_app_copy / 'build' / 'merged-binary.hex').is_file()
|
||||
|
||||
|
||||
def test_hints_components_loading(idf_copy: Path, test_app_copy: Path, idf_py: IdfPyFunc) -> None:
|
||||
logging.info('Testing component hint loading mechanism')
|
||||
logging.debug('Creating test IDF component')
|
||||
|
||||
# Create a test IDF component with hints
|
||||
idf_test_component_dir = idf_copy / 'components' / 'test_idf_comp'
|
||||
idf_test_component_dir.mkdir(parents=True, exist_ok=True)
|
||||
idf_component_hints = textwrap.dedent("""
|
||||
-
|
||||
re: "test_idf_component_error"
|
||||
hint: "HINT FROM IDF COMPONENT: This is a test hint from ESP-IDF component"
|
||||
""")
|
||||
(idf_test_component_dir / 'hints.yml').write_text(idf_component_hints)
|
||||
(idf_test_component_dir / 'CMakeLists.txt').write_text(
|
||||
'idf_component_register(SRCS "test_comp.c" INCLUDE_DIRS ".")'
|
||||
)
|
||||
(idf_test_component_dir / 'test_comp.c').touch()
|
||||
|
||||
logging.debug('Creating test project component')
|
||||
# Create a test project component with hints
|
||||
project_component_dir = test_app_copy / 'components' / 'test_project_comp'
|
||||
project_component_dir.mkdir(parents=True, exist_ok=True)
|
||||
project_component_hints = textwrap.dedent("""
|
||||
-
|
||||
re: "test_project_component_error"
|
||||
hint: "HINT FROM PROJECT COMPONENT: This is a test hint from project component"
|
||||
""")
|
||||
(project_component_dir / 'hints.yml').write_text(project_component_hints)
|
||||
(project_component_dir / 'CMakeLists.txt').write_text('idf_component_register(SRCS "test_comp.c" INCLUDE_DIRS ".")')
|
||||
(project_component_dir / 'test_comp.c').touch()
|
||||
|
||||
error_code = """
|
||||
test_idf_component_error();
|
||||
test_project_component_error();
|
||||
"""
|
||||
replace_in_file(test_app_copy / 'main' / 'build_test_app.c', '// placeholder_inside_main', error_code)
|
||||
|
||||
ret = idf_py('build', check=False)
|
||||
assert 'HINT FROM IDF COMPONENT' in ret.stderr, 'Hint from IDF component should be displayed in build output'
|
||||
assert 'HINT FROM PROJECT COMPONENT' in ret.stderr, (
|
||||
'Hint from project component should be displayed in build output'
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user