Files
iOSAI/Utils/DevDiskImageDeployer.py

136 lines
4.9 KiB
Python
Raw Normal View History

2025-08-28 15:46:17 +08:00
# 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' 的目录
"""
if hint and hint.exists():
return hint.resolve()
env = os.environ.get("SUPPORT_DDI_DIR")
if env:
p = Path(env).expanduser()
if p.exists():
return p.resolve()
here = Path(__file__).resolve().parent
2025-08-28 22:10:32 +08:00
for _ in range(4): # 当前目录 + 向上 3 层
2025-08-28 15:46:17 +08:00
cand = here / "SupportFiles"
if cand.exists():
return cand.resolve()
here = here.parent
return None
@dataclass
class DevDiskImageDeployer:
"""
2025-08-28 22:10:32 +08:00
同步 SupportFiles/<version>/ SupportFiles/<version>.zip ~/.tidevice/device-support
- 目录复制为 ~/.tidevice/device-support/<version>/
- zip原样复制为 ~/.tidevice/device-support/<version>.zip 不解压
- 已存在则跳过如设置 overwrite=True 则覆盖
2025-08-28 15:46:17 +08:00
"""
2025-08-28 22:10:32 +08:00
project_support_root: Optional[Path] = None
cache_root: Optional[Path] = None
2025-08-28 15:46:17 +08:00
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):
2025-08-28 22:10:32 +08:00
entries = list(self._iter_version_entries(self._src_dir))
2025-08-28 15:46:17 +08:00
copied = skipped = 0
2025-08-28 22:10:32 +08:00
for p, kind, version in entries:
# kind: "dir" 或 "zip"
if kind == "dir":
dst = self._cache_dir / version
exists = dst.exists()
if exists and not self.overwrite:
skipped += 1
2025-09-15 16:01:27 +08:00
# if self.verbose:
# print(f"[SKIP] {dst} 已存在(目录)")
2025-08-28 22:10:32 +08:00
continue
if exists and self.overwrite and not self.dry_run:
2025-08-28 15:46:17 +08:00
shutil.rmtree(dst)
2025-08-28 22:10:32 +08:00
if self.verbose:
print(f"[COPY] DIR {p} -> {dst}")
if not self.dry_run:
shutil.copytree(p, dst)
copied += 1
elif kind == "zip":
dst = self._cache_dir / f"{version}.zip"
exists = dst.exists()
if exists and not self.overwrite:
skipped += 1
2025-09-15 16:01:27 +08:00
# if self.verbose:
# print(f"[SKIP] {dst} 已存在zip")
2025-08-28 22:10:32 +08:00
continue
if exists and self.overwrite and not self.dry_run:
dst.unlink()
if self.verbose:
print(f"[COPY] ZIP {p} -> {dst}")
if not self.dry_run:
# 用 copy2 保留 mtime 等元数据
shutil.copy2(p, dst)
copied += 1
2025-08-28 15:46:17 +08:00
if self.verbose:
print(f"[SUMMARY] copied={copied}, skipped={skipped}, total={copied+skipped}")
# -------- helpers --------
2025-08-28 22:10:32 +08:00
def _iter_version_entries(self, root: Path) -> Iterable[tuple[Path, str, str]]:
"""
迭代返回 (路径, 类型, 版本号)
- 目录名称需匹配版本号
- zipstem去除后缀的名称需匹配版本号
"""
2025-08-28 15:46:17 +08:00
for p in sorted(root.iterdir()):
if p.is_dir() and VERSION_RE.match(p.name):
2025-08-28 22:10:32 +08:00
yield (p, "dir", p.name)
elif p.is_file() and p.suffix.lower() == ".zip" and VERSION_RE.match(p.stem):
yield (p, "zip", p.stem)