Skip to content

Commit 60c5c38

Browse files
committed
docs, skill: document the YAML-declared version workflow
Signed-off-by: Ludovic Henry <git@ludovic.dev>
1 parent 806be48 commit 60c5c38

9 files changed

Lines changed: 333 additions & 164 deletions

File tree

‎docs/development.md‎

Lines changed: 126 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,55 @@ The general process:
3535
2. Create a copy of the upstream build workflow in the `python-wheels` repo at
3636
`.github/workflows/build-<package>.yml`, where `<package>` matches the
3737
project name (e.g. `build-numpy.yml` for NumPy`).
38-
3. Remove any workflow logic not related to the Linux glibc and musl (if
38+
3. Create `docs/packages/<package>.yaml` declaring the version to build (see
39+
[Declaring Versions](#declaring-versions)).
40+
4. Remove any workflow logic not related to the Linux glibc and musl (if
3941
present) build processes, support for other architectures and operating
4042
systems (e.g. Windows, Mac OS). This includes the sdist build (unless it is
4143
consumed by a build or test step).
42-
4. Repeat steps #2 and #3 for the corresponding test workflow, if it is separate
44+
5. Repeat steps #2 and #4 for the corresponding test workflow, if it is separate
4345
from the upstream build file.
4446

4547
From this point, some customizations are required to enable builds targeting
4648
riscv64.
4749

50+
## Declaring Versions
51+
52+
`docs/packages/<package>.yaml` is the source of truth for what a workflow
53+
builds. A brand-new package starts with:
54+
55+
```yaml
56+
package-name: <package>
57+
source-code: <repository URL>
58+
license: <SPDX identifier>
59+
versions:
60+
- version: <version>
61+
```
62+
63+
`version` is the version the built wheel will carry (its `METADATA` version,
64+
not necessarily the git tag: the workflow derives the tag from it). An entry
65+
with no `tag:` and no `files:` is **pending**: it has not been released yet.
66+
`_setup.yml` reads the YAML at the start of every run and hands the list of
67+
pending versions to the build jobs as a matrix, so:
68+
69+
- a `pull_request` run builds every pending version and dry-runs the publish;
70+
- a `push` to `main` builds every pending version and publishes it;
71+
- a `workflow_dispatch` with a `version` glob (e.g. `2.5.*`) rebuilds every
72+
matching version, released or not, and republishes it from `main`;
73+
- a run with nothing pending (typically the push that lands `tag:`/`files:`)
74+
skips every job and stays green.
75+
76+
Publishing fills in `tag:`/`files:` under that entry through the documentation
77+
PR (`ci_scripts/update_doc.py`), which retires it from the pending set. The
78+
wheel's version must match a declared entry byte for byte, or the publish
79+
fails: a `setuptools_scm` dev version (`1.0.1.dev0+g...`) where `1.0.0` was
80+
declared is a bug in the build, not something to document.
81+
82+
To **upgrade** an existing package, append a `- version: <new version>` line to
83+
its YAML and open a PR; no workflow change is needed unless the new version
84+
needs one. The nightly `check_versions.py` opens exactly that PR
85+
automatically.
86+
4887
## Workflow Customizations for riscv64
4988

5089
### riscv64 Runners
@@ -129,26 +168,81 @@ when invoked.
129168
### Using the python-wheels Repository in Workflows
130169

131170
The `python-wheels` repository contains reusable workflows and patch files to
132-
apply for certain projects. Every `build-<package>.yml` workflow calls
133-
`_publish-wheel.yml`, which performs the following steps:
171+
apply for certain projects. Every `build-<package>.yml` workflow starts with a
172+
`setup` job that calls `_setup.yml` to turn the package YAML into the version
173+
matrix, and every other job iterates over that matrix:
174+
175+
```
176+
on:
177+
workflow_dispatch:
178+
inputs:
179+
version:
180+
description: 'Version glob to (re)build; empty builds every version of docs/packages/numpy.yaml not released yet'
181+
required: false
182+
default: ''
183+
pull_request:
184+
branches: [main]
185+
paths:
186+
- '.github/workflows/build-numpy.yml'
187+
- 'docs/packages/numpy.yaml'
188+
push:
189+
branches: [main]
190+
paths:
191+
- '.github/workflows/build-numpy.yml'
192+
- 'docs/packages/numpy.yaml'
193+
194+
jobs:
195+
setup:
196+
uses: $/.github/workflows/_setup.yml
197+
with:
198+
package: numpy
199+
version: ${{ inputs.version }}
200+
201+
build_wheels:
202+
needs: [setup]
203+
if: needs.setup.outputs.versions != '[]'
204+
name: Build numpy ${{ matrix.version }} ${{ matrix.python }}-manylinux_riscv64
205+
strategy:
206+
fail-fast: false
207+
matrix:
208+
version: ${{ fromJSON(needs.setup.outputs.versions) }}
209+
python: ["cp312", "cp313", "cp314", "cp314t"]
210+
env:
211+
NUMPY_VERSION: ${{ matrix.version }}
212+
...
213+
```
214+
215+
The `if:` guard is required: GitHub Actions rejects a job whose matrix vector is
216+
empty, and `versions` is `[]` whenever nothing is pending. Job outputs are not
217+
per matrix leg, so a job must not hand its version to another through
218+
`outputs:`; use `matrix.version` (or an artifact named after it) instead.
219+
220+
Every `build-<package>.yml` workflow ends with a call to `_publish-wheel.yml`,
221+
which performs the following steps:
134222

135223
1. Downloads the built wheel(s) from the previous job
136224
2. Creates an immutable GitHub Release containing the wheels
137-
3. Opens a PR against `docs/packages/<name>.yaml` documenting the new version
138-
(via `ci_scripts/update_doc.py`), which `ci_scripts/generate_packages_doc.py`
139-
later renders into the published Markdown page. With it in place, the
140-
`build-numpy.yml` script's `publish` job looks like this:
225+
3. Opens a PR against `docs/packages/<name>.yaml` recording the release under
226+
the version's entry (via `ci_scripts/update_doc.py`), which
227+
`ci_scripts/generate_packages_doc.py` later renders into the published
228+
Markdown page. With it in place, the `build-numpy.yml` script's `publish`
229+
job looks like this:
141230

142231
```
143232
publish:
144-
name: Publish numpy ${{ inputs.version || '2.5.0' }}
145-
needs: build_wheels
233+
name: Publish numpy ${{ matrix.version }}
234+
needs: [setup, build_wheels]
235+
if: needs.setup.outputs.versions != '[]'
236+
strategy:
237+
fail-fast: false
238+
matrix:
239+
version: ${{ fromJSON(needs.setup.outputs.versions) }}
146240
permissions:
147241
contents: write
148242
pull-requests: write
149243
uses: $/.github/workflows/_publish-wheel.yml
150244
with:
151-
artifact-pattern: numpy-${{ inputs.version || '2.5.0' }}-*-manylinux_riscv64
245+
artifact-pattern: numpy-${{ matrix.version }}-*-manylinux_riscv64
152246
```
153247
154248
`permissions` needs `contents: write` and `pull-requests: write` here (not just
@@ -161,15 +255,16 @@ the example.
161255
162256
## Testing a New Workflow
163257
164-
Open a new draft PR with the workflow(s) included, and include a `Trigger:` line
165-
in the PR description with a version for each package version you want to build,
166-
like so:
258+
Open a new draft PR with the workflow and the package YAML. The `pull_request`
259+
trigger runs the workflow, which builds every version the YAML declares as
260+
pending; to build additional versions, add more `- version:` lines. To force a
261+
rebuild of an already released version, dispatch the workflow on the PR branch
262+
with a version glob:
167263
168-
`Trigger: numpy:v2.5.0`
169-
`Trigger: numpy:v2.5.1`
264+
`gh workflow run build-numpy.yml --ref <branch> -f version='2.5.*'`
170265
171-
The repository's automation logic will pick up on and trigger the appropriate
172-
build workflows for each version. Achieving a passing (green) build may require
266+
(A brand-new workflow can only be dispatched once its first `pull_request` run
267+
has registered it with GitHub.) Achieving a passing (green) build may require
173268
several attempts including rework and possible patches, depending on the nature
174269
of the failure.
175270
@@ -222,22 +317,29 @@ in dry-run mode. Review the publish job and confirm that it selected exactly the
222317
expected wheels and derived the expected normalized package name and version.
223318
Pull request runs never create releases or documentation pull requests.
224319
225-
Merging the pull request does not publish its artifacts. After merge, a
226-
maintainer must dispatch the package workflow again from the `main` branch with
227-
the reviewed version. The successful `main` run:
320+
Merging the pull request pushes the pending version to `main`, which triggers
321+
the package workflow again through its `push` trigger. The successful `main`
322+
run:
228323
229324
1. creates a draft GitHub Release targeting the workflow's commit;
230325
2. names the release `<normalized-package>-v<version>` and gives its tag a UTC
231326
timestamp suffix, `<normalized-package>-v<version>-<yyyymmddhhmmss>`;
232327
3. uploads all selected wheels and any required `gpl-sources.tar` asset;
233328
4. publishes the release, at which point repository-level immutable releases
234329
freeze its tag and assets; and
235-
5. opens or updates the shared documentation pull request with the release tag,
236-
wheel filenames, SHA-256 hashes, and `Requires-Python` metadata.
330+
5. opens or updates the shared documentation pull request, filling in the
331+
version's entry with the release tag, wheel filenames, SHA-256 hashes, and
332+
`Requires-Python` metadata.
237333
238334
Every `main` run creates a new release; existing releases are never reused or
239335
modified. Its description links back to the workflow run that produced it.
240-
Publishing fails if immutable releases are disabled.
336+
Publishing fails if immutable releases are disabled, or if the wheel's version
337+
is not declared in the package YAML.
338+
339+
Merging the documentation pull request pushes the now-released entry to `main`
340+
and triggers the workflow once more; with nothing left pending, that run skips
341+
every job. Until then the version is still pending, so any other push touching
342+
the workflow or its YAML rebuilds and re-releases it.
241343
242344
The generated `/simple/` package index reads only `docs/packages/*.yaml`. The
243345
new release does not become discoverable through pip until the generated

‎docs/workflows.md‎

Lines changed: 35 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,20 @@ considered sufficient without user review. For best practices, reference
1717
1. Workflows should be designed to mirror the upstream equivalents' linux +
1818
glibc build/test processes, deviating only where necessary and with
1919
documentation as to why.
20-
2. **A brand-new `build-<package>.yml` must keep both the `workflow_dispatch`
21-
and `pull_request: paths` triggers.** GitHub only lets you dispatch a
22-
workflow that has already produced at least one run on the repository; a
23-
file that exists solely on a PR branch is not yet registered, so
24-
`workflow_dispatch` and a `Trigger:` line (see below) both fail with
25-
`HTTP 404`. The `pull_request: paths` run from opening the PR is what
26-
registers it. Never drop this trigger to "avoid a duplicate CI run" it's
27-
the only way a new package's build ever starts.
20+
2. **A brand-new package is two files: `build-<package>.yml` and
21+
`docs/packages/<package>.yaml`.** The YAML declares the versions to build
22+
(see [Declaring Versions](development#declaring-versions)); the workflow
23+
must keep all three triggers, `workflow_dispatch`, `pull_request: paths`
24+
and `push: paths`, each pointing at both files. GitHub only lets you
25+
dispatch a workflow that has already produced at least one run on the
26+
repository; a file that exists solely on a PR branch is not yet registered,
27+
so a `workflow_dispatch` fails with `HTTP 404`. The `pull_request: paths`
28+
run from opening the PR is what registers it, and builds the pending
29+
versions. Never drop this trigger to "avoid a duplicate CI run" it's the
30+
only way a new package's build ever starts.
2831
3. The `_publish-wheel.yml` reusable workflow must be used in place of any
2932
upstream deployment procedure. It performs a dry run on pull requests and
30-
publishes immutable GitHub Releases when dispatched from `main`. In
33+
publishes immutable GitHub Releases when run from `main`. In
3134
particular, the contributor **must** check that the correct number of wheels
3235
are generated and selected for upload.
3336
4. Built wheels need to be inspected for license compliance, both to ensure that
@@ -37,21 +40,21 @@ considered sufficient without user review. For best practices, reference
3740
the build environment's `gcc`) end up linked into a wheel, the
3841
`collect-gpl-sources` action must be used to publish those sources
3942
permanently alongside the release.
40-
5. After the contribution is merged, a maintainer must dispatch the workflow
41-
again from `main`. That run creates a new immutable GitHub Release and opens
42-
or updates the shared documentation PR. The release becomes visible through
43-
the RISE Simple API only after that documentation PR is reviewed, merged,
44-
and deployed. The maintainer also opens (or reuses) a tracking issue for the
45-
package and links it to the PR.
43+
5. Merging the contribution pushes the pending version to `main`, which runs
44+
the workflow again through its `push` trigger. That run creates a new
45+
immutable GitHub Release and opens or updates the shared documentation PR.
46+
The release becomes visible through the RISE Simple API only after that
47+
documentation PR is reviewed, merged, and deployed. The maintainer also
48+
opens (or reuses) a tracking issue for the package and links it to the PR.
4649

4750
## Reviewing a Documentation PR
4851

4952
Documentation updates are generated as part of the `_publish-wheel.yml`
5053
workflow, which runs `ci_scripts/update_doc.py` against every newly built wheel.
51-
Each version's YAML entry records immutable release tags and, for every wheel,
52-
the filename, SHA-256 hash, and optional `Requires-Python` value. These YAML
53-
files are the source of truth for both the package documentation and the static
54-
Simple API. This is separate from `.github/workflows/website.yml`, which builds
54+
It fills in the version's declared entry with the immutable release tag and,
55+
for every wheel, the filename, SHA-256 hash, and optional `Requires-Python`
56+
value. These YAML files are the source of truth for both the package
57+
documentation and the static Simple API. This is separate from `.github/workflows/website.yml`, which builds
5558
and deploys the site, including documentation previews. The generated
5659
pull requests need to be checked against the following criteria:
5760

@@ -67,11 +70,12 @@ pull requests need to be checked against the following criteria:
6770
license text in the documentation file. This should be replaced with the
6871
actual license type. For valid license strings, review the [SPDX
6972
list](https://spdx.org/licenses/).
70-
3. Each version entry must contain the latest release tag and list every
71-
expected wheel with the correct filename, SHA-256 hash, and
72-
`Requires-Python` value. If the package linked against one or more
73-
GPL-licensed projects, the same version entry must also record its
74-
`gpl-sources.tar` asset.
73+
3. Each released version entry must contain the latest release tag and list
74+
every expected wheel with the correct filename, SHA-256 hash, and
75+
`Requires-Python` value. An entry with neither `tag` nor `files` is still
76+
pending and will be built again by the next push touching the package. If
77+
the package linked against one or more GPL-licensed projects, the same
78+
version entry must also record its `gpl-sources.tar` asset.
7579
4. `patched: true` should be added if one or more patches are applied to the
7680
source for each version. If omitted, it defaults to `false`. This will
7781
produce a link to the appropriate patches
@@ -99,16 +103,13 @@ following workflows beyond those used for specific package builds:
99103
MERGE' tags are included in the subject lines, since commits of this type may
100104
be used for testing but should never be valid for merge. It also ensures that
101105
a valid `Upstream-Status` tag is included in every custom patch we include
102-
when deviating from upstream packages' workflow/source checkouts.
103-
4. `pr-trigger.yml`: Provides a supplemental trigger mechanism for contributors,
104-
allowing additional workflow runs for one or more packages to be triggered
105-
with a PR by specifying the `Trigger: <package>:<version>` directive in the
106-
PR description. This is only necessary if the default version in a workflow
107-
is not the only one to be built, or if other maintenance-style changes are
108-
being made where the contributors would like to validate behaviour. Note
109-
this only works for workflows already registered on GitHub (i.e. already
110-
merged to `main`, or having produced at least one `pull_request` run — see
111-
above).
106+
when deviating from upstream packages' workflow/source checkouts, and that
107+
every changed `docs/packages/*.yaml` is well-formed and every changed
108+
build workflow has one.
109+
4. `_setup.yml`: Called first by every `build-<package>.yml`; reads
110+
`docs/packages/<package>.yaml` and outputs the versions to build (the
111+
pending ones, or those matching the `version` glob of a
112+
`workflow_dispatch`).
112113

113114
## The RISC-V Wheels Dashboard
114115

0 commit comments

Comments
 (0)