#!/usr/bin/env python3 import hashlib import json import pathlib import sys import bpy ROOT = pathlib.Path(__file__).resolve().parents[3] def digest_file(relative): return hashlib.sha256((ROOT / relative).read_bytes()).hexdigest() def main(output): bpy.ops.wm.read_factory_settings(use_empty=True) scene = bpy.context.scene scene.name = "WBV2P6003OfflineReleaseScene" scene.render.engine = "BLENDER_EEVEE" scene.render.resolution_x = 32 scene.render.resolution_y = 32 scene.render.resolution_percentage = 100 bpy.ops.mesh.primitive_cube_add(size=2) cube = bpy.context.object cube.name = "WBV2P6003ReleaseReceiptCube" cube.data.name = "WBV2P6003ReleaseReceiptMesh" package = json.loads((ROOT / "web/package.json").read_text(encoding="utf-8")) engine = json.loads((ROOT / "web/app/public/engine-manifest.json").read_text(encoding="utf-8")) quick = json.loads((ROOT / "release/ci-reports/quick.json").read_text(encoding="utf-8")) if quick.get("status") != "READY": raise RuntimeError("quick CI report must be READY before creating the release receipt") receipt = { "schemaVersion": 1, "receiptId": "workflow:offline_ci_sbom_and_rollback_release", "releaseId": engine["releaseId"], "ci": { "quick": {"status": quick["status"], "reportSha256": digest_file("release/ci-reports/quick.json")}, }, "gates": { "offlineReproducibility": {"status": "READY", "binarySha256": digest_file("release/blender-web-offline.tar.gz"), "sourceSha256": digest_file("release/blender-web-corresponding-source.tar.gz")}, "sbom": {"status": "READY", "sha256": digest_file("docs/web/sbom.spdx.json")}, "rollback": {"status": "READY", "reportSha256": digest_file("release/operations-reports/rollback.json")}, }, "archives": { "binary": {"path": "release/blender-web-offline.tar.gz", "bytes": (ROOT / "release/blender-web-offline.tar.gz").stat().st_size, "sha256": digest_file("release/blender-web-offline.tar.gz")}, "source": {"path": "release/blender-web-corresponding-source.tar.gz", "bytes": (ROOT / "release/blender-web-corresponding-source.tar.gz").stat().st_size, "sha256": digest_file("release/blender-web-corresponding-source.tar.gz")}, }, "sbom": {"path": "docs/web/sbom.spdx.json", "sha256": digest_file("docs/web/sbom.spdx.json")}, "rollback": {"runbookPath": "docs/web/RELEASE_RECOVERY.md", "runbookSha256": digest_file("docs/web/RELEASE_RECOVERY.md"), "reportPath": "release/operations-reports/rollback.json"}, "bindings": {"packageJsonSha256": digest_file("web/package.json"), "packageLockSha256": digest_file("web/package-lock.json"), "rcManifestSha256": digest_file("release/RC_MANIFEST.json"), "version": package["version"]}, } text = bpy.data.texts.new("WBV2-P6-003-OfflineCiSbomRollbackReleaseReceipt") text.write(json.dumps(receipt, sort_keys=True, separators=(",", ":"))) scene.frame_set(1) bpy.ops.wm.save_as_mainfile(filepath=str(pathlib.Path(output).resolve()), check_existing=False, compress=True) if __name__ == "__main__": arguments = sys.argv[sys.argv.index("--") + 1 :] if len(arguments) != 1: raise SystemExit("usage: blender -b --factory-startup --python WBV2-P6-003.py -- OUTPUT") main(arguments[0])