#!/usr/bin/env python3 """Build the `retrobios` package PyPI serves: install.py as a command. The wheel and the sdist hold install.py, byte for byte the copy the bootstraps pin, a two-line launcher that runs it as a script, the metadata, README.md and LICENSE. Nothing of the collection. The metadata comes from packaging/pypi/pyproject.toml. The version is the date of the release in release.json: v2026.10.10 gives 2026.10.10. A release writes release.json first, so the package published with it carries its date. Both archives are written here with the standard library rather than by a build backend: the bytes then depend on the inputs alone, on any machine and with no network. The sdist still names setuptools as its backend, for pip and uv to build it. Usage: python scripts/build_pypi.py python scripts/build_pypi.py --output-dir tmp/pypi-dist """ from __future__ import annotations import argparse import base64 import calendar import gzip import hashlib import io import json import re import tarfile import zipfile from pathlib import Path from sitebootstrap import PIN, SH_NAME ROOT = Path(__file__).resolve().parent.parent PACKAGE_DIR = ROOT / "packaging" / "pypi" DEFAULT_OUTPUT = ROOT / "tmp" / "pypi-dist" MODULE = "retrobios" # The launcher. runpy runs install.py as __main__, so its own __main__ block # keeps the exit codes it gives a reader that left (141) and Ctrl+C (130). LAUNCHER = '''"""The RetroBIOS installer, install.py, run as the retrobios command.""" import runpy from pathlib import Path __version__ = "{version}" def main() -> None: runpy.run_path(str(Path(__file__).with_name("install.py")), run_name="__main__") ''' # The [project] keys the metadata below writes. Another key in pyproject.toml # would reach a build from the sdist and not the wheel written here. PROJECT_KEYS = frozenset({ "name", "dynamic", "description", "readme", "requires-python", "license", "license-files", "dependencies", "classifiers", "urls", "scripts", }) def package_version(release: dict) -> str: """The release date as a version, without the zeros PEP 440 drops.""" tag = str(release.get("tag", "")) match = re.fullmatch(r"v(\d{4})\.(\d{2})\.(\d{2})", tag) if match is None: raise ValueError(f"release.json names no dated release tag: {tag!r}") return ".".join(str(int(part)) for part in match.groups()) def load_project() -> dict: import tomllib # noqa: PLC0415 # 3.11, only where a package is built with (PACKAGE_DIR / "pyproject.toml").open("rb") as handle: project = tomllib.load(handle)["project"] unknown = set(project) - PROJECT_KEYS if unknown: raise ValueError(f"build_pypi.py writes no metadata for {sorted(unknown)}") if project["dependencies"] or project["dynamic"] != ["version"]: raise ValueError("the package has no dependencies and only its version is dynamic") return project def metadata(project: dict, version: str, readme: str) -> str: """Core metadata 2.4, as the wheel's METADATA and the sdist's PKG-INFO.""" lines = [ "Metadata-Version: 2.4", f"Name: {project['name']}", f"Version: {version}", f"Summary: {project['description']}", *(f"Project-URL: {label}, {url}" for label, url in project["urls"].items()), f"License-Expression: {project['license']}", *(f"License-File: {name}" for name in project["license-files"]), *(f"Classifier: {name}" for name in project["classifiers"]), f"Requires-Python: {project['requires-python']}", "Description-Content-Type: text/markdown", ] return "\n".join(lines) + "\n\n" + readme def installer_bytes() -> bytes: """install.py, refused unless it is the one install.sh pins: the package then runs the installer the one-line command runs, under the same id.""" data = (ROOT / "install.py").read_bytes() pins = PIN[SH_NAME].findall((ROOT / "install.sh").read_text(encoding="utf-8")) if pins != [hashlib.sha256(data).hexdigest()]: raise ValueError("install.py is not the installer install.sh pins: recompute the pin") return data def package_files(version: str) -> tuple[dict, dict[str, bytes]]: """The project table, and the files both archives ship, by path.""" project = load_project() readme = (PACKAGE_DIR / project["readme"]).read_text(encoding="utf-8") files = { f"{MODULE}/__init__.py": LAUNCHER.format(version=version).encode(), f"{MODULE}/install.py": installer_bytes(), "METADATA": metadata(project, version, readme).encode("utf-8"), "README.md": readme.encode("utf-8"), "pyproject.toml": (PACKAGE_DIR / "pyproject.toml").read_bytes(), } for name in project["license-files"]: files[name] = (ROOT / name).read_bytes() return project, files def _record_line(path: str, data: bytes) -> str: digest = base64.urlsafe_b64encode(hashlib.sha256(data).digest()).rstrip(b"=").decode() return f"{path},sha256={digest},{len(data)}" def write_wheel(path: Path, project: dict, files: dict[str, bytes], version: str, stamp: tuple[int, int, int]) -> None: info = f"{project['name']}-{version}.dist-info" scripts = "".join(f"{name} = {target}\n" for name, target in project["scripts"].items()) members = { f"{MODULE}/__init__.py": files[f"{MODULE}/__init__.py"], f"{MODULE}/install.py": files[f"{MODULE}/install.py"], f"{info}/METADATA": files["METADATA"], f"{info}/WHEEL": (b"Wheel-Version: 1.0\nGenerator: retrobios build_pypi.py\n" b"Root-Is-Purelib: true\nTag: py3-none-any\n"), f"{info}/entry_points.txt": f"[console_scripts]\n{scripts}".encode(), **{f"{info}/licenses/{name}": files[name] for name in project["license-files"]}, } record = "".join(_record_line(name, data) + "\n" for name, data in members.items()) members[f"{info}/RECORD"] = (record + f"{info}/RECORD,,\n").encode() with zipfile.ZipFile(path, "w") as archive: for name, data in members.items(): entry = zipfile.ZipInfo(name, date_time=(*stamp, 0, 0, 0)) entry.compress_type = zipfile.ZIP_DEFLATED entry.external_attr = 0o644 << 16 archive.writestr(entry, data) def write_sdist(path: Path, project: dict, files: dict[str, bytes], version: str, stamp: tuple[int, int, int]) -> None: top = f"{project['name']}-{version}" members = { "PKG-INFO": files["METADATA"], "pyproject.toml": files["pyproject.toml"], "README.md": files["README.md"], **{name: files[name] for name in project["license-files"]}, f"{MODULE}/__init__.py": files[f"{MODULE}/__init__.py"], f"{MODULE}/install.py": files[f"{MODULE}/install.py"], } mtime = calendar.timegm((*stamp, 0, 0, 0)) raw = io.BytesIO() with tarfile.open(fileobj=raw, mode="w", format=tarfile.PAX_FORMAT) as archive: for name, data in members.items(): entry = tarfile.TarInfo(f"{top}/{name}") entry.size, entry.mtime, entry.mode = len(data), mtime, 0o644 archive.addfile(entry, io.BytesIO(data)) with path.open("wb") as handle, gzip.GzipFile(fileobj=handle, mode="wb", filename="", mtime=mtime) as packed: packed.write(raw.getvalue()) def build(output_dir: Path) -> tuple[Path, Path]: """The wheel and the sdist in output_dir, replacing earlier builds there.""" version = package_version(json.loads((ROOT / "release.json").read_text(encoding="utf-8"))) year, month, day = (int(part) for part in version.split(".")) stamp = (year, month, day) project, files = package_files(version) output_dir.mkdir(parents=True, exist_ok=True) for pattern in ("*.whl", "*.tar.gz"): for old in output_dir.glob(f"{project['name']}-{pattern}"): old.unlink() wheel = output_dir / f"{project['name']}-{version}-py3-none-any.whl" sdist = output_dir / f"{project['name']}-{version}.tar.gz" write_wheel(wheel, project, files, version, stamp) write_sdist(sdist, project, files, version, stamp) return wheel, sdist def main() -> int: parser = argparse.ArgumentParser(allow_abbrev=False, description=__doc__.splitlines()[0]) parser.add_argument("--output-dir", type=Path, default=DEFAULT_OUTPUT, help="where the wheel and the sdist go (default: tmp/pypi-dist)") args = parser.parse_args() for path in build(args.output_dir): print(path) return 0 if __name__ == "__main__": from common import run_cli run_cli(main)