Files
codex-app-server/chv-guest/buildinfo.py
T
host 2ad29eeb1f feat(chv-guest): M4 Chromium 单独钉死 + /app 收紧为最小集
- Chromium 钉死:CHROMIUM_HEADLESS_SHELL_REV(纯数字修订号)
  设即钉死,buildinfo.py 校验读回的 ms-playwright 缓存目录名
  须恰好是该修订号,否则构建失败;generate/check 的 --verify
  也复核;未设时只记录、构建输出明说未钉死,BUILDINFO 记 null
- /app 最小集:build.sh 不再把整个 $REPO_DIR/cloud/ 拷进客机,
  只拷 host.py(只用标准库,不依赖 cloud/);REPO_DIR 预检
  不再要求 cloud/ 存在
- generate_lockfile.py 只读 BUILDINFO.versions 一事已在 to10 轨
  改写,本棒以合成 BUILDINFO 干跑复核(假版本 9.9.9 进 lockfile,
  宿主 git 1:2.43.0 未混入)并补钉死复核;真实 lockfile 产出
  仍待 V-9 真机构建,不编造版本号
2026-11-06 18:41:56 +00:00

173 lines
6.8 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
"""生成 CHV 客机镜像的 BUILDINFO.json(build.sh 调用)。
从 build.sh 抽出(B-02),使其可独立单元测试。输入全部是
已落盘的构建产物与读回文件,不编造版本号:versions 来自
客机读回的 dpkg-query / pip freeze(Playwright=1 时另有
ms-playwright 缓存目录名作 chromium 标识);artifacts 的
sha256/size 是对产物文件实测的;image_sha256 即 disk.raw
的 sha256,供 generate_lockfile.py 沿用。
用法:
python3 buildinfo.py OUT_DIR TAG UBUNTU CODEX_VERSION \\
REPO_COMMIT UBUNTU_IMAGE_SHA256 PLAYWRIGHT(0|1)
[CHROMIUM_HEADLESS_SHELL_REV]
CHROMIUM_HEADLESS_SHELL_REV(可选,纯数字):钉死的
ms-playwright 缓存修订号(如 1248)。PLAYWRIGHT=1 且
给了该值时,versions.chromium 里须恰好出现
chromium_headless_shell-<rev>,否则退出 1、构建失败;
未给时只记录、不强制,BUILDINFO.chromium_pinned_rev
记 null,构建输出由 build.sh 明说“未钉死”。
"""
import hashlib
import json
import os
import re
import sys
import time
# 轻量口径(2026-11-06 用户定):Ubuntu Minimal 底座,只装
# codex + Python + SSH + cloud-init + 薄 agent + git + gt +
# 最小中文字体;已裁剪 pandoc/texlive/python-docx 等文档工具
# 与 qemu-guest-agent。chv-sync 是仓库内脚本,版本随 repo_commit。
BASE_CONTENTS = [
"git", "openssh-server", "openssh-client", "cloud-init",
"python3", "python3-pip", "curl", "ca-certificates",
"xz-utils", "fonts-wqy-microhei", "fontconfig", "codex",
"codex-code-mode-host", "codex-resources/bwrap",
"/app/host.py", "gt", "chv-sync",
]
# 执行器默认内核 cmdline(config.py),构建产物须与它一起记录(B-12),
# 否则换宿主/换执行器配置时无法复现。
KERNEL_CMDLINE = "console=hvc0 root=/dev/vda1 rw"
# 浏览器装在系统路径(B-22),不是某用户的 ~/.cache:构建时
# PLAYWRIGHT_BROWSERS_PATH 指这里,agent 运行时也须设同一变量。
PLAYWRIGHT_BROWSERS_PATH = "/usr/local/share/ms-playwright"
def _read_lines(path):
try:
with open(path) as fh:
return [line.strip() for line in fh if line.strip()]
except OSError:
return []
def _sha256_size(path):
digest = hashlib.sha256()
size = 0
with open(path, "rb") as fh:
for chunk in iter(lambda: fh.read(1 << 20), b""):
digest.update(chunk)
size += len(chunk)
return digest.hexdigest(), size
def build(outdir, tag, ubuntu, codex_version, repo_commit,
ubuntu_image_sha256, playwright, chromium_pinned_rev=None):
disk_path = os.path.join(outdir, "disk.raw")
vmlinux_path = os.path.join(outdir, "vmlinux")
disk_sha, disk_size = _sha256_size(disk_path)
vmlinux_sha, vmlinux_size = _sha256_size(vmlinux_path)
contents = list(BASE_CONTENTS)
versions = {
"dpkg": _read_lines(
os.path.join(outdir, "guest-versions-dpkg.txt")),
"pip": _read_lines(
os.path.join(outdir, "guest-versions-pip.txt")),
"gt": _read_lines(
os.path.join(outdir, "guest-versions-gt.txt")),
}
gt_version = ""
for line in versions["gt"]:
m = re.search(r"(\d+\.\d+\.\d+)", line)
if m:
gt_version = m.group(1)
break
if playwright:
contents += ["playwright", "chromium-headless-shell"]
versions["chromium"] = _read_lines(
os.path.join(outdir, "guest-versions-chromium.txt"))
info = {
"tag": tag,
"ubuntu": ubuntu,
"codex_version": codex_version,
"repo_commit": repo_commit,
"gt_version": gt_version,
"built_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
"contents": contents,
"versions": versions,
"ubuntu_image_sha256": ubuntu_image_sha256,
"ubuntu_image_sha256_set": True,
"kernel_cmdline": KERNEL_CMDLINE,
"playwright": bool(playwright),
"playwright_browsers_path": PLAYWRIGHT_BROWSERS_PATH
if playwright else "",
# feat-chv-closeup(M4):钉死的 Chromium 修订号(未设
# 记 null,绝不编一个默认值出来假装已钉死)。
"chromium_pinned_rev": chromium_pinned_rev or None,
"image_sha256": disk_sha,
"artifacts": {
"disk.raw": {"sha256": disk_sha, "size": disk_size},
"vmlinux": {"sha256": vmlinux_sha, "size": vmlinux_size},
},
}
path = os.path.join(outdir, "BUILDINFO.json")
with open(path, "w") as fh:
json.dump(info, fh, indent=2)
fh.write("\n")
return info
def main(argv):
if len(argv) not in (8, 9):
print("用法:buildinfo.py OUT TAG UBUNTU CODEX_VERSION "
"REPO_COMMIT UBUNTU_IMAGE_SHA256 PLAYWRIGHT "
"[CHROMIUM_HEADLESS_SHELL_REV]", file=sys.stderr)
return 2
_, outdir, tag, ubuntu, codex_version, repo_commit, \
ubuntu_image_sha256, playwright = argv[:8]
chromium_pinned_rev = argv[8] if len(argv) == 9 else None
if chromium_pinned_rev and not re.fullmatch(r"[0-9]+",
chromium_pinned_rev):
print("CHROMIUM_HEADLESS_SHELL_REV 须为纯数字:%s"
% chromium_pinned_rev, file=sys.stderr)
return 2
info = build(outdir, tag, ubuntu, codex_version, repo_commit,
ubuntu_image_sha256, playwright == "1",
chromium_pinned_rev)
if not info["versions"]["dpkg"]:
print("BUILDINFO versions.dpkg 为空", file=sys.stderr)
return 1
if not info["versions"]["pip"]:
print("BUILDINFO versions.pip 为空", file=sys.stderr)
return 1
if not info["gt_version"]:
print("BUILDINFO gt_version 为空(gt 未装好)", file=sys.stderr)
return 1
if playwright == "1" and not info["versions"].get("chromium"):
print("BUILDINFO versions.chromium 为空(浏览器未装好或"
"读回路径不对)", file=sys.stderr)
return 1
if playwright == "1" and chromium_pinned_rev:
# feat-chv-closeup(M4):钉死校验——读回的缓存目录名
# 须恰好是该修订号,playwright 升级导致目录名变了就
# 在这里失败,不静默换浏览器版本。
expected = "chromium_headless_shell-%s" % chromium_pinned_rev
if expected not in (info["versions"].get("chromium") or []):
print("Chromium 修订号与钉死值不符:读回 %s,钉死 %s"
% (info["versions"].get("chromium"), expected),
file=sys.stderr)
return 1
if info["tag"] != tag:
print("BUILDINFO tag 不一致", file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
raise SystemExit(main(sys.argv))