1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
name: Release the Python bindings
# Triggered by a completed test run, not by a push, so a broken build can never
# be published. `workflows:` must match the `name:` of python-test.yml.
#
# The tag prefix is `py-v`, not `v`: release.yml already owns `v$version` for
# the crate. The two versions are independent lines, so a Python patch release
# and a crate release never collide and never imply each other. The wheel
# reports which crate it vendored as `evoc_rs.__core_version__`.
on:
workflow_run:
workflows:
branches:
types:
workflow_dispatch:
inputs:
publish:
description: Upload the wheels. Off by default, so a dispatch only builds.
type: boolean
default: false
index:
description: Which index to upload to.
type: choice
options:
- testpypi
- pypi
default: testpypi
tag:
description: Create the py-v tag and the GitHub release.
type: boolean
default: false
permissions:
contents: write
# PyPI trusted publishing. A called workflow's permissions are capped by the
# caller's token, so leaving this out makes the publish 403.
id-token: write
jobs:
# Version check, tag, GitHub release and the wheel matrix. Leaves the built
# wheels as `dist-*` artefacts.
build:
uses: GregorLueg/personal-actions/.github/workflows/python-maturin-release.yml@v1
with:
# Always true, and it does not mean "upload". It only decides whether the
# version is checked against the index before anything is built, so an
# already-published version costs one cheap job instead of three wheel
# builds. Whether an upload happens is the `publish` job's `if:` below.
publish: true
# ARMED. A dispatch still takes the form's value, so a rehearsal can opt
# out; anything else tags, which is what a version bump on main should do.
tag: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || true }}
# A choice rather than a free-text URL: there are only ever two answers
# here, and a typo costs the whole wheel matrix before it is noticed.
repository-url: ${{ (github.event_name == 'workflow_dispatch' && inputs.index == 'testpypi') && 'https://test.pypi.org/legacy/' || '' }}
# The sdist would carry a `path` dependency on the parent crate, which is
# not known to round-trip. Wheels only until `maturin sdist` plus a clean
# install has been shown to work.
sdist: false
# The upload lives here rather than in the reusable workflow, and has to.
# PyPI's trusted publishing matches the OIDC token's `job_workflow_ref`, which
# for a reusable workflow points at the *other* repository, and the publisher
# form cannot express that (pypi/warehouse#11096). Running it here makes
# `job_workflow_ref` this file, which is what the PyPI publisher names.
#
# So the trusted publisher on PyPI must be configured as:
# owner GregorLueg, repo evoc-rs, workflow python-release.yml,
# environment pypi
publish:
name: Publish to PyPI
needs: build
# ARMED. `should-build` is false when the version is already on PyPI, so an
# ordinary push to main reaches here, finds nothing to do and stops. A
# version bump is what makes this fire. The `pypi` environment still holds
# it for a review before the upload, which is the last reversible moment.
if: >-
needs.build.outputs.should-build == 'true' &&
(github.event_name == 'workflow_dispatch' && inputs.publish
|| github.event_name != 'workflow_dispatch')
runs-on: ubuntu-latest
timeout-minutes: 20
environment: pypi
permissions:
id-token: write
steps:
- uses: actions/download-artifact@v4
with:
pattern: dist-*
merge-multiple: true
path: dist
- name: List what is about to be uploaded
run: ls -la dist
- uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: dist
repository-url: ${{ inputs.index == 'testpypi' && 'https://test.pypi.org/legacy/' || 'https://upload.pypi.org/legacy/' }}
print-hash: true