64 lines
2.0 KiB
Python
64 lines
2.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Build deterministic dormant-release candidate artifacts."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
import tarfile
|
|
|
|
|
|
def sha256(value: bytes) -> str:
|
|
return hashlib.sha256(value).hexdigest()
|
|
|
|
|
|
def build(source: Path, output: Path, source_oid: str, tree_oid: str) -> dict[str, str]:
|
|
if not source.exists() or not source.is_dir():
|
|
raise SystemExit("source directory missing")
|
|
files = []
|
|
for item in sorted(source.rglob("*")):
|
|
if item.is_file() and ".git" not in item.parts:
|
|
files.append(item)
|
|
artifact = output / "candidate.tar"
|
|
manifest = output / "manifest.json"
|
|
output.mkdir(parents=True, exist_ok=True)
|
|
with tarfile.open(artifact, "w", format=tarfile.PAX_FORMAT) as archive:
|
|
for item in files:
|
|
info = archive.gettarinfo(str(item), arcname=str(item.relative_to(source)))
|
|
info.mtime = 0
|
|
info.uid = 0
|
|
info.gid = 0
|
|
with item.open("rb") as handle:
|
|
archive.addfile(info, handle)
|
|
payload = {
|
|
"source_oid": source_oid,
|
|
"tree_oid": tree_oid,
|
|
"artifact_sha256": sha256(artifact.read_bytes()),
|
|
"files": {
|
|
str(item.relative_to(source)): sha256(item.read_bytes())
|
|
for item in files
|
|
},
|
|
}
|
|
manifest.write_text(json.dumps(payload, sort_keys=True, indent=2), encoding="utf-8")
|
|
return {
|
|
"artifact_sha256": payload["artifact_sha256"],
|
|
"manifest_sha256": sha256(manifest.read_bytes()),
|
|
}
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("source", type=Path)
|
|
parser.add_argument("output", type=Path)
|
|
parser.add_argument("source_oid")
|
|
parser.add_argument("tree_oid")
|
|
args = parser.parse_args()
|
|
print(json.dumps(build(args.source, args.output, args.source_oid, args.tree_oid)))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|