# support_deployer.py import os import re import shutil from pathlib import Path from dataclasses import dataclass, field from typing import Iterable, Optional VERSION_RE = re.compile(r"^\d+(?:\.\d+)*$") # 15 / 15.6 / 16.7 / 16.7.1 def _find_support_root(hint: Optional[Path]) -> Optional[Path]: """ 1) 优先:显式传入的 hint 2) 其次:环境变量 SUPPORT_DDI_DIR 3) 再次:从 __file__ 所在目录向上搜索 3 层,找名为 'SupportFiles' 的目录 """ # 1) 显式参数 if hint and hint.exists(): return hint.resolve() # 2) 环境变量 env = os.environ.get("SUPPORT_DDI_DIR") if env: p = Path(env).expanduser() if p.exists(): return p.resolve() # 3) 向上搜索 here = Path(__file__).resolve().parent for i in range(4): # 当前目录 + 向上 3 层 cand = here / "SupportFiles" if cand.exists(): return cand.resolve() here = here.parent return None @dataclass class DevDiskImageDeployer: """ 同步 SupportFiles//... 到 ~/.tidevice/device-support//... - 自动定位 SupportFiles(显式参数/环境变量/向上搜索) - 已存在则跳过,不存在才复制 - 详细日志 """ project_support_root: Optional[Path] = None # 可不传,自动发现 cache_root: Optional[Path] = None # 默认 ~/.tidevice/device-support verbose: bool = True dry_run: bool = False overwrite: bool = False _src_dir: Path = field(init=False, repr=False) _cache_dir: Path = field(init=False, repr=False) def __post_init__(self): src = _find_support_root(self.project_support_root) if src is None: raise FileNotFoundError( "未找到 SupportFiles 目录。" "可传入 project_support_root,或设置环境变量 SUPPORT_DDI_DIR," "或确保在当前文件上层 3 级目录内存在名为 'SupportFiles' 的目录。" ) self._src_dir = src if self.cache_root is None: self._cache_dir = Path.home() / ".tidevice" / "device-support" else: self._cache_dir = Path(self.cache_root).expanduser().resolve() self._cache_dir.mkdir(parents=True, exist_ok=True) if self.verbose: print(f"[INFO] resolved SupportFiles = {self._src_dir}") print(f"[INFO] cache_dir = {self._cache_dir}") # 打印一眼能看见的兄弟目录,防路径误判 parent = self._src_dir.parent try: siblings = ", ".join(sorted(p.name for p in parent.iterdir() if p.is_dir())) print(f"[INFO] SupportFiles parent = {parent}") print(f"[INFO] siblings = {siblings}") except Exception: pass def deploy_all(self): candidates = list(self._iter_version_dirs(self._src_dir)) copied = skipped = 0 for ver_dir in candidates: dst = self._cache_dir / ver_dir.name if dst.exists() and not self.overwrite: skipped += 1 continue if dst.exists() and self.overwrite: if not self.dry_run: shutil.rmtree(dst) if self.verbose: print(f"[COPY] {ver_dir} -> {dst}") if not self.dry_run: shutil.copytree(ver_dir, dst) copied += 1 if self.verbose: print(f"[SUMMARY] copied={copied}, skipped={skipped}, total={copied+skipped}") # -------- helpers -------- def _iter_version_dirs(self, root: Path) -> Iterable[Path]: for p in sorted(root.iterdir()): if p.is_dir() and VERSION_RE.match(p.name): yield p