fix(espidf): las cabeceras incluidas con comillas tambien resuelven libreria
Arduino trata igual #include "Lib.h" que #include <Lib.h> para librerias, y los ejemplos de los propios fabricantes usan la forma con comillas: los de M5Stack para el Cardputer abren con #include "M5Cardputer.h". Al escanear solo la forma con angulos, ese sketch no llegaba siquiera al resolutor de librerias y moria con 'fatal error: M5Cardputer.h: No such file', mientras el mismo sketch con angulos compilaba sin problema. Cualquiera que pegue un ejemplo oficial se daba de bruces con esto. Se pasan ademas los nombres de los ficheros del propio sketch, para que un #include "Common.h" local siga siendo una cabecera del proyecto y no se confunda con una libreria. test/backend/unit/test_espidf_compiler.py cubre ambas formas, el espaciado, las cabeceras internas de IDF y la cabecera propia del proyecto.
This commit is contained in:
parent
0f68037c35
commit
c8ddfdddba
|
|
@ -1360,11 +1360,27 @@ class ESPIDFCompiler:
|
||||||
)
|
)
|
||||||
return ['user_libs_all'], header_to_comp
|
return ['user_libs_all'], header_to_comp
|
||||||
|
|
||||||
def _detect_external_includes(self, code: str) -> list[str]:
|
def _detect_external_includes(
|
||||||
"""Return library header names that are likely from external libraries."""
|
self, code: str, own_files: set[str] | None = None
|
||||||
|
) -> list[str]:
|
||||||
|
"""Return library header names that are likely from external libraries.
|
||||||
|
|
||||||
|
BOTH include forms count. Arduino treats `#include "Lib.h"` and
|
||||||
|
`#include <Lib.h>` alike for libraries, and vendors' own examples lean on
|
||||||
|
the quoted form — M5Stack ships `#include "M5Cardputer.h"` in theirs. Only
|
||||||
|
scanning the angled form meant such a sketch never reached the library
|
||||||
|
resolver at all and died on `fatal error: M5Cardputer.h: No such file`,
|
||||||
|
while the very same sketch with angle brackets built fine.
|
||||||
|
|
||||||
|
`own_files` are the sketch's own file names; a quoted include naming one
|
||||||
|
of them is a project-local header, not a library.
|
||||||
|
"""
|
||||||
headers = []
|
headers = []
|
||||||
for m in re.finditer(r'#\s*include\s*<([^>]+)>', code):
|
own = own_files or set()
|
||||||
h = m.group(1)
|
for m in re.finditer(r'#\s*include\s*(?:<([^>]+)>|"([^"]+)")', code):
|
||||||
|
h = m.group(1) or m.group(2)
|
||||||
|
if h in own:
|
||||||
|
continue
|
||||||
if h in self._BUILTIN_HEADERS:
|
if h in self._BUILTIN_HEADERS:
|
||||||
continue
|
continue
|
||||||
# Skip paths with / (esp-idf internal headers like freertos/FreeRTOS.h)
|
# Skip paths with / (esp-idf internal headers like freertos/FreeRTOS.h)
|
||||||
|
|
@ -2497,9 +2513,10 @@ class ESPIDFCompiler:
|
||||||
# which causes intermittent "cmake configure failed" and stale-object
|
# which causes intermittent "cmake configure failed" and stale-object
|
||||||
# false positives when a different project/manifest compiles next.
|
# false positives when a different project/manifest compiles next.
|
||||||
_sketch_text = '\n'.join(f.get('content', '') for f in files)
|
_sketch_text = '\n'.join(f.get('content', '') for f in files)
|
||||||
|
_own_names = {Path(str(f.get('name') or '')).name for f in files}
|
||||||
_core_hdrs = self._core_provided_headers()
|
_core_hdrs = self._core_provided_headers()
|
||||||
_ext_inc_token = ','.join(sorted(
|
_ext_inc_token = ','.join(sorted(
|
||||||
h for h in set(self._detect_external_includes(_sketch_text))
|
h for h in set(self._detect_external_includes(_sketch_text, _own_names))
|
||||||
if h not in _core_hdrs
|
if h not in _core_hdrs
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
@ -2811,13 +2828,15 @@ class ESPIDFCompiler:
|
||||||
# was scanned, so libs only referenced from project headers
|
# was scanned, so libs only referenced from project headers
|
||||||
# never reached _resolve_library_components and the build
|
# never reached _resolve_library_components and the build
|
||||||
# died with "fatal error: ESP32Servo.h: No such file".
|
# died with "fatal error: ESP32Servo.h: No such file".
|
||||||
|
own_names = {PurePosixPath(str(_f.get('name') or '').replace('\\', '/')).name
|
||||||
|
for _f in files}
|
||||||
ext_headers_set: set[str] = set(
|
ext_headers_set: set[str] = set(
|
||||||
self._detect_external_includes(main_content)
|
self._detect_external_includes(main_content, own_names)
|
||||||
)
|
)
|
||||||
for _f in files:
|
for _f in files:
|
||||||
if _f.get('name', '').endswith(('.h', '.hpp', '.ino', '.c', '.cpp')):
|
if _f.get('name', '').endswith(('.h', '.hpp', '.ino', '.c', '.cpp')):
|
||||||
ext_headers_set.update(
|
ext_headers_set.update(
|
||||||
self._detect_external_includes(_f.get('content', ''))
|
self._detect_external_includes(_f.get('content', ''), own_names)
|
||||||
)
|
)
|
||||||
ext_headers = list(ext_headers_set)
|
ext_headers = list(ext_headers_set)
|
||||||
component_names: list[str] = []
|
component_names: list[str] = []
|
||||||
|
|
|
||||||
|
|
@ -455,3 +455,48 @@ if __name__ == '__main__':
|
||||||
runner = unittest.TextTestRunner(verbosity=2)
|
runner = unittest.TextTestRunner(verbosity=2)
|
||||||
result = runner.run(suite)
|
result = runner.run(suite)
|
||||||
sys.exit(0 if result.wasSuccessful() else 1)
|
sys.exit(0 if result.wasSuccessful() else 1)
|
||||||
|
|
||||||
|
|
||||||
|
class TestDetectExternalIncludesQuotedForm(unittest.TestCase):
|
||||||
|
"""Both include forms must reach the library resolver.
|
||||||
|
|
||||||
|
Arduino treats `#include "Lib.h"` and `#include <Lib.h>` alike for
|
||||||
|
libraries, and vendors' own examples use the quoted form — M5Stack's
|
||||||
|
Cardputer examples open with `#include "M5Cardputer.h"`. Scanning only the
|
||||||
|
angled form meant those sketches never reached the resolver and died on
|
||||||
|
`fatal error: M5Cardputer.h: No such file`, while the same sketch with angle
|
||||||
|
brackets built fine.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.comp = make_compiler()
|
||||||
|
|
||||||
|
def test_angled_include_is_detected(self):
|
||||||
|
self.assertIn('M5Cardputer.h',
|
||||||
|
self.comp._detect_external_includes('#include <M5Cardputer.h>'))
|
||||||
|
|
||||||
|
def test_quoted_include_is_detected(self):
|
||||||
|
self.assertIn('M5Cardputer.h',
|
||||||
|
self.comp._detect_external_includes('#include "M5Cardputer.h"'))
|
||||||
|
|
||||||
|
def test_both_forms_in_one_sketch(self):
|
||||||
|
code = '#include "M5Cardputer.h"\n#include <M5GFX.h>\n'
|
||||||
|
found = self.comp._detect_external_includes(code)
|
||||||
|
self.assertIn('M5Cardputer.h', found)
|
||||||
|
self.assertIn('M5GFX.h', found)
|
||||||
|
|
||||||
|
def test_a_projects_own_header_is_not_a_library(self):
|
||||||
|
code = '#include "Common.h"\n#include "M5Cardputer.h"\n'
|
||||||
|
found = self.comp._detect_external_includes(code, {'Common.h', 'sketch.ino'})
|
||||||
|
self.assertNotIn('Common.h', found)
|
||||||
|
self.assertIn('M5Cardputer.h', found)
|
||||||
|
|
||||||
|
def test_idf_internal_headers_are_still_skipped(self):
|
||||||
|
code = '#include "freertos/FreeRTOS.h"\n#include "esp_wifi.h"\n'
|
||||||
|
self.assertEqual(self.comp._detect_external_includes(code), [])
|
||||||
|
|
||||||
|
def test_spacing_variants(self):
|
||||||
|
for code in ('#include"M5Cardputer.h"', '# include "M5Cardputer.h"'):
|
||||||
|
with self.subTest(code=code):
|
||||||
|
self.assertIn('M5Cardputer.h',
|
||||||
|
self.comp._detect_external_includes(code))
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue