Skip to content

Commit 13e3c70

Browse files
paddymulclaude
andauthored
build: use hatch-vcs for dynamic versioning from git tags (#639)
* build: use hatch-vcs for dynamic versioning from git tags Replace the static version in pyproject.toml with hatch-vcs, which derives the version from git tags at build time. This eliminates the drift where pyproject.toml on main stays stale because the release workflow could only push tags (not branch commits due to branch protection). - pyproject.toml: add hatch-vcs, replace static version with dynamic - checks.yml: BuildWheel gets fetch-depth: 0, TestPyPI uses SETUPTOOLS_SCM_PRETEND_VERSION instead of sed - release.yml: remove sed/commit/git-config — just tag and push - fallback-version handles shallow clones in non-build CI jobs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: use git tag --sort instead of git describe for TestPyPI version git describe only finds tags reachable from HEAD. Tags 0.13.0 and 0.13.1 live on orphaned commits (created by the old release workflow's version-bump commit that was never pushed to main), so git describe finds 0.12.11 instead. git tag --sort=-version:refname finds the highest semver tag regardless of reachability. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address Codex review — docs version + release retry - docs/source/conf.py: use importlib.metadata.version() instead of reading pyproject.toml (which no longer has a static version field) - release.yml: on retry, checkout the tagged commit and set SETUPTOOLS_SCM_PRETEND_VERSION so the build produces the exact release version, not a dev version from HEAD Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: avoid name collision with Sphinx's version config variable import metadata module instead of the version function directly, since Sphinx reads 'version' from conf.py's namespace as a config key. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent d08ce0e commit 13e3c70

5 files changed

Lines changed: 34 additions & 28 deletions

File tree

.github/workflows/checks.yml

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ jobs:
6262
timeout-minutes: 10
6363
steps:
6464
- uses: actions/checkout@v6
65+
with:
66+
fetch-depth: 0
6567
- name: Install uv
6668
uses: astral-sh/setup-uv@v7
6769
with:
@@ -80,15 +82,14 @@ jobs:
8082
- name: Install pnpm dependencies
8183
working-directory: packages
8284
run: pnpm install --frozen-lockfile
83-
- name: Install the project
84-
run: uv sync --all-extras --dev
8585
- name: Patch version for TestPyPI
8686
if: github.event_name == 'pull_request'
8787
run: |
88-
BASE=$(grep -Po '^version = "\K[^"]+' pyproject.toml)
89-
DEV="${BASE}.dev${{ github.run_id }}"
90-
sed -i "s/^version = \".*\"/version = \"${DEV}\"/" pyproject.toml
91-
echo "Published version: ${DEV}"
88+
BASE=$(git tag --sort=-version:refname | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | head -1 || echo "0.0.0")
89+
echo "SETUPTOOLS_SCM_PRETEND_VERSION=${BASE}.dev${{ github.run_id }}" >> "$GITHUB_ENV"
90+
echo "Published version: ${BASE}.dev${{ github.run_id }}"
91+
- name: Install the project
92+
run: uv sync --all-extras --dev
9293
- name: Build JS extension + Python wheel
9394
run: ./scripts/full_build.sh
9495
- name: Upload build artifacts

.github/workflows/release.yml

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -64,17 +64,19 @@ jobs:
6464
# `|| true` prevents pipefail exit when no tags match the pattern.
6565
LATEST_TAG=$(git tag --sort=-creatordate | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | head -1 || true)
6666
if [ -z "$LATEST_TAG" ]; then
67-
CURRENT=$(grep -Po '^version = "\K[^"]+' pyproject.toml)
68-
echo "No semver tags found; falling back to pyproject.toml: $CURRENT"
67+
CURRENT="0.0.0"
68+
echo "No semver tags found; starting from $CURRENT"
6969
else
7070
# If the latest tag has no GitHub release, a prior run failed
7171
# after tagging. Retry that version instead of bumping again.
7272
if ! gh release view "$LATEST_TAG" &>/dev/null; then
7373
PREV_TAG=$(git tag --sort=-creatordate | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | sed -n '2p' || true)
7474
echo "::warning::Tag $LATEST_TAG exists without a release — retrying incomplete release."
75-
echo "new=$LATEST_TAG" >> "$GITHUB_OUTPUT"
76-
echo "prev_tag=${PREV_TAG:-$LATEST_TAG}" >> "$GITHUB_OUTPUT"
77-
echo "retry=true" >> "$GITHUB_OUTPUT"
75+
{
76+
echo "new=$LATEST_TAG"
77+
echo "prev_tag=${PREV_TAG:-$LATEST_TAG}"
78+
echo "retry=true"
79+
} >> "$GITHUB_OUTPUT"
7880
exit 0
7981
fi
8082
CURRENT="$LATEST_TAG"
@@ -91,9 +93,11 @@ jobs:
9193
echo "::error::Tag $NEW already exists — this release may have already run. Aborting."
9294
exit 1
9395
fi
94-
echo "new=$NEW" >> "$GITHUB_OUTPUT"
95-
echo "prev_tag=$CURRENT" >> "$GITHUB_OUTPUT"
96-
echo "retry=false" >> "$GITHUB_OUTPUT"
96+
{
97+
echo "new=$NEW"
98+
echo "prev_tag=$CURRENT"
99+
echo "retry=false"
100+
} >> "$GITHUB_OUTPUT"
97101
echo "Bumping $CURRENT → $NEW (${{ inputs.bump }})"
98102
99103
- name: Generate release notes
@@ -106,21 +110,17 @@ jobs:
106110
--date "$(date +%Y-%m-%d)" \
107111
--output-dir /tmp
108112
109-
- name: Bump version, commit, tag, and push
113+
- name: Tag and push
110114
run: |
111-
sed -i 's/^version = ".*"/version = "${{ steps.versions.outputs.new }}"/' pyproject.toml
112115
if [ "${{ steps.versions.outputs.retry }}" = "true" ]; then
113-
echo "Retry mode — tag ${{ steps.versions.outputs.new }} already exists, skipping."
116+
echo "Retry mode — tag ${{ steps.versions.outputs.new }} already exists, checking out tagged commit."
117+
git checkout "${{ steps.versions.outputs.new }}"
114118
else
115-
# Commit the version bump so the tag points to correct source.
116-
# Only the tag is pushed — not the branch (branch protection).
117-
git config user.name "github-actions[bot]"
118-
git config user.email "github-actions[bot]@users.noreply.github.com"
119-
git add pyproject.toml
120-
git commit -m "chore: release ${{ steps.versions.outputs.new }}"
121119
git tag "${{ steps.versions.outputs.new }}"
122120
git push origin "${{ steps.versions.outputs.new }}"
123121
fi
122+
# Pin hatch-vcs to the exact release version for the build step
123+
echo "SETUPTOOLS_SCM_PRETEND_VERSION=${{ steps.versions.outputs.new }}" >> "$GITHUB_ENV"
124124
125125
- name: Create GitHub release
126126
env:

docs/source/conf.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55

66
# -- Project information -----------------------------------------------------
77
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
8-
import toml
8+
from importlib import metadata
99

1010
project = 'Buckaroo'
1111
copyright = '2023-2025, Paddy Mullen'
1212
author = 'Paddy Mullen'
13-
release = toml.load(open("../../pyproject.toml"))['project']['version']
13+
release = metadata.version('buckaroo')
1414

1515

1616
# -- General configuration ---------------------------------------------------

pyproject.toml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
[build-system]
22
requires = [
33
"hatchling",
4+
"hatch-vcs",
45
"jupyterlab~=4.0",
56
]
67
build-backend = "hatchling.build"
@@ -41,7 +42,7 @@ dependencies = [
4142
"numpy >= 2.2.5; python_version >= '3.13'",
4243
]
4344

44-
version = "0.12.12"
45+
dynamic = ["version"]
4546

4647

4748
[project.license]
@@ -120,6 +121,10 @@ buckaroo-table = "buckaroo_mcp_tool:main"
120121
[project.urls]
121122
Homepage = "https://github.com/paddymul/buckaroo"
122123

124+
[tool.hatch.version]
125+
source = "vcs"
126+
fallback-version = "0.0.0+unknown"
127+
123128
[tool.hatch.build]
124129
only-packages = true
125130
artifacts = ["buckaroo/static/widget.js", "buckaroo/static/compiled.css", "buckaroo/static/standalone.js", "buckaroo/static/standalone.css","scripts/hatch_build.py"]
@@ -172,6 +177,7 @@ line_length = 80
172177
# Exclude a variety of commonly ignored directories.
173178
exclude = [
174179
".bzr",
180+
".claude",
175181
".direnv",
176182
".eggs",
177183
".git",

uv.lock

Lines changed: 1 addition & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)