From 7da48ca56a6dba3cd4861c3adb4d43f9817891ca Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Mon, 9 Nov 2026 11:40:37 +0100 Subject: [PATCH] feat: date the PyPI package by install.py commits --- scripts/build_pypi.py | 37 +++++++++++++++++++++++++------------ tests/test_pypi_package.py | 20 +++++++++++++------- wiki/release-process.md | 12 +++++++----- 3 files changed, 45 insertions(+), 24 deletions(-) diff --git a/scripts/build_pypi.py b/scripts/build_pypi.py index efbeca92a..d7b3e7167 100644 --- a/scripts/build_pypi.py +++ b/scripts/build_pypi.py @@ -6,9 +6,11 @@ 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. +The version is the UTC date of the last commit that changed install.py, +with the zeros PEP 440 drops left out: 2026-11-09 gives 2026.11.9. When +install.py changed several times that day, the newest gets .N, its rank in +the day: the third change gives 2026.11.9.3. The same install.py always +gives the same version, so PyPI refuses a republish of unchanged code. 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 @@ -25,11 +27,13 @@ from __future__ import annotations import argparse import base64 import calendar +import datetime import gzip import hashlib import io import json import re +import subprocess import tarfile import zipfile from pathlib import Path @@ -63,13 +67,22 @@ PROJECT_KEYS = frozenset({ }) -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 package_version(stamps: list[int]) -> str: + """The version of install.py from its commit times, newest first (UTC).""" + if not stamps: + raise ValueError("install.py has no commit to date the package by") + day = datetime.datetime.fromtimestamp(stamps[0], datetime.timezone.utc).date() + rank = sum(1 for stamp in stamps + if datetime.datetime.fromtimestamp(stamp, datetime.timezone.utc).date() == day) + base = f"{day.year}.{day.month}.{day.day}" + return base if rank == 1 else f"{base}.{rank}" + + +def install_py_commits() -> list[int]: + """Commit times of install.py, newest first, from git.""" + out = subprocess.run(["git", "log", "--format=%ct", "--", "install.py"], cwd=ROOT, + capture_output=True, text=True, check=True).stdout + return [int(line) for line in out.split()] def load_project() -> dict: @@ -181,8 +194,8 @@ def write_sdist(path: Path, project: dict, files: dict[str, bytes], version: str 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(".")) + version = package_version(install_py_commits()) + year, month, day = (int(part) for part in version.split(".")[:3]) stamp = (year, month, day) project, files = package_files(version) output_dir.mkdir(parents=True, exist_ok=True) diff --git a/tests/test_pypi_package.py b/tests/test_pypi_package.py index cf3e7bdb5..812468b3e 100644 --- a/tests/test_pypi_package.py +++ b/tests/test_pypi_package.py @@ -96,15 +96,21 @@ class TheArchivesHoldTheInstallerAndNothingElse(Built): [self.wheel.read_bytes(), self.sdist.read_bytes()]) -class TheVersionIsTheReleaseDate(unittest.TestCase): +class TheVersionIsInstallPyCommitDate(unittest.TestCase): + """The release date put November code under an October version: the + version now follows the commits of install.py itself.""" - def test_dated_tag(self): - self.assertEqual(build_pypi.package_version({"tag": "v2026.10.04"}), "2026.10.4") + def test_one_change_that_day(self): + # 2026-11-09 12:00 UTC, then a change the day before. + self.assertEqual(build_pypi.package_version([1794225600, 1794139200]), "2026.11.9") - def test_other_tag_refused(self): - for tag in ("large-files", "2026.10.04", "v2026.10"): - with self.assertRaises(ValueError): - build_pypi.package_version({"tag": tag}) + def test_the_rank_in_the_day(self): + self.assertEqual(build_pypi.package_version([1794229200, 1794225600, 1794222000]), + "2026.11.9.3") + + def test_no_commit_refused(self): + with self.assertRaises(ValueError): + build_pypi.package_version([]) def _python38() -> str | None: diff --git a/wiki/release-process.md b/wiki/release-process.md index bafe163e7..0914e4a71 100644 --- a/wiki/release-process.md +++ b/wiki/release-process.md @@ -313,11 +313,13 @@ git add release.json README.md install/*.json install/targets/*.json git commit -m "chore: record release v${DATE}" && git push # 5b. The PyPI package, `uvx retrobios`: install.py and its command. The -# version is the date of the tag release.json now names (v2026.10.04 -# gives 2026.10.4), so a release is what bumps it. The build replaces -# any earlier one in tmp/pypi-dist. Build, run the wheel, then upload -# the two files. The token stays in its file: never in the -# repository, a script or a log. +# version is the UTC date of the last commit that changed install.py +# (2026-11-09 gives 2026.11.9, the third change that day 2026.11.9.3), +# so an unchanged install.py keeps its version and PyPI refuses it +# again. Publishing is not tied to a release: any push that changes +# install.py can be published. The build replaces any earlier one in +# tmp/pypi-dist. Build, run the wheel, then upload the two files. The +# token stays in its file: never in the repository, a script or a log. python scripts/build_pypi.py --output-dir tmp/pypi-dist python -m unittest tests.test_pypi_package uvx --from tmp/pypi-dist/retrobios-*.whl retrobios --help