"""Build the device-test plugin from a fixed Git commit without local artifacts.""" import argparse import hashlib import io import json from pathlib import Path, PurePosixPath import subprocess import tarfile import zipfile def git(root, *args): return subprocess.run(["git", *args], cwd=root, check=True, capture_output=True).stdout def write_immutable(path, data): if path.exists(): if path.read_bytes() != data: raise ValueError(f"Refusing to replace different existing bytes: {path.name}") return with path.open("xb") as handle: handle.write(data) def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--ref", default="HEAD", help="Commit containing the plugin source") parser.add_argument("--output", type=Path, default=Path("artifacts/device-test")) args = parser.parse_args() root = Path(__file__).resolve().parents[1] commit = git(root, "rev-parse", "--verify", "--end-of-options", args.ref + "^{commit}") commit = commit.decode("ascii").strip() source = git(root, "archive", "--format=tar", commit + ":plugins/artemis-device-test") files = {} with tarfile.open(fileobj=io.BytesIO(source)) as tree: for entry in tree: name = entry.name path = PurePosixPath(name) if entry.isdir(): continue if ( not entry.isfile() or path.is_absolute() or ".." in path.parts or str(path) != name or any( part in {"__pycache__", ".git", "node_modules", ".venv"} for part in path.parts ) or name.endswith((".pyc", ".pyo")) or path.name == ".env" ): raise ValueError(f"Non-distributable source entry: {name}") handle = tree.extractfile(entry) if handle is None: raise ValueError(f"Missing source bytes: {name}") files[name] = handle.read() manifest = json.loads(files["plugin.json"]) if manifest["id"] != "artemis-device-test": raise ValueError("Unexpected plugin ID") target = f"artemis-device-test-{manifest['version'].replace('+', '-')}.zip" buffer = io.BytesIO() with zipfile.ZipFile(buffer, "w", compression=zipfile.ZIP_DEFLATED) as archive: for name, data in sorted(files.items()): info = zipfile.ZipInfo(name, date_time=(1980, 1, 1, 0, 0, 0)) info.compress_type = zipfile.ZIP_DEFLATED info.external_attr = 0o100644 << 16 archive.writestr(info, data, compresslevel=9) data = buffer.getvalue() digest = hashlib.sha256(data).hexdigest() record = { "plugin_id": manifest["id"], "version": manifest["version"], "source_commit": commit, "filename": target, "sha256": digest, "size_bytes": len(data), "files": len(files), } args.output.mkdir(parents=True, exist_ok=True) write_immutable(args.output / target, data) write_immutable(args.output / (target + ".sha256"), f"{digest} {target}\n".encode("ascii")) write_immutable(args.output / "build.json", (json.dumps(record, indent=2) + "\n").encode()) print(json.dumps(record)) if __name__ == "__main__": main()