96 lines
5.2 KiB
Python
96 lines
5.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Freeze a rendered native atlas with raw measurements, source revision and build hash."""
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
import subprocess
|
|
import zipfile
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
def sha(data):
|
|
return hashlib.sha256(data).hexdigest()
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--raw", type=Path, required=True)
|
|
parser.add_argument("--rendered", type=Path, required=True)
|
|
parser.add_argument("--jar", type=Path, required=True)
|
|
parser.add_argument("--output", type=Path, required=True)
|
|
parser.add_argument("--extra", type=Path, action="append", default=[])
|
|
args = parser.parse_args()
|
|
if args.output.exists():
|
|
raise SystemExit("Refusing to overwrite an existing immutable atlas")
|
|
status = subprocess.check_output(["git", "status", "--porcelain"], cwd=ROOT, text=True)
|
|
if status.strip():
|
|
raise SystemExit("Commit atlas code and documentation before freezing the archive")
|
|
commit = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=ROOT, text=True).strip()
|
|
jar_bytes = args.jar.read_bytes()
|
|
with zipfile.ZipFile(args.jar) as jar:
|
|
if jar.testzip() is not None:
|
|
raise SystemExit("Invalid built JAR")
|
|
version = json.loads(jar.read("fabric.mod.json"))["version"]
|
|
files = {}
|
|
cases = []
|
|
for source in sorted(args.raw.glob("terrain-*.json")):
|
|
raw = source.read_bytes()
|
|
data = json.loads(raw)
|
|
if data["version"] != version:
|
|
raise SystemExit(f"Stale native export: {source}")
|
|
rendered = args.rendered / source.stem
|
|
provenance = json.loads((rendered / "provenance.json").read_text())
|
|
if provenance["source_sha256"] != sha(raw):
|
|
raise SystemExit(f"Rendered atlas differs from source: {source}")
|
|
if provenance["script_sha256"] != sha((ROOT / "scripts/terrain_atlas.py").read_bytes()):
|
|
raise SystemExit("Rendering script changed after rendering")
|
|
files["raw/" + source.name] = raw
|
|
for name in ("maps.png", "maps.pdf", "sections.png", "sections.pdf", "summary.json", "provenance.json"):
|
|
files[f"figures/{source.stem}/{name}"] = (rendered / name).read_bytes()
|
|
for name in ("upper-terrain.png", "upper-terrain.pdf"):
|
|
if (rendered / name).is_file(): files[f"figures/{source.stem}/{name}"] = (rendered / name).read_bytes()
|
|
cases.append({"seed": data["seed"], "diameter": data["diameter"], "directory": source.stem})
|
|
identities = {(c["seed"], c["diameter"]) for c in cases}
|
|
if not cases or len(identities) != len(cases):
|
|
raise SystemExit("Empty atlas or duplicate seed/size")
|
|
for name in ("scripts/terrain_atlas.py", "scripts/terrain-atlas-requirements.txt",
|
|
"scripts/archive_terrain_atlas.py", "docs/terrain-atlas.md",
|
|
"scripts/structure_atlas.py", "scripts/finishing_atlas.py", f"docs/generation-alpha{version.split('alpha.')[-1]}.md", f"docs/testing-alpha{version.split('alpha.')[-1]}.md"):
|
|
files[name] = (ROOT / name).read_bytes()
|
|
for path in args.extra:
|
|
key = "structures/" + path.name
|
|
if key in files: raise SystemExit("Duplicate extra atlas file")
|
|
files[key] = path.read_bytes()
|
|
rows = ["# Sanctuary — atlas " + version, "", f"Source : `{commit}`.", "",
|
|
"Mesures natives avant décoration ; voir docs/terrain-atlas.md pour les échelles et limites.", "",
|
|
"| Diamètre | Graine | Cartes | Coupes |", "|---|---|---|---|"]
|
|
for c in cases:
|
|
base = "figures/" + c["directory"]
|
|
rows.append(f"| {c['diameter']} | {c['seed']} | [PNG]({base}/maps.png) / [PDF]({base}/maps.pdf) | [PNG]({base}/sections.png) / [PDF]({base}/sections.pdf) |")
|
|
rows += ["", "Reproduction des figures dans un environnement Python isolé :", "", "```sh",
|
|
"python -m pip install -r scripts/terrain-atlas-requirements.txt",
|
|
"python scripts/terrain_atlas.py --output rendered raw/terrain-*.json", "```", ""]
|
|
files["README.md"] = "\n".join(rows).encode()
|
|
files["manifest.json"] = (json.dumps({"version": version, "source_commit": commit,
|
|
"jar": args.jar.name, "jar_sha256": sha(jar_bytes), "cases": cases,
|
|
"files_sha256": {name: sha(content) for name, content in sorted(files.items())}}, indent=2) + "\n").encode()
|
|
args.output.parent.mkdir(parents=True, exist_ok=True)
|
|
with zipfile.ZipFile(args.output, "x", compression=zipfile.ZIP_DEFLATED, compresslevel=9) as archive:
|
|
for name, content in sorted(files.items()):
|
|
info = zipfile.ZipInfo(name, date_time=(2000, 1, 1, 0, 0, 0))
|
|
info.compress_type = zipfile.ZIP_DEFLATED
|
|
info.external_attr = 0o100644 << 16
|
|
archive.writestr(info, content)
|
|
with zipfile.ZipFile(args.output) as archive:
|
|
assert archive.testzip() is None
|
|
for name, content in files.items():
|
|
assert archive.read(name) == content
|
|
print(json.dumps({"path": str(args.output), "cases": len(cases), "source_commit": commit,
|
|
"sha256": sha(args.output.read_bytes()), "bytes": args.output.stat().st_size}))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|