from __future__ import annotations import json import shutil from pathlib import Path from typing import Iterable def _load_json(path: Path) -> dict: try: raw = path.read_text(encoding="utf-8") except OSError: return {} try: data = json.loads(raw) except json.JSONDecodeError: return {} return data if isinstance(data, dict) else {} def _from_build(build_dir: Path, relative_path: object) -> Path | None: if not isinstance(relative_path, str) or not relative_path.strip(): return None candidate = build_dir / relative_path if candidate.exists() and candidate.is_file(): return candidate return None def _collect_from_flasher_args(build_dir: Path) -> dict[str, Path]: result: dict[str, Path] = {} data = _load_json(build_dir / "flasher_args.json") flash_files = data.get("flash_files") if isinstance(flash_files, dict): for rel in flash_files.values(): path = _from_build(build_dir, rel) if path is not None: result[path.name] = path for key in ("bootloader", "app", "partition-table"): section = data.get(key) if not isinstance(section, dict): continue path = _from_build(build_dir, section.get("file")) if path is not None: result[path.name] = path return result def _find_in_build_tree(build_dir: Path, name: str) -> Path | None: direct = build_dir / name if direct.exists() and direct.is_file(): return direct for path in build_dir.glob(f"**/{name}"): if path.is_file(): return path return None def sync_bins_from_idf_build( bin_dir: Path, required_bins: Iterable[str], build_dir: Path, ) -> list[tuple[Path, Path]]: if not build_dir.exists() or not build_dir.is_dir(): return [] build_map = _collect_from_flasher_args(build_dir) copied: list[tuple[Path, Path]] = [] seen: set[str] = set() for raw_name in required_bins: name = str(raw_name).strip() if not name or name in seen: continue seen.add(name) target = bin_dir / name if target.exists(): continue source = build_map.get(name) if source is None: source = _find_in_build_tree(build_dir, name) if source is None: continue target.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(source, target) copied.append((source, target)) return copied