From 7b626ef5906f88d4547988edc3b5403a7680941b Mon Sep 17 00:00:00 2001 From: Marcus Date: Tue, 11 Aug 2026 01:24:45 +0800 Subject: [PATCH] ci: build and publish a release when a tag is pushed Pushing a tag matching v* now builds the wheel and the sdist and attaches both to a GitHub Release. A bare tag gives you only the "Source code" archives GitHub generates, which are snapshots of the repository rather than built packages, so installing from one means building from source. The workflow refuses to build when the tag does not match __version__. A tag can point at any commit, so nothing else keeps the version in the artefact and the version people install by from drifting apart. It runs the tests for the same reason, and imports the built wheel in an empty environment before publishing. `gh release create` rather than a third-party action: the CLI is already on the runner, and this is one less thing with write access to the repository. Also adds .gitattributes. Without it git checks out CRLF on Windows while the mixed-line-ending hook rewrites to LF, so the two undo each other on every run and every commit warns about it. Drop this file if the churn is preferable. Co-Authored-By: Claude Opus 5 --- .gitattributes | 4 +++ .github/workflows/release.yml | 59 +++++++++++++++++++++++++++++++++++ README.md | 47 +++++++++++++++++++++++++--- 3 files changed, 105 insertions(+), 5 deletions(-) create mode 100644 .gitattributes create mode 100644 .github/workflows/release.yml diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..0071ae2 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +# LF in the repository and in the working tree, on every platform. Without this +# git checks out CRLF on Windows while the mixed-line-ending hook rewrites to +# LF, so the two undo each other on every run and every commit warns about it. +* text=auto eol=lf diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..a8e00ed --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,59 @@ +name: Release + +# Pushing a tag like v2.0.0 builds the distributions and publishes them as a +# GitHub Release. The tag is the trigger; nothing is released without one. +on: + push: + tags: + - "v*" + +permissions: + contents: write + +jobs: + release: + name: Build and publish + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + enable-cache: true + + # A tag can point at any commit, so the version in the artefact and the + # version in the tag are only the same if something checks. + - name: Check the tag matches __version__ + run: | + tag="${GITHUB_REF_NAME#v}" + version=$(sed -n 's/^__version__ = "\(.*\)"$/\1/p' src/loggerhelper/__init__.py) + echo "tag=$tag __version__=$version" + if [ "$tag" != "$version" ]; then + echo "::error::tag ${GITHUB_REF_NAME} does not match __version__ ${version} in src/loggerhelper/__init__.py" + exit 1 + fi + + - name: Sync dependencies + run: uv sync --all-extras --dev + + # A tag can also point at a commit that never passed CI. + - name: Run tests + run: uv run pytest tests/ + + - name: Build the distributions + run: uv build + + - name: Check the wheel imports from a clean environment + run: | + uv run --no-project --isolated --with dist/*.whl \ + python -c "import loggerhelper; print(loggerhelper.__version__)" + + - name: Publish the release + env: + GH_TOKEN: ${{ github.token }} + run: | + gh release create "$GITHUB_REF_NAME" dist/* \ + --title "$GITHUB_REF_NAME" \ + --generate-notes \ + --verify-tag diff --git a/README.md b/README.md index 5dad172..4a26b31 100644 --- a/README.md +++ b/README.md @@ -630,7 +630,44 @@ has nothing else in it: uv run --no-project --isolated --with dist/loggerhelper-2.0.0-py3-none-any.whl python -c "import loggerhelper; print(loggerhelper.__version__)" ``` -Publish: +### Releasing + +Releases are cut by pushing a tag. `.github/workflows/release.yml` does the rest: +it checks the tag against `__version__`, runs the tests, builds, verifies the +wheel imports from a clean environment, and publishes a GitHub Release with both +distributions attached. + +```bash +# 1. bump __version__ in src/loggerhelper/__init__.py, commit, merge to main +# 2. tag the commit on main and push the tag +git tag -a v2.0.0 -m "v2.0.0" +git push origin v2.0.0 +``` + +The tag must be `v` + exactly what `__version__` says, or the workflow stops +before building — a tag can point at any commit, so nothing else guarantees the +version in the artefact matches the version in the tag. + +The release page then carries: + +| Asset | What it is | +| -------------------------------------- | -------------------------------------- | +| `loggerhelper-2.0.0-py3-none-any.whl` | The wheel — this is what you install | +| `loggerhelper-2.0.0.tar.gz` | The sdist | +| Source code (zip) / (tar.gz) | Added by GitHub automatically; a snapshot of the repo, **not** a built package | + +Only the first two are built artefacts. The two GitHub adds are archives of the +repository at that tag, and installing from them means building from source. + +Install a released wheel straight from GitHub: + +```bash +pip install https://github.com/Marcus778899/LoggerPackage/releases/download/v2.0.0/loggerhelper-2.0.0-py3-none-any.whl +``` + +### Publishing to an index + +Not part of the workflow, since it needs a token: ```bash uv publish @@ -644,10 +681,10 @@ directly — this is what `Database_connector_mcp` does: loggerhelper = { path = "lib/loggerhelper-2.0.0-py3-none-any.whl" } ``` -Before tagging a release, bump `__version__` in `src/loggerhelper/__init__.py`. -That is the only place it is written — `pyproject.toml` declares the version -`dynamic` and reads it from there, so the built artefact and -`loggerhelper.__version__` cannot disagree. +`__version__` in `src/loggerhelper/__init__.py` is the only place the version is +written — `pyproject.toml` declares it `dynamic` and reads it from there, so the +built artefact and `loggerhelper.__version__` cannot disagree, and the release +workflow checks the tag against it. ---