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
36 changes: 28 additions & 8 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
# The distributions are the four libraries easyssf-core, easyssf-receiver, easyssf-test and
# easyssf-receiver-fastapi (the conformance harness and the examples carry the classifier
# "Private :: Do Not Upload" and are not built here), built once, tested, checked, and uploaded
# with trusted publishing: no stored API token, the publish job's GitHub OIDC token is exchanged
# for a PyPI token that lives a quarter of an hour. The trusted publisher of each of the four
# projects on PyPI names this repository, this workflow file and the environment "pypi". Only the
# publish job may request the OIDC token, as PyPI recommends; it runs no code of the repository.
# with trusted publishing: no stored API token, each publish job's GitHub OIDC token is exchanged
# for a PyPI token that lives a quarter of an hour and reaches its one project. The trusted
# publisher of each of the four projects on PyPI names this repository, this workflow file and the
# environment "pypi-<distribution>". Only the publish jobs may request the OIDC token, as PyPI
# recommends; they run no code of the repository.
name: Release
# a tag push carries no commit for GitHub to title the run with, so name it by the tag
run-name: Release ${{ github.ref_name }}
Expand Down Expand Up @@ -119,14 +120,22 @@ jobs:
path: notes.md

publish:
name: Publish to PyPI
name: Publish ${{ matrix.distribution }} to PyPI
needs: build
runs-on: ubuntu-latest
timeout-minutes: 15
# the GitHub environment the trusted publishers name, and where a required reviewer can be set
strategy:
# one upload that fails (a project without its trusted publisher yet) leaves the others be;
# "Re-run failed jobs" retries it and then creates the GitHub release
fail-fast: false
matrix:
# the DISTRIBUTIONS of the build job; the env context is not available to a matrix
distribution: [easyssf-core, easyssf-receiver, easyssf-test, easyssf-receiver-fastapi]
# one GitHub environment per distribution, which its trusted publisher names: PyPI accepts a
# pending publisher only once per owner, repository, workflow and environment
environment:
name: pypi
url: https://pypi.org/project/easyssf-receiver/
name: pypi-${{ matrix.distribution }}
url: https://pypi.org/project/${{ matrix.distribution }}/
permissions:
# the OIDC token that trusted publishing exchanges for the PyPI token
id-token: write
Expand All @@ -135,11 +144,22 @@ jobs:
with:
name: dist
path: dist
- name: Select the files of the distribution
# the version in the pattern keeps easyssf_receiver- from matching easyssf_receiver_fastapi-
run: |
version=${GITHUB_REF_NAME#v}
name=${DISTRIBUTION//-/_}
mkdir upload
mv "dist/$name-$version-py3-none-any.whl" "dist/$name-$version.tar.gz" upload/
ls -l upload
env:
DISTRIBUTION: ${{ matrix.distribution }}
- name: Upload to PyPI
# with digital attestations (PEP 740); skip-existing makes a rerun after a partial upload
# idempotent, PyPI refuses to replace a file it has
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: upload/
skip-existing: true

github-release:
Expand Down
25 changes: 15 additions & 10 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,18 +194,23 @@ Releases go to [PyPI](https://pypi.org): the four libraries `easyssf-core`, `eas
a pushed tag `v<version>`. It checks that the tag matches the version of every member of the
workspace and takes the release notes from the `CHANGELOG.md` section of that version. It then
tests, lints, builds the four, checks that there is a wheel and an sdist of each at that version
(`twine check --strict`), and installs the wheels in a clean environment. The `publish` job
uploads them, and the `github-release` job creates the GitHub release with the files attached,
marked as a prerelease when the version has a label (`0.8.0rc1`).
(`twine check --strict`), and installs the wheels in a clean environment. The `publish` matrix
uploads each library in a job of its own, and the `github-release` job creates the GitHub release
with the files attached once all four are up, marked as a prerelease when the version has a label
(`0.8.0rc1`).

The upload uses [trusted publishing](https://docs.pypi.org/trusted-publishers/): no API token is
stored. Only the `publish` job has `id-token: write`, and it runs in the GitHub environment `pypi`.
`pypa/gh-action-pypi-publish` exchanges the job's OIDC token for a short-lived PyPI token and
uploads with digital attestations (PEP 740). Each of the four projects on PyPI has a trusted
publisher that names owner `easyssf`, repository `easyssf-python`, workflow `release.yml` and
environment `pypi`. Before a project's first release, it is a "pending publisher" in the PyPI
account (Your projects, Publishing). The first upload creates the project and turns it into a
regular publisher. A required reviewer on the `pypi` environment holds a release until approved.
stored. Only the `publish` jobs have `id-token: write`, each in the GitHub environment
`pypi-<distribution>` (`pypi-easyssf-core`, ...). `pypa/gh-action-pypi-publish` exchanges the job's
OIDC token for a short-lived PyPI token for that one project and uploads with digital attestations
(PEP 740). Each of the four projects on PyPI has a trusted publisher that names owner `easyssf`,
repository `easyssf-python`, workflow `release.yml` and its environment. One environment per
library is needed because PyPI accepts a pending publisher (the publisher of a project that does
not exist yet, in the PyPI account under Your projects, Publishing) only once per owner,
repository, workflow and environment, and holds at most three pending publishers at a time. The
first upload creates the project and turns its pending publisher into a regular one. A new
library therefore needs a GitHub environment and a pending publisher before its first release. A
required reviewer on the environments holds a release until approved.

A release goes through a commit like every other change:

Expand Down
Loading