refactor: preserve library directory structure in ESP-IDF component conversion

- Maintain original library layout (src/, utility/) instead of flattening files
- Add exclusion logic for non-buildable directories (examples, tests, docs, CI)
- Dynamically generate INCLUDE_DIRS from actual directory structure
- Add validation to ensure buildable source files exist before proceeding
- Support both flat and src-based library layouts
- Fix path separator normalization for cross-platform compatibility

This improves compatibility with complex Arduino libraries that rely on
specific directory structures and relative includes.
This commit is contained in:
ZhadowValker 2026-04-16 07:37:28 +05:30
parent c163b213e4
commit 4ff1502b96
1 changed files with 61 additions and 28 deletions

View File

@ -567,47 +567,80 @@ class ESPIDFCompiler:
comp_dir = user_libs_dir / safe_name
comp_dir.mkdir(parents=True, exist_ok=True)
# Collect all source files — preserve subdirectory structure via INCLUDE_DIRS
# We copy files flat into the component root but add src/ as an include dir
cpp_files: list[str] = []
seen_names: set[str] = set()
# Preserve the original library layout for actual buildable library code
# while skipping repo-only content such as examples, tests, and CI files.
lib_root = src_root.parent if src_root.name == 'src' else src_root
include_dirs: set[str] = {'.'}
cpp_files: set[str] = set()
copied_any = False
for pattern in ('*.h', '*.cpp', '*.c', 'src/*.h', 'src/*.cpp', 'src/*.c'):
for f in src_root.parent.glob(pattern) if pattern.startswith('src/') else src_root.glob(pattern):
if not f.is_file():
continue
dest = comp_dir / f.name
if f.name not in seen_names:
shutil.copy2(f, dest)
seen_names.add(f.name)
if f.suffix in ('.cpp', '.c') and f.name not in cpp_files:
cpp_files.append(f.name)
has_src_layout = (lib_root / 'src').is_dir()
excluded_dirs = {
'.git',
'.github',
'.vscode',
'__pycache__',
'docs',
'doc',
'example',
'examples',
'test',
'tests',
'extras',
'ci',
'fuzz',
'fuzzing',
'benchmark',
'benchmarks',
}
# Also copy from src/ subdirectory if present (e.g. Adafruit libraries)
src_sub = src_root / 'src' if (src_root / 'src').is_dir() else None
if src_sub is None and (src_root.parent / 'src').is_dir():
src_sub = src_root.parent / 'src'
if src_sub:
for f in src_sub.glob('**/*'):
if not f.is_file() or f.suffix not in ('.h', '.cpp', '.c'):
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)
def should_include(relative_path: Path) -> bool:
parts = relative_path.parts
if any(part.lower() in excluded_dirs for part in parts[:-1]):
return False
if relative_path.suffix not in ('.h', '.hpp', '.c', '.cpp'):
return False
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
dest = comp_dir / rel_path
dest.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(f, dest)
copied_any = True
include_dirs.add(str(rel_path.parent).replace('\\', '/'))
if f.suffix in ('.cpp', '.c'):
cpp_files.add(str(rel_path).replace('\\', '/'))
if not copied_any:
raise ValueError(f'No buildable source files found in library {lib_dir_name}')
# Generate CMakeLists.txt for this component
include_dirs.discard('.')
ordered_include_dirs = ['.'] + sorted(d for d in include_dirs if d and d != '.')
if cpp_files:
srcs_line = 'SRCS ' + ' '.join(f'"{f}"' for f in sorted(cpp_files))
else:
srcs_line = '# header-only library'
include_dirs_line = 'INCLUDE_DIRS ' + ' '.join(
f'"{include_dir}"' for include_dir in ordered_include_dirs
)
cmake_content = (
f'# Auto-generated by Velxio for library: {lib_dir_name}\n'
f'idf_component_register(\n'
f' {srcs_line}\n'
f' INCLUDE_DIRS "."\n'
f' {include_dirs_line}\n'
f' REQUIRES {arduino_comp_name}\n'
f')\n'
)