fix: Apply library structure preservation to _merge_arduino_libs_to_component

_create_idf_component was fixed but NOT _merge_arduino_libs_to_component.
The latter was still flattening library files, causing ArduinoJson compilation to fail
with 'src/ArduinoJson.h: No such file or directory'.

Changes:
- Replace flat copy with directory-structure-preserving copy
- Add exclusion logic for non-buildable directories (examples, tests, docs)
- Generate INCLUDE_DIRS from actual directory structure
- Track files by relative path to prevent name collisions

This ensures libraries with src/ layouts (ArduinoJson, etc.) compile correctly.
This commit is contained in:
ZhadowValker 2026-04-28 09:38:27 +05:30
parent 4ff1502b96
commit 79cfd8197d
1 changed files with 47 additions and 28 deletions

View File

@ -454,32 +454,42 @@ class ESPIDFCompiler:
found_any = True found_any = True
header_to_comp[header] = 'user_libs_all' header_to_comp[header] = 'user_libs_all'
# Copy all source files flat into the merged component directory. # Preserve directory structure while merging libraries.
# First-writer wins for name conflicts (rare across Arduino libs). # Skip non-buildable directories like examples, tests, docs.
for pattern in ('*.h', '*.cpp', '*.c', 'src/*.h', 'src/*.cpp', 'src/*.c'): lib_root = src_root.parent if src_root.name == 'src' else src_root
glob_root = src_root.parent if pattern.startswith('src/') else src_root has_src_layout = (lib_root / 'src').is_dir()
for f in glob_root.glob(pattern):
if not f.is_file():
continue
if f.name not in seen_names:
shutil.copy2(f, comp_dir / f.name)
seen_names.add(f.name)
if f.suffix in ('.cpp', '.c') and f.name not in cpp_files:
cpp_files.append(f.name)
# Also handle libraries that use an src/ subdirectory layout. excluded_dirs = {
src_sub = (src_root / 'src') if (src_root / 'src').is_dir() else None '.git', '.github', '.vscode', '__pycache__',
if src_sub is None and (src_root.parent / 'src').is_dir(): 'docs', 'doc', 'example', 'examples', 'test', 'tests',
src_sub = src_root.parent / 'src' 'extras', 'ci', 'fuzz', 'fuzzing', 'benchmark', 'benchmarks',
if src_sub: }
for f in src_sub.glob('**/*'):
if not f.is_file() or f.suffix not in ('.h', '.cpp', '.c'): def _should_include(rel_path: Path) -> bool:
continue parts = rel_path.parts
if f.name not in seen_names: if any(part.lower() in excluded_dirs for part in parts[:-1]):
shutil.copy2(f, comp_dir / f.name) return False
seen_names.add(f.name) if rel_path.suffix not in ('.h', '.hpp', '.c', '.cpp'):
if f.suffix in ('.cpp', '.c') and f.name not in cpp_files: return False
cpp_files.append(f.name) if has_src_layout:
return parts[0] == 'src' or len(parts) == 1
return len(parts) == 1 or parts[0].lower() == 'utility'
for f in lib_root.rglob('*'):
if not f.is_file():
continue
rel_path = f.relative_to(lib_root)
if not _should_include(rel_path):
continue
# Track file by its relative path to preserve structure
file_key = str(rel_path).replace('\\', '/')
if file_key not in seen_names:
dest = comp_dir / rel_path
dest.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(f, dest)
seen_names.add(file_key)
if f.suffix in ('.cpp', '.c') and file_key not in cpp_files:
cpp_files.append(file_key)
# Scan newly copied headers for transitive includes. # Scan newly copied headers for transitive includes.
for lib_file in comp_dir.glob('*.h'): for lib_file in comp_dir.glob('*.h'):
@ -497,13 +507,22 @@ class ESPIDFCompiler:
return [], {} return [], {}
srcs_line = 'SRCS ' + ' '.join(f'"{f}"' for f in sorted(cpp_files)) if cpp_files else '' srcs_line = 'SRCS ' + ' '.join(f'"{f}"' for f in sorted(cpp_files)) if cpp_files else ''
# Generate INCLUDE_DIRS from the directory structure of copied files
include_dirs: set[str] = {'.'}
for file_key in seen_names:
parent = str(Path(file_key).parent)
if parent and parent != '.':
include_dirs.add(parent)
include_dirs_line = 'INCLUDE_DIRS ' + ' '.join(f'"{d}"' for d in sorted(include_dirs))
cmake_content = ( cmake_content = (
'# Auto-generated by Velxio — all user libraries merged into one component.\n' '# Auto-generated by Velxio — all user libraries merged into one component.\n'
'# Single flat directory: every header sees every other header without\n' '# Directory structure preserved for libraries like ArduinoJson with src/ layout.\n'
'# cross-component REQUIRES propagation.\n'
'idf_component_register(\n' 'idf_component_register(\n'
f' {srcs_line}\n' f' {srcs_line}\n'
' INCLUDE_DIRS "."\n' f' {include_dirs_line}\n'
f' REQUIRES {arduino_comp_name}\n' f' REQUIRES {arduino_comp_name}\n'
')\n' ')\n'
) )