From 01d511ca8465175fc8a4fd5690ecde034dd95aa3 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 6 Jul 2026 12:15:38 +0000 Subject: [PATCH] Derive the version from git tags (hatch-vcs) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Releasing no longer requires bumping pyproject.toml and __version__ by hand — the tag is the single source of truth: - pyproject: dynamic version via hatch-vcs. - __init__: __version__ from importlib.metadata, with a dev fallback for uninstalled source trees. - release.yml: drop the now-impossible tag/version mismatch guard; fetch-depth: 0 so hatch-vcs can see tags. - ci.yml: fetch-depth: 0, and mark the mounted repo safe for git inside the manylinux container. - RELEASING.md: releasing is now just pushing a tag. Verified locally: editable install resolves 0.1.3.dev0+g... past the v0.1.2 tag, wheel build gets the same version, uninstalled source tree falls back to 0.0.0.dev0, all 5 tests pass. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01SQbVkPEprK2ebcjck5ouUy --- .github/workflows/ci.yml | 5 +++++ .github/workflows/release.yml | 22 +++++----------------- RELEASING.md | 22 ++++++++++++---------- pyproject.toml | 9 +++++++-- src/gomc_rest/__init__.py | 7 ++++++- 5 files changed, 35 insertions(+), 30 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8d30a7e..b6a4ec9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,6 +10,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 # hatch-vcs derives the version from git tags - uses: actions/setup-python@v5 with: @@ -30,11 +32,14 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 # hatch-vcs derives the version from git tags - name: Smoke test on glibc 2.34 run: | docker run --rm -v "$PWD":/w -w /w \ quay.io/pypa/manylinux_2_34_x86_64 bash -c ' PY=/opt/python/cp311-cp311/bin/python + git config --global --add safe.directory /w && "$PY" -m pip install -e . pytest && "$PY" scripts/vendor_binaries.py --only gomc-rest-linux-amd64 && "$PY" -m pytest -v diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8c6a5d9..1507cbb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,20 +35,14 @@ jobs: plat: manylinux2014_aarch64 steps: - uses: actions/checkout@v4 + with: + # hatch-vcs derives the version from git tags + fetch-depth: 0 - uses: actions/setup-python@v5 with: python-version: "3.11" - - name: Check tag matches project version - if: github.ref_type == 'tag' - shell: bash - run: | - tag="${GITHUB_REF_NAME#v}" - proj=$(python -c "import tomllib,pathlib; print(tomllib.loads(pathlib.Path('pyproject.toml').read_bytes().decode())['project']['version'])") - echo "tag=$tag project.version=$proj" - test "$tag" = "$proj" || { echo "::error::tag $GITHUB_REF_NAME does not match project.version $proj"; exit 1; } - - name: Install build tooling run: python -m pip install --upgrade build wheel @@ -72,19 +66,13 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 - uses: actions/setup-python@v5 with: python-version: "3.11" - - name: Check tag matches project version - if: github.ref_type == 'tag' - shell: bash - run: | - tag="${GITHUB_REF_NAME#v}" - proj=$(python -c "import tomllib,pathlib; print(tomllib.loads(pathlib.Path('pyproject.toml').read_bytes().decode())['project']['version'])") - test "$tag" = "$proj" || { echo "::error::tag $GITHUB_REF_NAME does not match project.version $proj"; exit 1; } - - name: Build sdist (no binary bundled) run: | python -m pip install --upgrade build diff --git a/RELEASING.md b/RELEASING.md index 2073510..1fe7753 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -6,8 +6,8 @@ Maintainer notes. Users don't need anything here — see [README.md](README.md). Pushing a `v*` tag runs `.github/workflows/release.yml`: -1. **Guard** — the tag must equal `project.version` in `pyproject.toml`, - or every job fails immediately. +1. **Version** — derived from the tag itself by hatch-vcs + (`dynamic = ["version"]`); there is no version string to keep in sync. 2. **Build wheels** (one per platform, each bundling only its matching server binary, downloaded and SHA-256-verified at build time): - `win_amd64` — `gomc-rest.exe` @@ -25,15 +25,17 @@ Pushing a `v*` tag runs `.github/workflows/release.yml`: ## Cutting a release -1. Bump the package version in **both** places (they must match the tag): - - `pyproject.toml` → `project.version` - - `src/gomc_rest/__init__.py` → `__version__` -2. Merge to `main`, then tag that exact version: +Tag `main` and push — that's all. The version comes from the tag +(hatch-vcs), and `__version__` reads the installed package metadata, so +there is nothing to bump anywhere. - ```bash - git tag v0.2.0 - git push origin v0.2.0 - ``` +```bash +git tag v0.2.0 +git push origin v0.2.0 +``` + +Untagged builds get a dev version like `0.1.3.dev2+g1234abc` — handy for +telling local builds apart from releases. If publish fails transiently, re-run the failed jobs for that tag's run from the GitHub Actions UI — PyPI skips files it already has, and the diff --git a/pyproject.toml b/pyproject.toml index 874e531..d82fc7f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,12 @@ [build-system] -requires = ["hatchling"] +requires = ["hatchling", "hatch-vcs"] build-backend = "hatchling.build" [project] name = "gomc-rest" -version = "0.1.2" +# The version comes from the git tag (hatch-vcs); releasing is just pushing +# a v* tag — no manual bump anywhere. +dynamic = ["version"] description = "Talk to Mitsubishi PLCs from Python over the MC protocol via a bundled, auto-launched gomc-rest server — with optional REST API exposure." readme = "README.md" requires-python = ">=3.10" @@ -26,6 +28,9 @@ Homepage = "https://github.com/Moge800/gomc_rest_python" "Upstream server" = "https://github.com/Moge800/gomc-rest" "HTTP client" = "https://github.com/Moge800/gomc_rest_client" +[tool.hatch.version] +source = "vcs" + [tool.hatch.build.targets.wheel] packages = ["src/gomc_rest"] # The platform-specific server binaries under gomc_rest/binaries/ are vendored diff --git a/src/gomc_rest/__init__.py b/src/gomc_rest/__init__.py index 1d52e24..8d8141d 100644 --- a/src/gomc_rest/__init__.py +++ b/src/gomc_rest/__init__.py @@ -29,9 +29,14 @@ PLCClient, ) +from importlib.metadata import PackageNotFoundError, version as _pkg_version + from ._server import Server -__version__ = "0.1.2" +try: + __version__ = _pkg_version("gomc-rest") +except PackageNotFoundError: # running from a source tree without install + __version__ = "0.0.0.dev0" def launch(