fix(espidf): lv_conf.h stops dragging an unrelated library into the build
A sketch whose only LVGL include was <lvgl.h> died on a wall of errors from
Adafruit LvGL Glue, a library it never included. LVGL's sources carry
`#include "lv_conf.h"` behind `#if !LV_CONF_SKIP`; the include scan is
textual and does not evaluate that guard, so the header was queued even for a
build defining LV_CONF_SKIP=1, resolved against the shared library dir, and
found the copy the glue library vendors for LVGL v7. That merged the whole
library, whose sources then failed against the LVGL v8 actually in use
('lv_fs_drv_t' has no member named 'user_data').
Same failure shape as the Hash.h -> stemihexapod case already documented
next to it, so the fix is the same: a project-supplied config header never
resolves against user libraries. A project shipping its own lv_conf.h is
unaffected (sketch files are found in the build's own source dir), and a
project that really wants the glue library still reaches it by including
its own header.
This commit is contained in:
parent
28a1c62887
commit
467cf108ff
|
|
@ -1520,6 +1520,27 @@ class ESPIDFCompiler:
|
|||
'WCharacter.h', 'binary.h', 'pins_arduino.h', 'Esp.h',
|
||||
})
|
||||
|
||||
# Config headers the PROJECT is meant to supply, never a library.
|
||||
#
|
||||
# LVGL's sources do `#include "lv_conf.h"` behind `#if !LV_CONF_SKIP`.
|
||||
# The include scan is textual and does not evaluate that guard, so the
|
||||
# header is queued even for a build that defines LV_CONF_SKIP=1 and will
|
||||
# never read it. Resolving it against the shared library dir then finds
|
||||
# whichever installed library vendors its own copy -- Adafruit LvGL Glue
|
||||
# ships one written for LVGL v7 -- and merges that ENTIRE library. Its
|
||||
# sources fail against the LVGL v8 that the sketch actually uses
|
||||
# ("lv_fs_drv_t has no member named 'user_data'", "'file_t' was not
|
||||
# declared"), so a sketch whose only LVGL include is <lvgl.h> dies with a
|
||||
# wall of errors naming a library the user never asked for. Same failure
|
||||
# shape as the Hash.h -> stemihexapod case above. Reported 2026-08-28.
|
||||
#
|
||||
# A project that DOES ship its own lv_conf.h is unaffected: sketch files
|
||||
# are copied into the build's own source dir and found there, not through
|
||||
# this library scan.
|
||||
_PROJECT_SUPPLIED_HEADERS: frozenset[str] = frozenset({
|
||||
'lv_conf.h', 'lv_drv_conf.h',
|
||||
})
|
||||
|
||||
# Basenames of C/C++ standard headers. A user library that ships a private
|
||||
# header with one of these names (e.g. LovyanGFX's src/lgfx/internal/
|
||||
# limits.h, algorithm.h, memory.h, alloca.h) must NOT put that directory on
|
||||
|
|
@ -1858,6 +1879,16 @@ class ESPIDFCompiler:
|
|||
)
|
||||
continue
|
||||
|
||||
# Project-supplied config headers (lv_conf.h ...). See
|
||||
# _PROJECT_SUPPLIED_HEADERS: resolving these globally drags in
|
||||
# whatever unrelated library happens to vendor a copy.
|
||||
if header in self._PROJECT_SUPPLIED_HEADERS:
|
||||
logger.info(
|
||||
f'[espidf] <{header}> is a project-supplied config header '
|
||||
f'— never resolving against user libraries'
|
||||
)
|
||||
continue
|
||||
|
||||
# P2 manifest scope. When a manifest is supplied, resolve the header
|
||||
# to the DECLARED library that provides it — not the first-
|
||||
# alphabetical lib in the shared dir. Several installed libs may
|
||||
|
|
|
|||
|
|
@ -144,5 +144,72 @@ class TestMergedLibraryReport(unittest.TestCase):
|
|||
self.assertEqual(hdr2comp.get("A.h"), "user_libs_all")
|
||||
|
||||
|
||||
class TestProjectSuppliedHeaders(unittest.TestCase):
|
||||
"""A config header the PROJECT owns must never drag in a library.
|
||||
|
||||
The live failure (2026-08-28, an LVGL clock on a XIAO ESP32-S3): the
|
||||
sketch included only <lvgl.h>, but LVGL's sources carry
|
||||
`#include "lv_conf.h"` behind `#if !LV_CONF_SKIP`. The include scan is
|
||||
textual, so lv_conf.h got queued even though the build defined
|
||||
LV_CONF_SKIP=1, resolved to Adafruit LvGL Glue (which vendors its own
|
||||
copy for LVGL v7), and merged that whole library. Its sources then failed
|
||||
against LVGL v8 and the build died naming a library the user never asked
|
||||
for.
|
||||
"""
|
||||
|
||||
def setUp(self) -> None:
|
||||
self.tmp = Path(tempfile.mkdtemp())
|
||||
self.libs = self.tmp / "libraries"
|
||||
self.out = self.tmp / "project" / "user_libs"
|
||||
self.out.mkdir(parents=True)
|
||||
self.c = ESPIDFCompiler()
|
||||
self.c.arduino_path = ""
|
||||
self.c._core_headers_cache = None
|
||||
|
||||
def tearDown(self) -> None:
|
||||
shutil.rmtree(self.tmp, ignore_errors=True)
|
||||
|
||||
def _resolve(self, headers, merged):
|
||||
return self.c._resolve_library_components(
|
||||
headers, arduino_libs=self.libs, esp32_libs=None,
|
||||
arduino_comp_name="arduino-esp32", user_libs_dir=self.out,
|
||||
merged_libs=merged,
|
||||
)
|
||||
|
||||
def _make_lvgl_world(self) -> None:
|
||||
# LVGL itself: lvgl.h pulls lv_conf.h the way the real sources do.
|
||||
_mk(self.libs / "lvgl@8.3.11-0d103ab1" / "src" / "lvgl.h",
|
||||
'#include "lv_conf.h"\n')
|
||||
_mk(self.libs / "lvgl@8.3.11-0d103ab1" / "library.properties",
|
||||
"name=lvgl\narchitectures=*\n")
|
||||
# The unrelated library that happens to vendor an lv_conf.h.
|
||||
_mk(self.libs / "adafruitlittlevglgluelibrary@2.1.7-896f753c" / "src" / "lv_conf.h")
|
||||
_mk(self.libs / "adafruitlittlevglgluelibrary@2.1.7-896f753c" / "src"
|
||||
/ "Adafruit_LvGL_Glue.h")
|
||||
_mk(self.libs / "adafruitlittlevglgluelibrary@2.1.7-896f753c" / "library.properties",
|
||||
"name=Adafruit LvGL Glue Library\narchitectures=*\n")
|
||||
|
||||
def test_lv_conf_does_not_drag_in_the_glue_library(self):
|
||||
self._make_lvgl_world()
|
||||
merged: dict[str, str] = {}
|
||||
self._resolve(["lvgl.h"], merged)
|
||||
self.assertEqual(merged.get("lvgl.h"), "lvgl")
|
||||
self.assertNotIn("lv_conf.h", merged)
|
||||
self.assertNotIn(
|
||||
"Adafruit LvGL Glue Library", merged.values(),
|
||||
"a sketch that only includes <lvgl.h> must not merge the glue library",
|
||||
)
|
||||
|
||||
def test_a_library_that_owns_lv_conf_is_still_reachable_by_its_own_header(self):
|
||||
# The guard is scoped to the config header, not to the library: a
|
||||
# project that really wants the glue can still include its header.
|
||||
self._make_lvgl_world()
|
||||
merged: dict[str, str] = {}
|
||||
self._resolve(["Adafruit_LvGL_Glue.h"], merged)
|
||||
self.assertEqual(
|
||||
merged.get("Adafruit_LvGL_Glue.h"), "Adafruit LvGL Glue Library"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
|||
Loading…
Reference in New Issue