Repository navigation
125 lines (124 loc) · 6.67 KB
/
Copy pathrelease-python.yml
File metadata and controls
125 lines (124 loc) · 6.67 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
# release-python — publish python/ to PyPI on a `python/v*` tag, via PyPI
# Trusted Publishing (OIDC): no token is stored anywhere. Configure the
# publisher once on PyPI (project `chtypes`, owner Wave-RF, repo chtypes,
# workflow release-python.yml, environment pypi).
name: release-python
on:
push:
tags: ["python/v*"]
# A dry run: every step up to the publish (tag and version agreement against the
# tag NAME given here, the build, `twine check`), and nothing after it.
workflow_dispatch:
inputs:
tag:
description: "the tag name to check the version against, e.g. python/v1.0.0"
required: true
type: string
dry_run:
description: "stop before the publish and the post-publish check"
required: true
type: boolean
default: true
verify_only:
description: "Skip the publish job and run only the verify job against the ALREADY-PUBLISHED version the tag names (registry install first, with its retry loop). Wins over dry_run."
required: false
type: boolean
default: false
permissions:
contents: read
# Every run: step uses bash with -eo pipefail. GitHub's default for a step
# with no shell: is `bash -e {0}`, WITHOUT pipefail, so a failing command on
# the left of a pipe was hidden by the exit status of the one on the right.
defaults:
run:
shell: bash
jobs:
build:
if: github.event_name != 'workflow_dispatch' || !inputs.verify_only
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
- name: the tag and the package agree on the version
env:
TAG_NAME: ${{ inputs.tag || github.ref_name }}
run: |
tag="${TAG_NAME#python/v}"
ver="$(cd python && uv run --no-project python -c 'import tomllib;print(tomllib.load(open("pyproject.toml","rb"))["project"]["version"])')"
[ "$tag" = "$ver" ] || { echo "::error::tag python/v$tag but pyproject says $ver"; exit 1; }
- name: build sdist + wheel
run: cd python && uv build
- name: the distributions pass twine check
run: uvx twine check python/dist/*
# The User-Agent assertion: install the BUILT wheel (not the source tree)
# into a clean venv and read the constant the fetch layer sends. It runs
# on the tag path too, before anything is published.
- name: the User-Agent version is the version being tagged
env:
TAG_NAME: ${{ inputs.tag || github.ref_name }}
run: |
tag="${TAG_NAME#python/v}"
venv="$RUNNER_TEMP/ua-venv"
uv venv --quiet --python 3.13 "$venv"
uv pip install --quiet --python "$venv/bin/python" python/dist/*.whl
got="$(cd "$RUNNER_TEMP" && "$venv/bin/python" -c 'from chtypes._ocifetch._http import USER_AGENT; print(USER_AGENT)')"
echo "User-Agent: $got"
[ "$got" = "chtypes-python/$tag" ] || { echo "::error::the wheel's User-Agent is '$got', the tag says chtypes-python/$tag"; exit 1; }
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with: { name: python-dist, path: python/dist/ }
publish:
needs: build
if: github.event_name == 'push' || (inputs.dry_run == false && !inputs.verify_only)
runs-on: ubuntu-latest
environment: pypi
permissions:
id-token: write
steps:
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with: { name: python-dist, path: dist/ }
- uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
# A publish step exiting 0 does not mean anyone can install the package.
# Measured on ts/v0.1.1 (issue #10): the job went green ~7 minutes before the
# registry served the artifact, and for ~2 of those minutes the version
# resolved to something that 404'd. Its own job, because `publish` downloads
# an artifact into its workspace rather than checking the repository out.
#
# The check installs the PUBLISHED package into a clean environment, with no
# credentials, the way a consumer does, and then makes it speak:
# 1. the binding's ABI fingerprint constant equals the one in include/chtypes.h
# (read from the header, never hard-coded, and no revision number anywhere);
# 2. the published CLI fetches one published release line from the production
# registry (`chtypes list`, then `chtypes fetch <line>`);
# 3. the public API opens that line and the loaded library reports the same
# fingerprint.
# The retry loop is for registry lag: an install that cannot resolve the version
# yet is retried for up to 15 minutes, and every other failure fails at once.
# The steps are scripts/release-verify.sh, shared by every path: a tag push
# verifies the registry's copy after the publish; a dry run (dry_run=true)
# runs the same steps against the package this run built, the registry
# retry loop skipped; verify_only=true skips the publish job and runs them
# against the already-published version the `tag` input names. So the parser
# of `chtypes list` and the whole sequence are exercised BEFORE a publish.
verify:
needs: [build, publish]
if: ${{ !failure() && (needs.build.result == 'success' || (needs.build.result == 'skipped' && inputs.verify_only)) && (needs.publish.result == 'success' || needs.publish.result == 'skipped') && (github.event_name == 'push' || inputs.verify_only || inputs.dry_run || needs.publish.result == 'success') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
- name: the verify script's own selftest (the list parser)
run: scripts/release-verify.sh --selftest
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
if: github.event_name == 'workflow_dispatch' && inputs.dry_run && !inputs.verify_only
with: { name: python-dist, path: "${{ runner.temp }}/python-dist/" }
- name: it installs, speaks the header's ABI fingerprint, lists, fetches a line and opens it
env:
TAG_NAME: ${{ inputs.tag || github.ref_name }}
MODE: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run && !inputs.verify_only && 'local' || 'registry' }}
run: |
version="${TAG_NAME#python/v}"
if [ "$MODE" = local ]; then
scripts/release-verify.sh python local "$version" "$(ls "$RUNNER_TEMP"/python-dist/*.whl)"
else
scripts/release-verify.sh python registry "$version"
fi