muse-agent-example/工具/构建发布.py
zizi 0658d178ad 工程底座:单一生成入口、验证期写闸门与并行分片
- Makefile:新增 验收数据库(生成→检查→库层全量,前置校验隔离库连接串)、数据库分片、浏览器测试三个入口;
  格式写入 末尾就地重新生成;数据库测试 与 验收数据库 统一排除 浏览器/网络;快检 不再静默跳过类型门;
  前端旅程 预检补齐四个必需变量;pytest 目标改用仓内解释器(uv run 在嵌套检出会解析到外层环境)。
- 工具/验证锁.py:验证会话持共享锁,写入口用 --执行 在独占锁内落盘,生成/格式写入/索引生成走同一闸门。
- 工具/并行数据库测试.py:按文件分片并行,缺连接串在采集前拒绝。
- 工具/构建编排.py、环境预检.py、构建资源包.py、维护索引.py 与上述口径对齐。
- .gitignore / CI / README / AGENTS:忽略构建产物、CI 与 Makefile 单一口径、README 按实际实现陈述、AGENTS 补日常入口与验证纪律。
2026-09-18 01:14:50 +08:00

134 lines
5.2 KiB
Python

"""生成可审发布目录:固定轮子、带哈希依赖、工作台和部署清单;不发布或迁移。"""
import argparse
import hashlib
import json
import os
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
from zipfile import ZipFile
from 构建编排 import 执行构建
根 = Path(__file__).resolve().parents[1]
def 运行(args):
subprocess.run(args, cwd=根, env={**os.environ, "PYTHONPATH": str(根 / "src")}, check=True)
def 构建发布(output: Path, *, 生成=True):
output = output.resolve()
if output.exists():
raise ValueError("发布目录已存在;使用新目录保留已有版本")
# 生成参数为旧调用者保留;共享入口始终按内容和产物存在性判断复用。
报告 = 执行构建(轮子=True)
路径 = 报告.get("wheel")
if not isinstance(路径, str):
raise ValueError("增量构建未返回本批轮子")
wheel = Path(路径).resolve()
if not wheel.is_relative_to(根.resolve()) or wheel.suffix != ".whl" or not wheel.is_file():
raise ValueError("本批轮子不存在或不在仓库构建范围内")
运行([sys.executable, "工具/维护索引.py", "--检查"])
运行(
[
sys.executable,
"-c",
"from muse.资源加载 import 核对资源; "
"from muse.基础设施.系统运行 import 运行代码哈希; "
"assert 核对资源()['代码哈希'] == 运行代码哈希(), '资源与当前代码不匹配,先重新构建'",
]
)
output.parent.mkdir(parents=True, exist_ok=True)
with tempfile.TemporaryDirectory(prefix=".muse-release-", dir=output.parent) as temporary:
stage = Path(temporary)
(stage / "wheels").mkdir()
本批轮子 = stage / "wheels" / wheel.name
shutil.copyfile(wheel, 本批轮子)
运行(
[
"uv",
"export",
"--frozen",
"--no-dev",
"--no-emit-project",
"--no-header",
"--output-file",
str(stage / "requirements.txt"),
]
)
wheels = list((stage / "wheels").glob("*.whl"))
if len(wheels) != 1:
raise ValueError("发布目录必须对应一个明确轮子")
with ZipFile(wheels[0]) as archive:
names = archive.namelist()
if len(names) != len(set(names)) or any(
part in {".git", "tests", ".agents.local", ".env"}
for name in names
for part in Path(name).parts
):
raise ValueError("轮子含重复文件或发布范围之外的内容")
manifest = json.loads(archive.read("muse/资源/清单.json"))
for name in [
"Dockerfile",
"compose.yaml",
"compose.test.yaml",
"启动.sh",
"停止.sh",
"应用.example.toml",
"部署.example.env",
"Pi/package.json",
"Pi/package-lock.json",
]:
target = stage / name
target.parent.mkdir(parents=True, exist_ok=True)
shutil.copyfile(根 / "部署" / name, target)
config = stage / "应用.example.toml"
config.write_text(config.read_text().replace("<固定资源构建身份>", manifest["构建身份"]))
for name in ["启动.sh", "停止.sh"]:
(stage / name).chmod(0o755)
shutil.copyfile(根 / "数据库/初始化/用途角色.sql", stage / "用途角色.sql")
payloads = [wheels[0], stage / "requirements.txt"]
(stage / "SHA256SUMS").write_text(
"".join(
f"{hashlib.sha256(p.read_bytes()).hexdigest()} {p.relative_to(stage).as_posix()}\n"
for p in payloads
)
)
files = {
p.relative_to(stage).as_posix(): hashlib.sha256(p.read_bytes()).hexdigest()
for p in sorted(stage.rglob("*"))
if p.is_file()
}
release = {
"schema_version": 1,
"version": manifest["代码版本"],
"build_id": manifest["构建身份"],
"code_hash": manifest["代码哈希"],
"resource_release": manifest["发布身份"],
"files": files,
}
release["release_id"] = hashlib.sha256(
json.dumps(release, ensure_ascii=False, sort_keys=True).encode()
).hexdigest()
(stage / "发布清单.json").write_text(
json.dumps(release, ensure_ascii=False, indent=2) + "\n",
)
# 原子替换仅作用于本次新目录;此前已检查不可覆盖历史发布。
stage.rename(output)
return release
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--输出", required=True, type=Path)
parser.add_argument(
"--已生成", action="store_true", help="兼容参数;统一增量入口自动复用产物,仍校验资源和索引"
)
parser.add_argument("--缓存根", type=Path, help="显式发布缓存根;不接管普通目录或备份")
args = parser.parse_args()
print(json.dumps(构建发布(args.输出, 生成=not args.已生成), ensure_ascii=False))