fix(nightly): stamp the version before building instead of renaming the wheel. (#762)
* fix(nightly): stamp the version before building instead of renaming the wheel * test(nightly): require the known build jobs rather than an exact count
This commit is contained in:
@@ -33,45 +33,35 @@ jobs:
|
||||
run: |
|
||||
python -m pip install --upgrade pip setuptools wheel build twine
|
||||
|
||||
- name: Build package
|
||||
run: |
|
||||
python -m build
|
||||
ls -la dist/
|
||||
|
||||
- name: Generate nightly version
|
||||
# Stamped before the build, not patched into the filename after it.
|
||||
# pyproject.toml takes the version from `vocalinux.version.__version__`
|
||||
# (tool.setuptools.dynamic), so renaming the wheel left its own metadata
|
||||
# saying 0.16.1 while the filename claimed 0.16.1.dev<date>+<sha>. pip
|
||||
# tolerated that; uv rejects it as a malformed wheel, and the AppImage
|
||||
# build has installed with uv since #743 — which is what broke nightly.
|
||||
# __version_info__ is deliberately left alone: a dev/local suffix has no
|
||||
# meaningful place in a numeric tuple, and nothing builds from it.
|
||||
- name: Stamp the nightly version
|
||||
id: version
|
||||
run: |
|
||||
set -euo pipefail
|
||||
BASE_VERSION=$(grep -oP '__version__\s*=\s*"\K[^"]+' src/vocalinux/version.py)
|
||||
DATE=$(date +%Y%m%d)
|
||||
SHORT_SHA=$(git rev-parse --short HEAD)
|
||||
NIGHTLY_VERSION="${BASE_VERSION}.dev${DATE}+${SHORT_SHA}"
|
||||
|
||||
sed -i "s/^__version__ = \".*\"$/__version__ = \"${NIGHTLY_VERSION}\"/" src/vocalinux/version.py
|
||||
grep -q "^__version__ = \"${NIGHTLY_VERSION}\"$" src/vocalinux/version.py
|
||||
|
||||
echo "version=${NIGHTLY_VERSION}" >> $GITHUB_OUTPUT
|
||||
echo "base_version=${BASE_VERSION}" >> $GITHUB_OUTPUT
|
||||
echo "date=${DATE}" >> $GITHUB_OUTPUT
|
||||
echo "sha=${SHORT_SHA}" >> $GITHUB_OUTPUT
|
||||
echo "Built version: ${NIGHTLY_VERSION}"
|
||||
echo "Building version: ${NIGHTLY_VERSION}"
|
||||
|
||||
- name: Rename artifacts for nightly
|
||||
- name: Build package
|
||||
run: |
|
||||
for file in dist/*; do
|
||||
base=$(basename "$file")
|
||||
if [[ "$base" == vocalinux-* ]]; then
|
||||
# Extract the file extension
|
||||
if [[ "$base" == *.whl ]]; then
|
||||
# vocalinux-0.5.0b0-py3-none-any.whl -> vocalinux-0.5.0b0.dev20250211+g1234567-py3-none-any.whl
|
||||
newname="vocalinux-${{ steps.version.outputs.version }}-py3-none-any.whl"
|
||||
elif [[ "$base" == *.tar.gz ]]; then
|
||||
# vocalinux-0.5.0b0.tar.gz -> vocalinux-0.5.0b0.dev20250211+g1234567.tar.gz
|
||||
newname="vocalinux-${{ steps.version.outputs.version }}.tar.gz"
|
||||
else
|
||||
# Fallback: replace version in filename
|
||||
newname=$(echo "$base" | sed 's/vocalinux-[^[:space:]]*/vocalinux-${{ steps.version.outputs.version }}/')
|
||||
fi
|
||||
if [[ "$base" != "$newname" ]]; then
|
||||
mv "$file" "dist/$newname"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
python -m build
|
||||
ls -la dist/
|
||||
|
||||
# glslc is built from the pinned shaderc commit inside the container -
|
||||
@@ -270,6 +260,16 @@ jobs:
|
||||
run: |
|
||||
python -m pip install --upgrade pip setuptools wheel build
|
||||
|
||||
# This job builds its own wheel, so it needs the same stamp: without it
|
||||
# the aarch64 AppImage bundles a wheel calling itself 0.16.1 while the
|
||||
# release it is attached to is 0.16.1.dev<date>+<sha>.
|
||||
- name: Stamp the nightly version
|
||||
run: |
|
||||
set -euo pipefail
|
||||
VERSION="${{ needs.nightly.outputs.version }}"
|
||||
sed -i "s/^__version__ = \".*\"$/__version__ = \"${VERSION}\"/" src/vocalinux/version.py
|
||||
grep -q "^__version__ = \"${VERSION}\"$" src/vocalinux/version.py
|
||||
|
||||
- name: Build package
|
||||
run: |
|
||||
python -m build
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
"""A nightly artifact's filename and its own metadata have to agree.
|
||||
|
||||
`nightly.yml` used to build the wheel and then rename the file to carry the
|
||||
nightly version, leaving `.dist-info` still saying 0.16.1 while the filename
|
||||
claimed 0.16.1.dev<date>+<sha>. pip installed that without complaint for as long
|
||||
as the AppImage build used pip. #743 moved that install to uv, which rejects the
|
||||
mismatch outright:
|
||||
|
||||
Wheel version does not match filename (0.16.1 != 0.16.1.dev20260831+71929b7),
|
||||
which indicates a malformed wheel.
|
||||
|
||||
Nightly went red the first night it ran the new build.sh and stayed red. The fix
|
||||
is to stamp `vocalinux.version.__version__` — where `tool.setuptools.dynamic`
|
||||
reads the version from — before building, so the two cannot disagree.
|
||||
|
||||
Parsed as text rather than YAML: the only YAML parser in the venv arrives
|
||||
transitively through pre-commit.
|
||||
"""
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
NIGHTLY = REPO_ROOT / ".github" / "workflows" / "nightly.yml"
|
||||
PYPROJECT = REPO_ROOT / "pyproject.toml"
|
||||
|
||||
STAMP = '__version__ = \\"${'
|
||||
|
||||
#: The jobs that build a distribution, and so must stamp before they do.
|
||||
BUILDING_JOBS = {"nightly", "nightly-appimage-arm64"}
|
||||
|
||||
|
||||
def _text() -> str:
|
||||
return NIGHTLY.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def _jobs() -> dict:
|
||||
"""job name -> its block of the workflow, split on the 2-space indent."""
|
||||
text = _text()
|
||||
start = text.index("\njobs:\n")
|
||||
body = text[start:]
|
||||
headers = list(re.finditer(r"^ ([a-z0-9][a-z0-9-]*):$", body, re.M))
|
||||
assert headers, "no jobs found; nightly.yml's layout moved"
|
||||
|
||||
jobs = {}
|
||||
for index, header in enumerate(headers):
|
||||
end = headers[index + 1].start() if index + 1 < len(headers) else len(body)
|
||||
jobs[header.group(1)] = body[header.start() : end]
|
||||
return jobs
|
||||
|
||||
|
||||
def _building_jobs() -> dict:
|
||||
return {name: block for name, block in _jobs().items() if "python -m build" in block}
|
||||
|
||||
|
||||
def test_the_version_still_comes_from_the_file_the_workflow_stamps():
|
||||
"""If setuptools stopped reading version.py, stamping it would silently do
|
||||
nothing and the mismatch would come back."""
|
||||
pyproject = PYPROJECT.read_text(encoding="utf-8")
|
||||
assert 'version = {attr = "vocalinux.version.__version__"}' in pyproject
|
||||
|
||||
|
||||
def test_nightly_never_renames_a_built_artifact():
|
||||
"""Renaming a wheel does not change what is recorded inside it."""
|
||||
text = _text()
|
||||
assert "Rename artifacts" not in text, "the rename step is back"
|
||||
offenders = [
|
||||
line.strip()
|
||||
for line in text.splitlines()
|
||||
if re.search(r"\bmv\b.*dist/", line) or re.search(r"newname=", line)
|
||||
]
|
||||
assert not offenders, "nightly renames build output:\n" + "\n".join(offenders)
|
||||
|
||||
|
||||
def test_every_job_that_builds_stamps_the_version_first():
|
||||
jobs = _building_jobs()
|
||||
# A floor, not a fixed count. The guard is worthless if the parser quietly
|
||||
# stops finding a job, but a third build job should not fail CI merely for
|
||||
# existing: the loop below covers whatever is actually there.
|
||||
missing = BUILDING_JOBS - set(jobs)
|
||||
assert not missing, f"{sorted(missing)} no longer builds, or the parser stopped seeing it"
|
||||
for name, block in jobs.items():
|
||||
assert STAMP in block, f"{name} builds without stamping the version"
|
||||
assert block.index(STAMP) < block.index(
|
||||
"python -m build"
|
||||
), f"{name} stamps the version after building, which changes nothing"
|
||||
|
||||
|
||||
def test_the_stamp_is_verified_rather_than_assumed():
|
||||
"""A `sed` that matches nothing exits 0 and hands the build a stale version."""
|
||||
for name, block in _building_jobs().items():
|
||||
assert re.search(
|
||||
r'grep -q "\^__version__ = ', block
|
||||
), f"{name} does not check that the stamp landed"
|
||||
|
||||
|
||||
def test_both_jobs_stamp_the_same_version():
|
||||
"""The aarch64 job builds its own wheel; if it stamps something else, the two
|
||||
AppImages on one nightly release carry different versions.
|
||||
|
||||
Anchored on the assignment that feeds `sed`, not on the value appearing
|
||||
anywhere in the job: it also appears in the AppImage step, which made an
|
||||
earlier version of this test pass while aarch64 stamped a date.
|
||||
"""
|
||||
arm = _jobs()["nightly-appimage-arm64"]
|
||||
assert (
|
||||
'VERSION="${{ needs.nightly.outputs.version }}"' in arm
|
||||
), "aarch64 stamps something other than the version the nightly job published"
|
||||
Reference in New Issue
Block a user