Skip to content

Release

Release #2

Workflow file for this run

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/*