fix(compile): let an Arduino sketch include IDF-tree headers

arduino-esp32 3.x keeps most of its requires PRIVATE, so a sketch that
includes esp_lcd_panel_io.h dies with 'No such file or directory' -
while the SAME header resolves fine as soon as any user library is
present, because the merged user-libs component requires esp_lcd. That
made the failure look random.

Detect the IDF-tree components a sketch includes and add them to main's
REQUIRES, the same shape as the registry managed-component table next to
it. Guarded on the component existing in the IDF tree, so older IDF
generations are unaffected.

Found writing the ESP32-S3-EYE camera example, and it matters beyond
that board: esp_lcd, esp_cache, esp_ldo_regulator and esp_psram are
exactly what an S3/P4 sketch reaches for once it drives a panel.
This commit is contained in:
David Montero Crespo 2026-08-20 19:04:11 +02:00
parent 00f0b3e69b
commit f4ff3e7430
1 changed files with 56 additions and 0 deletions

View File

@ -908,6 +908,38 @@ class ESPIDFCompiler:
deps[name] = version
return deps
# IDF components that ship INSIDE the IDF tree (not the registry) and are
# NOT on an Arduino sketch's include path by default. arduino-esp32 3.x
# keeps most of its requires PRIVATE, so a sketch that includes one of
# these dies with "No such file or directory" while the very same header
# resolves fine from a user library (whose merged component requires it).
# Hit while writing the ESP32-S3-EYE camera example: esp_lcd_panel_io.h
# compiled only when an unrelated Adafruit library happened to be present.
_IDF_COMPONENT_INCLUDES: tuple[tuple[str, str], ...] = (
(r'esp_lcd_panel_io\.h', 'esp_lcd'),
(r'esp_lcd_panel_ops\.h', 'esp_lcd'),
(r'esp_lcd_panel_vendor\.h', 'esp_lcd'),
(r'esp_lcd_types\.h', 'esp_lcd'),
(r'esp_lcd_mipi_dsi\.h', 'esp_lcd'),
(r'esp_cache\.h', 'esp_mm'),
(r'esp_ldo_regulator\.h', 'esp_hw_support'),
(r'esp_psram\.h', 'esp_psram'),
)
def _detect_idf_components(self, code: str) -> list[str]:
"""IDF-tree components the source #includes, that exist in this IDF."""
wanted: list[str] = []
for pattern, comp in self._IDF_COMPONENT_INCLUDES:
if comp in wanted:
continue
if not re.search(r'#include\s*[<"]' + pattern + r'[">]', code):
continue
if self.idf_path and os.path.isdir(
os.path.join(self.idf_path, 'components', comp)
):
wanted.append(comp)
return wanted
def _detect_camera_usage(self, code: str) -> bool:
"""Does the sketch use the ESP32 camera driver?
@ -3897,6 +3929,30 @@ class ESPIDFCompiler:
managed = self._detect_managed_components(sketch_src)
if managed:
self._add_managed_components(project_dir, managed)
# Same idea for components that live in the IDF tree rather than the
# registry: they need to be in main's REQUIRES or the sketch cannot
# see their headers.
idf_comps = self._detect_idf_components(sketch_src)
if idf_comps:
cmake_path = project_dir / 'main' / 'CMakeLists.txt'
cmake_text = cmake_path.read_text(encoding='utf-8')
missing = [c for c in idf_comps if c not in cmake_text]
if missing:
for old_req in [
r'REQUIRES ${_arduino_comp_name}',
f'REQUIRES {arduino_comp_name}',
]:
if old_req in cmake_text:
cmake_text = cmake_text.replace(
old_req, old_req + ' ' + ' '.join(missing), 1
)
cmake_path.write_text(cmake_text, encoding='utf-8')
logger.info(
f'[espidf] main REQUIRES += {" ".join(missing)} '
'(IDF components the sketch includes)'
)
break
else:
# Pure ESP-IDF mode (no Arduino component usable for this
# target). Remove Arduino main.cpp to avoid conflict.