Release #2
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
| name: Release | |
| on: | |
| push: | |
| tags: | |
| - "v*.*.*" | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: "Release version, with or without the leading v (example: 0.4.4)" | |
| required: true | |
| type: string | |
| permissions: | |
| contents: write | |
| concurrency: | |
| group: release-${{ github.ref }} | |
| cancel-in-progress: false | |
| jobs: | |
| test: | |
| name: Test Python ${{ matrix.python-version }} | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| python-version: ["3.10", "3.11", "3.12"] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| cache: pip | |
| - name: Install | |
| run: | | |
| python -m pip install -U pip | |
| python -m pip install --no-cache-dir -e ".[dev]" | |
| - name: Pytest | |
| env: | |
| QT_QPA_PLATFORM: offscreen | |
| PYSTRAY_BACKEND: dummy | |
| run: python -m pytest tests/basic -q | |
| build: | |
| name: Build distributions | |
| runs-on: ubuntu-latest | |
| needs: [test] | |
| outputs: | |
| version: ${{ steps.release-meta.outputs.version }} | |
| tag: ${{ steps.release-meta.outputs.tag }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| cache: pip | |
| - name: Install build tools | |
| run: | | |
| python -m pip install -U pip | |
| python -m pip install build twine | |
| - name: Validate version and changelog | |
| id: release-meta | |
| env: | |
| RELEASE_INPUT_VERSION: ${{ github.event.inputs.version || '' }} | |
| run: | | |
| python - <<'PY' | |
| import os | |
| import re | |
| import sys | |
| from pathlib import Path | |
| if sys.version_info >= (3, 11): | |
| import tomllib | |
| else: | |
| import tomli as tomllib | |
| version_input = os.environ.get("RELEASE_INPUT_VERSION", "").strip() | |
| ref_name = os.environ["GITHUB_REF_NAME"].strip() | |
| raw_version = version_input or ref_name | |
| version = raw_version[1:] if raw_version.startswith("v") else raw_version | |
| if not re.fullmatch(r"\d+\.\d+\.\d+(?:[a-zA-Z0-9.-]+)?", version): | |
| raise SystemExit(f"Invalid release version: {raw_version!r}") | |
| pyproject = tomllib.loads(Path("pyproject.toml").read_text(encoding="utf-8")) | |
| package_version = pyproject["project"]["version"] | |
| if version != package_version: | |
| raise SystemExit( | |
| f"Release version {version!r} does not match pyproject version {package_version!r}" | |
| ) | |
| changelog = Path("CHANGELOG.md").read_text(encoding="utf-8") | |
| heading = f"## [{version}]" | |
| if heading not in changelog: | |
| raise SystemExit(f"CHANGELOG.md is missing {heading!r}") | |
| notes = [] | |
| capture = False | |
| for line in changelog.splitlines(): | |
| if line.startswith("## ["): | |
| if capture: | |
| break | |
| capture = line.startswith(heading) | |
| continue | |
| if capture: | |
| notes.append(line) | |
| release_notes = "\n".join(notes).strip() or f"Release {version}" | |
| Path("release-notes.md").write_text(release_notes + "\n", encoding="utf-8") | |
| with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as fh: | |
| fh.write(f"version={version}\n") | |
| fh.write(f"tag=v{version}\n") | |
| PY | |
| - name: Build | |
| run: python -m build | |
| - name: Check distributions | |
| run: python -m twine check dist/* | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: release-dist | |
| path: dist/* | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: release-notes | |
| path: release-notes.md | |
| ensure-tag: | |
| name: Ensure release tag | |
| runs-on: ubuntu-latest | |
| needs: [build] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Create or validate annotated tag | |
| env: | |
| TAG: ${{ needs.build.outputs.tag }} | |
| run: | | |
| set -euo pipefail | |
| git fetch --tags --force | |
| if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then | |
| echo "Tag ${TAG} already exists; reusing it." | |
| exit 0 | |
| fi | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git tag -a "${TAG}" -m "Release ${TAG}" | |
| git push origin "${TAG}" | |
| publish-pypi: | |
| name: Publish to PyPI | |
| runs-on: ubuntu-latest | |
| needs: [build, ensure-tag] | |
| environment: | |
| name: pypi | |
| url: https://pypi.org/project/abstractassistant/ | |
| permissions: | |
| id-token: write | |
| contents: read | |
| steps: | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| name: release-dist | |
| path: dist | |
| - uses: pypa/gh-action-pypi-publish@release/v1 | |
| github-release: | |
| name: Create GitHub Release | |
| runs-on: ubuntu-latest | |
| needs: [build, ensure-tag, publish-pypi] | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| name: release-dist | |
| path: dist | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| name: release-notes | |
| path: . | |
| - uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ needs.build.outputs.tag }} | |
| name: AbstractAssistant ${{ needs.build.outputs.version }} | |
| body_path: release-notes.md | |
| files: dist/* |