Derive the version from the git tag instead of a checked-in file - #31
Merged
Conversation
`xbudget/version.py` held a literal that had to be bumped in its own commit before every release, and nothing tied that commit to the tag the release was actually cut from. The two could disagree, and when they did the symptom was a 400 from PyPI at the very end of the release. hatch-vcs derives the version from the tag at build time and writes it to a generated `xbudget/_version.py`, so tagging *is* the bump. `version.py` becomes a shim over the generated file, with a `0.0.0+unknown` fallback for a checkout that has never been built. The tag has to be visible for that to work, so every checkout that installs the package uses `fetch-depth: 0` and Read the Docs unshallows in `post_checkout`; without it the build quietly produces a `.devN` artifact. The publish workflow also asserts that the version it built matches the tag it was fired from, which is the check that would have caught the failure mode described above. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ports the scheme from hdrake/xeos#10 to
xbudget: the version is derived from the git tag byhatch-vcsinstead of being read from a literal inxbudget/version.py.Why
xbudget/version.pyheld__version__ = "0.7.0", which had to be bumped in its own commit before every release. Nothing tied that commit to the tag the release was actually cut from, so the two could disagree — and when they did, the symptom was a400 File already existsfrom PyPI at the very end of the release. That is exactly how thexeosv0.2.1 release failed: the tag was placed one commit before the bump, the workflow built the previous version, and PyPI rejected it.With the tag as the source of truth, tagging is the bump. There is no second commit to remember and no way for the tag and the artifact to disagree.
What changed
pyproject.toml—hatch-vcsadded tobuild-system.requires;[tool.hatch.version]switches frompath = "xbudget/version.py"tosource = "vcs";local_scheme = "no-local-version"so untagged builds areX.Y.Z.devNrather than PEP 440 local versions that indexes refuse; a build hook writes the resolved version toxbudget/_version.py.xbudget/version.py— now a shim that imports from the generated_version.py, with a0.0.0+unknownfallback for a checkout that has never been built or installed.xbudget.__version__is unchanged for anything installed from a release..gitignore— ignores the generatedxbudget/_version.py..github/workflows/publish-to-pypi.yml—fetch-depth: 0(a shallow clone has no tag, so the build would silently produce a.devNartifact), plus a step asserting the built version matches the release tag.checkout/setup-pythonbumped off the long-EOL v2..github/workflows/ci.yml—fetch-depth: 0on both checkouts, since both install the package..readthedocs.yaml—post_checkoutunshallows and fetches tags, so the docs are titled with the release version rather than a.devN.README.md— a## Releasingsection documenting the tag-is-the-version procedure.CLAUDE.md,CHANGELOG.md— the invariants and the user-visible consequences.Verified locally
v9.9.9producesxbudget-9.9.9.tar.gz/.whl, no suffix.mainbuilds as0.7.1.dev2(last tagv0.7.0+ distance).xbudget/_version.py, and extracting it outside any git repo and building a wheel from it yields9.9.9— the property conda-forge depends on.Follow-up needed on the feedstock
conda-forge builds with
--no-build-isolation, so the backend's requirements must be present inhost. conda-forge/xbudget-feedstock's recipe currently lists onlyhatchling; I confirmed locally that building the sdist with--no-build-isolationand nohatch-vcsinstalled fails inhatchling.builders.plugin.interface.get_build_hooks.hatch-vcsmust be added next tohatchlingin the feedstock'shostrequirements before the next release builds.ci/check_conda_parity.pycompares only run requirements, so it will not catch this; the requirement is written down inCLAUDE.mdand the README instead.Note on untagged builds
A checkout without tags — a shallow clone, or a fork that never fetched them — now resolves a
.devNversion rather than the release line. That is why every checkout in CI usesfetch-depth: 0.🤖 Generated with Claude Code