Replace PM3 compile-from-source in pi-gen with pre-built tarball extraction (saves 43-58 min). Merge stagePM3 into stageDangerousPi as 02-pm3-install substage, renumber all subsequent substages. Switch CI PM3 build to native ARM64 runner (ubuntu-24.04-arm64) eliminating QEMU overhead. Add weekly base-image workflow for pre-baking stages 0-2. Support PM3_TARBALL, BASE_IMAGE, and APT_PROXY env vars in build-image.sh. Also includes prior Phase 5 work: theme system, design system integration, component update system, OS updates, CI build pipeline, and test results. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
125 lines
3.8 KiB
Python
Executable File
125 lines
3.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Generate release-manifest.json from built component tarballs.
|
|
|
|
Scans the dist/ directory for component tarballs, reads their
|
|
COMPONENT_VERSION metadata, computes SHA256 checksums, and writes
|
|
a release-manifest.json suitable for upload as a GitHub Release asset.
|
|
|
|
Usage:
|
|
python3 ci/generate-manifest.py [dist_dir] [--version VERSION]
|
|
"""
|
|
import hashlib
|
|
import json
|
|
import os
|
|
import sys
|
|
import tarfile
|
|
from pathlib import Path
|
|
|
|
|
|
def sha256_file(path: Path) -> str:
|
|
"""Compute SHA256 checksum of a file."""
|
|
h = hashlib.sha256()
|
|
with open(path, "rb") as f:
|
|
while chunk := f.read(65536):
|
|
h.update(chunk)
|
|
return h.hexdigest()
|
|
|
|
|
|
def read_component_version(tarball_path: Path) -> dict:
|
|
"""Read COMPONENT_VERSION JSON from inside a tarball."""
|
|
with tarfile.open(tarball_path, "r:gz") as tf:
|
|
for member in tf.getmembers():
|
|
if member.name.endswith("COMPONENT_VERSION"):
|
|
f = tf.extractfile(member)
|
|
if f:
|
|
return json.loads(f.read().decode())
|
|
return {}
|
|
|
|
|
|
def main():
|
|
dist_dir = Path(sys.argv[1]) if len(sys.argv) > 1 else Path("dist")
|
|
version = None
|
|
if "--version" in sys.argv:
|
|
idx = sys.argv.index("--version")
|
|
version = sys.argv[idx + 1]
|
|
|
|
if not dist_dir.exists():
|
|
print(f"Error: {dist_dir} does not exist")
|
|
sys.exit(1)
|
|
|
|
# Read VERSION file if version not specified
|
|
if not version:
|
|
version_file = Path("VERSION")
|
|
version = version_file.read_text().strip() if version_file.exists() else "0.1.0"
|
|
|
|
manifest = {
|
|
"schema_version": 1,
|
|
"release_version": version,
|
|
"components": {},
|
|
}
|
|
|
|
tarballs = sorted(dist_dir.glob("*.tar.gz"))
|
|
if not tarballs:
|
|
print(f"Warning: no tarballs found in {dist_dir}")
|
|
|
|
for tarball in tarballs:
|
|
filename = tarball.name
|
|
checksum = sha256_file(tarball)
|
|
size = tarball.stat().st_size
|
|
comp_version = read_component_version(tarball)
|
|
comp_id = comp_version.get("component_id", "")
|
|
|
|
if not comp_id:
|
|
# Try to infer from filename
|
|
for known in ("pm3", "frontend", "backend", "theme"):
|
|
if filename.startswith(known):
|
|
comp_id = known
|
|
break
|
|
|
|
if not comp_id:
|
|
print(f" Skipping {filename} (unknown component)")
|
|
continue
|
|
|
|
comp_ver = comp_version.get("version", version)
|
|
|
|
# Build asset key (platform-specific for pm3)
|
|
platform_str = comp_version.get("platform")
|
|
python_abi = comp_version.get("python_abi")
|
|
if platform_str and python_abi:
|
|
asset_key = f"{platform_str}-{python_abi}"
|
|
else:
|
|
asset_key = "default"
|
|
|
|
asset_info = {
|
|
"filename": filename,
|
|
"checksum_sha256": checksum,
|
|
"size": size,
|
|
}
|
|
if comp_version.get("python_version"):
|
|
asset_info["python_version"] = comp_version["python_version"]
|
|
if platform_str:
|
|
asset_info["platform"] = platform_str
|
|
|
|
# Initialize component entry if needed
|
|
if comp_id not in manifest["components"]:
|
|
manifest["components"][comp_id] = {
|
|
"version": comp_ver,
|
|
"assets": {},
|
|
"changelog": "",
|
|
}
|
|
|
|
manifest["components"][comp_id]["assets"][asset_key] = asset_info
|
|
print(f" {comp_id}/{asset_key}: {filename} ({size} bytes)")
|
|
|
|
# Write manifest
|
|
manifest_path = dist_dir / "release-manifest.json"
|
|
with open(manifest_path, "w") as f:
|
|
json.dump(manifest, f, indent=2)
|
|
|
|
print(f"\n✅ Wrote {manifest_path}")
|
|
print(f" Components: {list(manifest['components'].keys())}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|