From 7b5d30791c1077f308c31606a7e9a51ab98b77ab Mon Sep 17 00:00:00 2001 From: David Montero Date: Tue, 4 Aug 2026 05:23:42 +0200 Subject: [PATCH] espidf: never resolve generic platform headers against user libraries Reported by a user whose every ESP32 build failed with errors from libraries he never installed (SoftwareSerial.h, ArduinoBearSSL.h, Adafruit_GFX.h, AsyncElegantOTA.h) coming out of user_libs_all, in a project whose file list doesn't even show that folder. Traced end to end: ESPAsyncWebServer includes (an ESP8266-core header, guarded for that platform, so arduino-esp32's tree has no such file). The global scan then resolved it to stemi-hexapod's src/Hash.h -- a hexapod-robot library -- merged the WHOLE library into user_libs_all, and its Serial.cpp / Hash.cpp / Server.cpp / BluetoothLowEnergy.cpp / ExpansionDriver.cpp were compiled, each failing on its own unrelated includes. Log line: 'Merging "stemihexapod" into user_libs_all for '. Hash.h and friends now join the never-resolve-against-user-libs set. They cannot be discovered by the core-tree scan (the core genuinely lacks them) yet their names are generic enough that some installed library owns a file by that name. --- backend/app/services/espidf_compiler.py | 14 ++++++++ test/backend/unit/test_espidf_core_first.py | 40 +++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/backend/app/services/espidf_compiler.py b/backend/app/services/espidf_compiler.py index b5fe2b9b..2dfe0134 100644 --- a/backend/app/services/espidf_compiler.py +++ b/backend/app/services/espidf_compiler.py @@ -693,6 +693,20 @@ class ESPIDFCompiler: 'Udp.h', 'IPAddress.h', 'Client.h', 'Server.h', 'Stream.h', 'Print.h', 'Printable.h', 'WiFiUdp.h', 'WiFiClient.h', 'WiFiServer.h', 'WiFiType.h', 'esp_wifi.h', + # Platform headers that do NOT exist in the arduino-esp32 core, so + # the core-tree scan can't discover them, yet carry names generic + # enough that some random installed library owns a file by that + # name. Resolving one drags that whole library into user_libs_all + # and its sources then fail on THEIR own missing includes. + # + # Reported 2026-08-03 (nikas79): ESPAsyncWebServer includes + # (an ESP8266-core header, guarded for that platform) and the global + # scan resolved it to `stemihexapod/src/Hash.h` — a hexapod-robot + # library — whose Serial.cpp / Hash.cpp / Server.cpp / + # BluetoothLowEnergy.cpp / ExpansionDriver.cpp then broke every + # ESP32 build with errors naming libraries the user never installed. + 'Hash.h', 'Serial.h', 'HardwareSerial.h', 'WString.h', + 'WCharacter.h', 'binary.h', 'pins_arduino.h', 'Esp.h', }) # arduino-esp32 uses a single library architecture id ("esp32") across diff --git a/test/backend/unit/test_espidf_core_first.py b/test/backend/unit/test_espidf_core_first.py index 10630528..d3622c8f 100644 --- a/test/backend/unit/test_espidf_core_first.py +++ b/test/backend/unit/test_espidf_core_first.py @@ -171,3 +171,43 @@ class TestManifestScope(unittest.TestCase): if __name__ == "__main__": unittest.main() + + +class TestGenericHeaderCollision(unittest.TestCase): + """Issue reported 2026-08-03 (nikas79): ESPAsyncWebServer includes + (an ESP8266-core header) and the global scan resolved it to a + hexapod-robot library that happens to ship src/Hash.h. The whole library + was merged and its unrelated sources broke every ESP32 build with + 'fatal error: SoftwareSerial.h' and friends.""" + + def setUp(self) -> None: + self.tmp = Path(tempfile.mkdtemp()) + + def tearDown(self) -> None: + shutil.rmtree(self.tmp, ignore_errors=True) + + def test_generic_platform_header_never_resolves_to_a_user_lib(self): + ulibs = self.tmp / "Arduino" / "libraries" + # A library with a generic Hash.h, exactly like stemi-hexapod. + _mk(ulibs / "stemihexapod" / "library.properties", + "name=stemi-hexapod\narchitectures=esp32\n") + _mk(ulibs / "stemihexapod" / "src" / "Hash.h") + _mk(ulibs / "stemihexapod" / "src" / "Serial.cpp", + '#include \n') + + c = ESPIDFCompiler() + c.arduino_path = "" + c._core_headers_cache = None + out = self.tmp / "project" / "user_libs" + out.mkdir(parents=True) + + _names, hdr2comp = c._resolve_library_components( + ["Hash.h", "Server.h", "Serial.h"], + arduino_libs=ulibs, esp32_libs=None, + arduino_comp_name="arduino-esp32", user_libs_dir=out, + ) + for header in ("Hash.h", "Server.h", "Serial.h"): + self.assertNotIn(header, hdr2comp, f"{header} must stay core-provided") + copied = [p.name for p in out.rglob("*") if p.is_file()] + self.assertNotIn("Serial.cpp", copied, + "the unrelated library must NOT be merged")