world_sync/scripts/sync_code_to_app.py
2026-09-21 13:33:56 +08:00

111 lines
3.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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())