Skip to content

Commit ac3a35b

Browse files
committed
Improve GitHub Actions Python release workflow
Split the workflow into validation and release paths and add clearer step names and safeguards. PRs against main/master now run a validation matrix (3.10, 3.11, 3.12) that builds the package, runs twine check, and smoke-tests installing the wheel; fail-fast is disabled to surface version-specific issues. Tag pushes matching v*.*.* trigger a canonical release build (single Python 3.12) that produces dist artifacts, uploads them as workflow artifacts, and a separate publish job downloads those artifacts and uploads them to PyPI. Other improvements: minimal permissions (contents: read), explicit checkout/setup steps, pip caching enabled, comments for clarity, and use of --skip-existing when uploading to PyPI.
1 parent 853075e commit ac3a35b

1 file changed

Lines changed: 49 additions & 10 deletions

File tree

.github/workflows/python-package.yml

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,42 @@
11
name: Build, validate & Release
22

3+
# Usage:
4+
# - For PRs: this workflow runs automatically to validate the package builds and installs correctly on multiple Python versions. No artifacts are published for PRs.
5+
# - For releases: when you push a tag like v1.2.3, this workflow runs the full matrix validation, then builds the release artifacts, and finally publishes to PyPI if all checks pass.
6+
37
on:
8+
# Release pipeline: run only when pushing a version-like tag (e.g. v1.2.3)
49
push:
510
tags:
611
- "v*.*.*"
12+
13+
# Validation pipeline: run on PRs targeting main/master (no publishing)
714
pull_request:
815
branches: [main, master]
916
types: [opened, edited, synchronize, reopened]
1017

18+
# This workflow only needs to read repo contents
1119
permissions:
1220
contents: read
1321

1422
jobs:
1523
test_matrix:
24+
# PR + tag validation: ensure the project builds and installs on multiple Pythons
1625
name: Test install & smoke (Py ${{ matrix.python-version }})
1726
runs-on: ubuntu-latest
1827
strategy:
28+
# Run all versions even if one fails (helps spot version-specific issues)
1929
fail-fast: false
2030
matrix:
2131
python-version: ["3.10", "3.11", "3.12"]
32+
2233
steps:
23-
- uses: actions/checkout@v6
24-
- uses: actions/setup-python@v6
34+
# Fetch repository sources so we can build/test
35+
- name: Checkout sources
36+
uses: actions/checkout@v6
37+
38+
- name: Set up Python
39+
uses: actions/setup-python@v6
2540
with:
2641
python-version: ${{ matrix.python-version }}
2742

@@ -35,18 +50,21 @@ jobs:
3550
libxkbcommon-x11-0 \
3651
libxcb-cursor0
3752
38-
- name: Install tools
39-
run: |
40-
python -m pip install --upgrade pip
41-
python -m pip install build twine wheel "packaging>=24.2"
42-
cache: pip
53+
# Install packaging toolchain:
54+
# - build: creates wheel + sdist
55+
# - twine: validates metadata and can upload (upload only happens in publish job)
56+
- name: Install build tools
57+
run: python -m pip install -U pip build twine
4358

59+
# Build distributions just to verify packaging config works on this Python
4460
- name: Build (for validation only)
4561
run: python -m build
4662

63+
# Validate dist metadata (README rendering, required fields, etc.)
4764
- name: Twine check
4865
run: python -m twine check dist/*
4966

67+
# Smoke test: install the built wheel and verify the package imports
5068
- name: Install from wheel & smoke test
5169
run: |
5270
python -m pip install dist/*.whl
@@ -57,50 +75,71 @@ jobs:
5775
PY
5876
5977
build_release:
78+
# Tag-only build: produce the "official" release artifacts once matrix passed
6079
name: Build release artifacts
6180
runs-on: ubuntu-latest
6281
needs: test_matrix
82+
# Safety gate: only run for version tags, never for PRs/branches
6383
if: startsWith(github.ref, 'refs/tags/v')
84+
6485
steps:
65-
- uses: actions/checkout@v6
66-
- uses: actions/setup-python@v6
86+
# Fetch sources for the tagged revision
87+
- name: Checkout sources
88+
uses: actions/checkout@v6
89+
90+
# Use a single, modern Python for the canonical release build
91+
- name: Set up Python (release build)
92+
uses: actions/setup-python@v6
6793
with:
6894
python-version: "3.12"
6995

96+
# Install build + validation tooling
7097
- name: Install build tools
7198
run: python -m pip install -U pip build twine
7299

100+
# Produce both sdist and wheel in dist/
73101
- name: Build distributions
74102
run: python -m build
75103

104+
# Re-check metadata on the final artifacts we intend to publish
76105
- name: Twine check
77106
run: python -m twine check dist/*
78107

108+
# Store dist/ outputs so the publish job uploads exactly what we built here
79109
- name: Upload dist artifacts
80110
uses: actions/upload-artifact@v4
81111
with:
82112
name: dist
83113
path: dist/*
84114

85115
publish:
116+
# Tag-only publish: download built artifacts and upload them to PyPI
86117
name: Publish to PyPI (API token)
87118
runs-on: ubuntu-latest
88119
needs: build_release
120+
# Safety gate: only run for version tags
89121
if: startsWith(github.ref, 'refs/tags/v')
122+
90123
steps:
124+
# Retrieve the exact distributions produced in build_release
91125
- name: Download dist artifacts
92126
uses: actions/download-artifact@v4
93127
with:
94128
name: dist
95129
path: dist
96130

97-
- uses: actions/setup-python@v6
131+
# Set up Python (only needed to run Twine)
132+
- name: Set up Python (publish)
133+
uses: actions/setup-python@v6
98134
with:
99135
python-version: "3.x"
100136

137+
# Install twine for uploading
101138
- name: Install Twine
102139
run: python -m pip install -U twine
103140

141+
# Upload to PyPI using an API token stored in repo secrets.
142+
# --skip-existing avoids failing if you re-run a workflow for the same version.
104143
- name: Publish to PyPI
105144
env:
106145
TWINE_USERNAME: __token__

0 commit comments

Comments
 (0)