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:
Roland Dobai
2026-02-13 15:40:24 +01:00
7 changed files with 143 additions and 32 deletions
+6
View File
@@ -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'"
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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`` 参数关闭提示。
+2
View File
@@ -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')
-8
View File
@@ -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."
+89 -22
View File
@@ -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
+44
View File
@@ -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'
)