-
-
Notifications
You must be signed in to change notification settings - Fork 2
162 lines (153 loc) · 7.57 KB
/
Copy pathpython-release.yml
File metadata and controls
162 lines (153 loc) · 7.57 KB
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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
name: Manual PyPI Publish
# Mirrors release.yml's manual, button-triggered shape for the Python adapter.
# Unlike npm (NPM_TOKEN), PyPI uses trusted publishing (OIDC) — no secret.
# Bump `__version__` in src/selenium_devtools/__init__.py before running.
on:
workflow_dispatch:
inputs:
target:
description: 'Publish target'
required: true
type: choice
default: pypi
options:
- pypi
- testpypi
defaults:
run:
working-directory: packages/selenium-devtools-py
# A version uploads once, so an overlapping run fails on the winner's upload.
concurrency:
group: python-release-${{ inputs.target }}
cancel-in-progress: false
jobs:
release:
runs-on: ubuntu-latest
environment: ${{ inputs.target }}
permissions:
id-token: write # PyPI trusted publishing (OIDC) — no token/secret needed
contents: write # commits the version bump and tags the published tree
steps:
# main, and with history: the release DECIDES the version by consuming
# `changes/`, then commits and tags the result. A tag cannot be the input
# to that, because it would have to name a version nothing has computed
# yet — so `py-v<version>` is an output, pointing at exactly the tree that
# was published.
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: 'main'
fetch-depth: 0
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: '3.12'
# Consumes changes/ — bumps `__version__` by the strongest level pending
# and writes the changelog section. On testpypi it stays in the working
# tree so a dry run builds the real next artifact without spending the
# fragments; only a pypi release commits it below.
- name: Apply pending change fragments
run: python scripts/changes.py apply
# Before the build, because the alternative is finding out from twine's
# 400 once the checks have already run. Also what refuses a release with
# no fragments and nothing new to say: the version would be unchanged and
# therefore already published.
- name: Version is not already on the index
env:
INDEX_HOST: ${{ inputs.target == 'testpypi' && 'test.pypi.org' || 'pypi.org' }}
run: |
VERSION=$(sed -n 's/^__version__ = "\(.*\)"$/\1/p' src/selenium_devtools/__init__.py)
if [ -z "$VERSION" ]; then
echo "::error::could not read __version__ from src/selenium_devtools/__init__.py"
exit 1
fi
# Only a 404 is evidence the version is free. Treating "not 200" as
# free makes a 5xx or a DNS failure read as availability, which is the
# opposite of what this step is for: the preflight would pass on no
# information and the duplicate would surface as an upload error.
CODE=$(curl -sS --retry 3 --retry-delay 2 --retry-all-errors \
-o /dev/null -w '%{http_code}' \
"https://$INDEX_HOST/pypi/selenium-devtools-py/$VERSION/json" || echo 000)
case "$CODE" in
404) echo "$VERSION is free on $INDEX_HOST" ;;
200)
echo "::error::selenium-devtools-py $VERSION is already on $INDEX_HOST — a version uploads once, so add a change fragment or bump __version__"
exit 1 ;;
*)
echo "::error::$INDEX_HOST answered HTTP $CODE; cannot establish whether $VERSION is free"
exit 1 ;;
esac
- uses: ./.github/actions/python-package
# The pinned backend is the one a `pip install` user runs, and an older
# one serves none of the adapter's routes and scopes while leaving the run
# green, so this refuses the publish rather than the merge.
- name: Pinned backend serves this adapter's contract
run: python scripts/check_backend_pin.py
- name: 🚀 Publish to PyPI
if: ${{ inputs.target == 'pypi' }}
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
with:
packages-dir: packages/selenium-devtools-py/dist
- name: 🚀 Publish to TestPyPI
if: ${{ inputs.target == 'testpypi' }}
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
with:
packages-dir: packages/selenium-devtools-py/dist
repository-url: https://test.pypi.org/legacy/
# After the publish, so main never claims a release that did not happen.
# A dry run reaches none of this: its bump stays in the runner's tree and
# the fragments live on for the real release to spend.
- name: Record the release
if: ${{ inputs.target == 'pypi' }}
run: |
VERSION=$(sed -n 's/^__version__ = "\(.*\)"$/\1/p' src/selenium_devtools/__init__.py)
git config user.email "bot@webdriver.io"
git config user.name "WebdriverIO Release Bot"
git add src/selenium_devtools/__init__.py CHANGELOG.md changes/
# The tag hangs off the PUBLISH, not off whether files changed. A
# first release has no fragments to consume, so nothing is staged and
# the older form returned here — publishing a version that then
# carried no tag, which is the one case the tag exists to cover.
committed=false
if git diff --cached --quiet; then
echo "nothing to commit; tagging the published tree"
else
git commit -m "chore(selenium-devtools-py): release $VERSION"
committed=true
fi
# THE tree that was built and uploaded, captured before the rebase
# below can exist. Rebasing replays the release commit onto whatever
# landed meanwhile, so its tree then carries source the artifact does
# not — and a tag on that commit would name a release nobody can
# reproduce. Tagged by sha, and tagged first: if main never accepts
# the commit, this is still the only honest record of what shipped.
release_sha=$(git rev-parse HEAD)
if git rev-parse -q --verify "refs/tags/py-v$VERSION" >/dev/null; then
echo "py-v$VERSION already exists"
else
git tag "py-v$VERSION" "$release_sha"
git push origin "py-v$VERSION"
fi
if [ "$committed" = true ]; then
# PyPI has already accepted the upload, so failing to land this
# leaves the bump off main and a rerun refused by the index
# preflight. Concurrency only serialises this workflow, and anyone
# may push to main meanwhile — so rebase onto whatever arrived and
# try again rather than giving up on the first reject.
pushed=false
for attempt in 1 2 3 4 5; do
if git push origin HEAD:main; then
pushed=true
break
fi
echo "main moved; rebasing onto it (attempt $attempt)"
git fetch origin main
git rebase origin/main || {
git rebase --abort || true
echo "::error::could not rebase the release commit onto main"
break
}
done
if [ "$pushed" != true ]; then
echo "::error::selenium-devtools-py $VERSION is PUBLISHED and tagged py-v$VERSION ($release_sha), but that commit is not on main. Land its __version__, CHANGELOG.md and changes/ deletions by hand before the next release."
exit 1
fi
fi