#!/usr/bin/env python3 """Build the dependency-free GovOPlaN deployer as one executable zipapp.""" from __future__ import annotations import argparse from hashlib import sha256 from pathlib import Path from zipfile import ZIP_DEFLATED, ZipFile, ZipInfo ROOT = Path(__file__).resolve().parent DEFAULT_OUTPUT = ROOT.parent.parent / "runtime" / "deployment" / "govoplan-deploy.pyz" ZIP_TIMESTAMP = (1980, 1, 1, 0, 0, 0) PYTHON_FILE_MODE = 0o100644 def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--output", type=Path, default=DEFAULT_OUTPUT) args = parser.parse_args(argv) output = args.output.expanduser().resolve() if output == ROOT or ROOT in output.parents: raise SystemExit("output must be outside the deployment source directory") output.parent.mkdir(mode=0o700, parents=True, exist_ok=True) temporary = output.with_name(f".{output.name}.tmp") if temporary.exists(): temporary.unlink() _write_reproducible_zipapp(temporary) temporary.chmod(0o755) temporary.replace(output) digest = sha256(output.read_bytes()).hexdigest() print(f"{digest} {output}") return 0 def _write_reproducible_zipapp(target: Path) -> None: sources = tuple( path for path in sorted(ROOT.rglob("*"), key=lambda item: item.as_posix()) if path.is_file() and _include_source(path.relative_to(ROOT)) ) if not any(path.relative_to(ROOT).as_posix() == "__main__.py" for path in sources): raise ValueError("deployment source has no __main__.py") for path in sources: if path.is_symlink(): raise ValueError(f"deployment source must not contain symlinks: {path}") with target.open("wb") as handle: handle.write(b"#!/usr/bin/env python3\n") with ZipFile( handle, mode="w", compression=ZIP_DEFLATED, compresslevel=9, strict_timestamps=True, ) as archive: for source in sources: relative = source.relative_to(ROOT).as_posix() info = ZipInfo(relative, date_time=ZIP_TIMESTAMP) info.compress_type = ZIP_DEFLATED info.create_system = 3 info.external_attr = PYTHON_FILE_MODE << 16 info.flag_bits |= 0x800 archive.writestr( info, source.read_bytes(), compress_type=ZIP_DEFLATED, compresslevel=9, ) def _include_source(path: Path) -> bool: return ( "__pycache__" not in path.parts and path.suffix not in {".pyc", ".pyo"} and path.name != "build-deployer-zipapp.py" ) if __name__ == "__main__": raise SystemExit(main())