From 3ff145a0f0df845bb5b56cd439e2211f6d941168 Mon Sep 17 00:00:00 2001 From: Leo Vasanko Date: Sun, 4 Oct 2026 18:15:07 +0000 Subject: [PATCH] Fix release versioning: keep tree ruff-clean, fail fast on dirty tag builds The tagged v0.10.6 CI build produced wheels versioned 0.10.7.dev0+g503ce8e49.d20261004: setup.py was not ruff-format-clean, so release.py's own 'ruff format .' step rewrote it, dirtying the checkout before setuptools_scm ran. PyPI then rejected the local version segment with HTTP 400. - ruff-format setup.py - release.py: on a v*-tagged HEAD, fail before building if the version is not a clean release, printing the dirty files - add .gitattributes (eol=lf) so Windows checkouts can't dirty the tree via normalize_line_endings either --- .gitattributes | 1 + .gitignore | 1 + setup.py | 6 ++++-- tools/release.py | 27 +++++++++++++++++++++++++++ 4 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6313b56 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/.gitignore b/.gitignore index f748760..3e62326 100644 --- a/.gitignore +++ b/.gitignore @@ -8,5 +8,6 @@ /src/aeg/_aegis*.pyd __pycache__ !.gitignore +!.gitattributes !.gitea/ !.gitmodules diff --git a/setup.py b/setup.py index bfa78ae..3c226e1 100644 --- a/setup.py +++ b/setup.py @@ -14,8 +14,10 @@ if sys.maxsize <= 2**32: "aeg does not support 32-bit platforms; build with a 64-bit Python" ) -libaegis_static = ROOT / "libaegis/zig-out/lib" / ( - "aegis.lib" if sys.platform == "win32" else "libaegis.a" +libaegis_static = ( + ROOT + / "libaegis/zig-out/lib" + / ("aegis.lib" if sys.platform == "win32" else "libaegis.a") ) ffibuilder = FFI() diff --git a/tools/release.py b/tools/release.py index 438e8be..217266c 100755 --- a/tools/release.py +++ b/tools/release.py @@ -347,6 +347,33 @@ def main(): return 1 is_release = is_release_version(version) + # On a tagged commit the version must be a clean release (e.g. 0.10.6). + # A dev/dirty version means the steps above rewrote a tracked file (or the + # checkout was dirty); PyPI rejects such versions, so fail before building. + head_tags = subprocess.run( + ["git", "tag", "--points-at", "HEAD", "--list", "v*"], + capture_output=True, + text=True, + check=False, + ).stdout.split() + if head_tags and not is_release: + print( + f"✗ HEAD is tagged ({', '.join(head_tags)}) but the version resolved " + f"to {version}, not a clean release.", + file=sys.stderr, + ) + dirty = subprocess.run( + ["git", "status", "--porcelain"], + capture_output=True, + text=True, + check=False, + ).stdout + if dirty.strip(): + print("Working copy is dirty:", file=sys.stderr) + print(dirty, file=sys.stderr, end="") + print("Commit the changes, re-tag, and run again.", file=sys.stderr) + return 1 + # Main header for the packaging process print(f"\n{'=' * 70}") print(