feat(factory): add local flashing toolkit and secure OTA defaults
This commit is contained in:
92
tools/esptool-factory/factory_common/idf_artifacts.py
Normal file
92
tools/esptool-factory/factory_common/idf_artifacts.py
Normal file
@@ -0,0 +1,92 @@
|
||||
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
|
||||
Reference in New Issue
Block a user