111 lines
3.9 KiB
Python
111 lines
3.9 KiB
Python
#!/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())
|