TraceStudio/scripts/prepare_dist.py

138 lines
4.4 KiB
Python
Raw Normal View History

2026-01-12 21:51:45 +08:00
"""准备分发目录或归档大文件。
用法:
python scripts/prepare_dist.py --mode copy # 将必需文件复制到 dist/
python scripts/prepare_dist.py --mode archive # 将大文件归档到 archive/
脚本不会删除源文件会生成操作日志和回滚脚本
"""
import argparse
import os
import shutil
import json
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
DIST = ROOT / "dist"
ARCHIVE = ROOT / "archive"
LOG = ROOT / "scripts" / "prepare_dist.log"
DEFAULT_INCLUDES = [
"server",
"web",
"cloud/custom_nodes",
"README.md",
"server/system_config.yaml",
]
LARGE_EXT = {"*.csv", "*.utrace", "*.zip"}
# Patterns to ignore when copying to dist
IGNORE_PATTERNS = [
"__pycache__",
"node_modules",
"*.pyc",
"*.pyo",
2026-01-13 00:29:18 +08:00
".vscode",
".devcontainer",
"*.sln",
2026-01-12 21:51:45 +08:00
]
def write_log(entry: dict):
with open(LOG, "a", encoding="utf-8") as f:
f.write(json.dumps(entry, ensure_ascii=False) + "\n")
def gather_large_files(root: Path):
results = []
for p in root.rglob("*"):
if p.is_file():
if any(p.match(pattern) for pattern in LARGE_EXT):
results.append(p)
return results
def copy_includes(includes, dest: Path):
dest.mkdir(parents=True, exist_ok=True)
actions = []
for inc in includes:
src = ROOT / inc
if not src.exists():
continue
target = dest / src.name
if src.is_dir():
for root_dir, dirs, files in os.walk(src):
# skip ignored directories
rel = os.path.relpath(root_dir, src)
parts = rel.split(os.sep) if rel != '.' else []
if any(pat in parts for pat in IGNORE_PATTERNS if not pat.startswith('*')):
continue
# copy files except ignored patterns
for f in files:
skip = False
for pat in IGNORE_PATTERNS:
if pat.startswith('*') and Path(f).match(pat):
skip = True
break
if skip:
continue
src_file = Path(root_dir) / f
relpath = src_file.relative_to(src)
dest_file = target / relpath
dest_file.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(src_file, dest_file)
actions.append((str(src), str(target), "copytree_filtered"))
else:
# single file copy
shutil.copy2(src, target)
actions.append((str(src), str(target), "copyfile"))
write_log({"action": "copy_includes", "items": actions})
return actions
def archive_files(files, dest: Path):
dest.mkdir(parents=True, exist_ok=True)
actions = []
for f in files:
# skip ignored
if any(part == '__pycache__' for part in f.parts):
continue
rel = f.relative_to(ROOT)
target = dest / rel
target.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(f, target)
actions.append((str(f), str(target)))
write_log({"action": "archive_files", "items": actions})
return actions
def build_rollback(actions, rollback_path: Path):
rollback_path.parent.mkdir(parents=True, exist_ok=True)
with open(rollback_path, "w", encoding="utf-8") as f:
f.write("# 回滚脚本(手动检查并运行)\n")
for src, dst, kind in actions:
f.write(f"# 删除目标: {dst}\n")
write_log({"action": "write_rollback", "path": str(rollback_path)})
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--mode", choices=["copy", "archive"], default="copy")
parser.add_argument("--includes", nargs="*", default=DEFAULT_INCLUDES)
args = parser.parse_args()
if args.mode == "copy":
actions = copy_includes(args.includes, DIST)
build_rollback(actions, ROOT / "scripts" / "rollback_copy.sh")
print("已将包含项复制到 dist/,查看 scripts/prepare_dist.log 获取详情。")
else:
files = gather_large_files(ROOT / "cloud")
actions = archive_files(files, ARCHIVE)
build_rollback(actions, ROOT / "scripts" / "rollback_archive.sh")
print("已将大文件复制到 archive/,查看 scripts/prepare_dist.log 获取详情。")
if __name__ == "__main__":
main()