#!/usr/bin/env python3 # -*- coding: utf-8 -*- """M11b-3 代码镜像同步器:modules/world_sync/ 是唯一真源, apps/scense/pkgs/world_sync/ 是打包镜像(宿主 build.sh 从应用仓库安装,禁止手工双写)。 与 sync_models_to_app.py(只管 models/)互补,本脚本同步 **Python 包与测试**: - world_sync/*.py(包源码,含 pbl_runtime_broadcast.py) - tests/*.py(含 test_m11b3_post_commit_broadcast.py) - skill/SKILL.md、README.md 用法: python modules/world_sync/scripts/sync_code_to_app.py # 同步 python modules/world_sync/scripts/sync_code_to_app.py --check # 只校验 sha256 行为:逐文件以同一份字节覆盖镜像落点,打印两两 sha256 比对;--check 不一致退出码 1。 """ from __future__ import annotations import argparse import hashlib import os import sys HERE = os.path.dirname(os.path.abspath(__file__)) WS_ROOT = os.path.abspath(os.path.join(HERE, "..", "..", "..")) SRC_ROOT = os.path.join("modules", "world_sync") DST_ROOT = os.path.join("apps", "scense", "pkgs", "world_sync") #: 需要镜像的相对路径(真源 = modules/world_sync/) MIRROR_SUBDIRS = ("world_sync", "tests") MIRROR_FILES = ("README.md", os.path.join("skill", "SKILL.md")) #: 不同步的产物/缓存 SKIP_SUFFIX = ("__pycache__", ".pyc", ".egg-info", ".pytest_cache") def sha256_of(path: str) -> str: h = hashlib.sha256() with open(path, "rb") as fh: for chunk in iter(lambda: fh.read(65536), b""): h.update(chunk) return h.hexdigest() def collect() -> list: """返回需要镜像的相对路径列表(相对 SRC_ROOT)。 注意:路径必须以 WS_ROOT 为基准拼绝对路径再判断,历史上这里用相对 SRC_ROOT 直接 isdir,导致从模块目录内执行时恒为空列表 —— --check 会打印「RESULT: PASS」却一个文件都没比对(假 PASS)。 """ rels = [] src_abs = os.path.join(WS_ROOT, SRC_ROOT) for sub in MIRROR_SUBDIRS: base = os.path.join(src_abs, sub) if not os.path.isdir(base): continue for name in sorted(os.listdir(base)): if any(s in name for s in SKIP_SUFFIX): continue if not name.endswith(".py"): continue rels.append(os.path.join(sub, name)) for rel in MIRROR_FILES: if os.path.isfile(os.path.join(src_abs, rel)): rels.append(rel) return rels def main() -> int: ap = argparse.ArgumentParser() ap.add_argument("--check", action="store_true", help="只校验,不写入") args = ap.parse_args() bad = 0 rels = collect() if not rels: # 空清单说明扫描本身出错(路径基准问题),必须判 FAIL 而非静默 PASS print("RESULT: FAIL (未扫描到任何待镜像文件,检查 WS_ROOT/SRC_ROOT)") return 1 print("# 待镜像文件 %d 个: %s -> %s" % (len(rels), SRC_ROOT, DST_ROOT)) for rel in rels: src = os.path.join(WS_ROOT, SRC_ROOT, rel) dst = os.path.join(WS_ROOT, DST_ROOT, rel) src_sha = sha256_of(src) same = os.path.isfile(dst) and sha256_of(dst) == src_sha if args.check: print("%-58s %s" % (rel, "OK" if same else "DRIFT")) if not same: bad += 1 continue os.makedirs(os.path.dirname(dst), exist_ok=True) with open(src, "rb") as fh: data = fh.read() with open(dst, "wb") as fh: fh.write(data) ok = sha256_of(dst) == src_sha print("%-58s %s sha256=%s" % (rel, "SYNCED" if ok else "FAIL", src_sha[:12])) if not ok: bad += 1 if bad: print("RESULT: FAIL (%d 项不一致)" % bad) return 1 print("RESULT: PASS (镜像与真源逐文件 sha256 一致)") return 0 if __name__ == "__main__": sys.exit(main())