Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions .github/workflows/publish-testpypi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Publish (TestPyPI)

# Dry-run lane ONLY — manual dispatch, publishes to test.pypi.org via OIDC
# trusted publishing. Cannot touch real PyPI: this workflow contains no pypi
# environment and no pypi publisher matches its filename. The real-PyPI lane
# is publish.yml (GitHub release). One-time setup: RELEASING.md.

on:
workflow_dispatch:

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Build sdist + wheel
run: |
python -m pip install --upgrade build twine
python -m build
python -m twine check --strict dist/*
- name: Smoke-test the wheel
run: |
python -m venv /tmp/wheeltest
/tmp/wheeltest/bin/pip install -q dist/*.whl
/tmp/wheeltest/bin/gpu-proof --help
/tmp/wheeltest/bin/python -c "import pytest_gpu_proof"
/tmp/wheeltest/bin/python -m pytest -p pytest_gpu_proof --version
- uses: actions/upload-artifact@v4
with:
name: dist
path: dist/

publish-testpypi:
needs: build
runs-on: ubuntu-latest
environment: testpypi
permissions:
id-token: write # OIDC trusted publishing
steps:
- uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- uses: pypa/gh-action-pypi-publish@release/v1
with:
repository-url: https://test.pypi.org/legacy/
27 changes: 4 additions & 23 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
name: Publish

# PyPI publishing via OIDC trusted publishing — no tokens stored in the repo.
# One-time setup on pypi.org (and test.pypi.org) is documented in RELEASING.md.
#
# - GitHub release published -> build + publish to PyPI
# - manual workflow_dispatch -> build + publish to TestPyPI (dry-run lane)
# Real-PyPI lane ONLY — runs exclusively on a published GitHub release, via
# OIDC trusted publishing (no tokens). The TestPyPI dry-run lane is the
# separate publish-testpypi.yml workflow (manual dispatch), so a dispatch run
# never shows a PyPI job. One-time setup: RELEASING.md.

on:
release:
types: [published]
workflow_dispatch:

jobs:
build:
Expand All @@ -36,24 +34,7 @@ jobs:
name: dist
path: dist/

publish-testpypi:
if: github.event_name == 'workflow_dispatch'
needs: build
runs-on: ubuntu-latest
environment: testpypi
permissions:
id-token: write # OIDC trusted publishing
steps:
- uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- uses: pypa/gh-action-pypi-publish@release/v1
with:
repository-url: https://test.pypi.org/legacy/

publish-pypi:
if: github.event_name == 'release'
needs: build
runs-on: ubuntu-latest
environment: pypi
Expand Down
29 changes: 19 additions & 10 deletions RELEASING.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
# Releasing pytest-gpu-proof

Publishing runs through GitHub Actions **OIDC trusted publishing** — no PyPI
tokens are stored anywhere. `.github/workflows/publish.yml` has two lanes:
tokens are stored anywhere. The two lanes are **separate workflow files**, so
a TestPyPI dry-run never shows (or touches) a PyPI job:

- **TestPyPI** — manual `workflow_dispatch` of the Publish workflow (dry-run lane).
- **PyPI** — automatic on a published GitHub release.
- **TestPyPI** — `.github/workflows/publish-testpypi.yml`, manual
`workflow_dispatch` only.
- **PyPI** — `.github/workflows/publish.yml`, automatic on a published GitHub
release only.

Both lanes build sdist + wheel, run `twine check --strict`, and smoke-test the
wheel (CLI entry point, import, pytest plugin registration) before uploading.

## One-time setup (repo owner, web UI)

1. **PyPI** (pypi.org → account → Publishing → "Add a new pending publisher"):
- PyPI project name: `pytest-gpu-proof`
- PyPI Project Name: `pytest-gpu-proof`
- Owner: `A2R-Lab`, Repository: `pytest-gpu-proof`
- Workflow name: `publish.yml`
- Environment: `pypi`
2. **TestPyPI** (test.pypi.org, same form) with environment `testpypi`.
2. **TestPyPI** (test.pypi.org, same form):
- Workflow name: `publish-testpypi.yml` ← note: NOT publish.yml
- Environment: `testpypi`
3. **GitHub** (repo → Settings → Environments): create environments `pypi` and
`testpypi`. Optionally add yourself as a required reviewer on `pypi` so
every real publish needs a click of approval.
`testpypi`. Strongly recommended: add yourself as a **required reviewer**
on `pypi` — every real publish then waits for your explicit approval click,
as defense-in-depth on top of the release-only trigger.

The first trusted-publisher upload CREATES the project on (Test)PyPI and
registers the name — no separate name registration step.
Expand All @@ -29,15 +35,18 @@ registers the name — no separate name registration step.
1. Update `version` in `pyproject.toml` and retitle the `unreleased` section
in `CHANGELOG.md` with the date. Commit via the normal branch → PR → CI
green → merge flow.
2. Dry run: Actions → Publish → "Run workflow" (publishes to TestPyPI), then
2. Dry run: Actions → **Publish (TestPyPI)** → "Run workflow", then
`pip install -i https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ pytest-gpu-proof`
in a scratch venv and run `gpu-proof --help`.
(TestPyPI never accepts the same version twice — a duplicate-version error
on a re-run is expected and harmless.)
3. Tag + release:
```bash
git tag v0.X.Y && git push origin v0.X.Y
gh release create v0.X.Y --title "v0.X.Y" --notes-file <(sed -n '/^## \[0.X.Y\]/,/^## \[/p' CHANGELOG.md | head -n -1)
gh release create v0.X.Y --title "v0.X.Y" --generate-notes
```
The release event publishes to PyPI.
The release event runs the **Publish** workflow → PyPI (pausing for your
environment approval if configured).
4. Sanity: `pip install pytest-gpu-proof==0.X.Y` in a scratch venv.

## Versioning
Expand Down
Loading