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
6 changes: 4 additions & 2 deletions .github/workflows/reusable-publish-release-packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -284,11 +284,12 @@ jobs:
-F 'client_payload[run_url]='"$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID"

request-mip-publication:
# Final releases only: a .devN/rcN must never reach the board index.
# Every release asks for publication. The request itself keeps a
# .devN/rcN away from the mip index, which only takes finals; the library
# index takes them all.
if: >-
always()
&& inputs.mip-profile != ''
&& needs.prepare-release.outputs.prerelease == 'false'
&& needs.prepare-release.result == 'success'
&& !contains(needs.*.result, 'failure')
&& !contains(needs.*.result, 'cancelled')
Expand All @@ -302,6 +303,7 @@ jobs:
release-ref: ${{ needs.prepare-release.outputs.release-ref }}
version: ${{ needs.prepare-release.outputs.version }}
profile: ${{ inputs.mip-profile }}
prerelease: ${{ needs.prepare-release.outputs.prerelease == 'true' }}
# The MIP request mints its own App token; it needs the App secrets, which
# inherit passes through from the calling repository.
secrets: inherit
53 changes: 51 additions & 2 deletions .github/workflows/reusable-request-mip-publication.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,20 @@ on:
them for a repository that publishes more than one package.
required: true
type: string
prerelease:
description: >-
The release is an aN/bN/rcN/.devN. The mip index (PyDevices.github.io/mip)
only ever takes finals, because mip installs its latest.json; the
library index (PyDevices.github.io/lib) takes every release.
default: false
type: boolean
legacy-index:
description: >-
Also request the mip index (finals only). A release that mip's
profiles don't know, such as a palettes or pdwidgets release from
the pydevices repository, sets this false.
default: true
type: boolean

permissions:
contents: read
Expand All @@ -25,14 +39,16 @@ jobs:
request-serialized-mip-publication:
runs-on: ubuntu-latest
steps:
- name: Mint an installation token for mip
- name: Mint an installation token for the two indexes
id: app-token
uses: actions/create-github-app-token@v3
with:
app-id: ${{ secrets.PYDEVICES_APP_ID }}
private-key: ${{ secrets.PYDEVICES_APP_PRIVATE_KEY }}
owner: PyDevices
repositories: mip
repositories: |
mip
lib

# One dispatch per profile. A repository with a single package sends one,
# exactly as before; audiodsp sends two. They are not raced: mip's
Expand All @@ -41,6 +57,7 @@ jobs:
# cancel-in-progress false, so the second waits for the first and each
# run re-syncs every locked source anyway.
- name: Dispatch the exact release to the MIP queue
if: ${{ inputs.legacy-index && !inputs.prerelease }}
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
SOURCE_REPOSITORY: ${{ github.repository }}
Expand All @@ -66,3 +83,35 @@ jobs:
exit 1
fi
echo "dispatched $dispatched profile(s)"

# The library index takes every release, pre-releases included, and
# queues in its own group (omnipython-lib-publication-queue in
# PyDevices/lib). It runs whether or not the MIP request above worked:
# the two are built independently.
- name: Dispatch the exact release to the library index
if: ${{ !cancelled() }}
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
SOURCE_REPOSITORY: ${{ github.repository }}
SOURCE_REF: ${{ inputs.release-ref }}
RELEASE_VERSION: ${{ inputs.version }}
PUBLICATION_PROFILES: ${{ inputs.profile }}
run: |
set -euo pipefail
dispatched=0
while IFS= read -r profile; do
[[ -n "$profile" ]] || continue
echo "dispatching $profile @ $SOURCE_REF to PyDevices/lib"
gh api --method POST repos/PyDevices/lib/dispatches \
-f event_type=publish-package-release \
-f "client_payload[source_repository]=$SOURCE_REPOSITORY" \
-f "client_payload[source_ref]=$SOURCE_REF" \
-f "client_payload[version]=$RELEASE_VERSION" \
-f "client_payload[profile]=$profile"
dispatched=$((dispatched + 1))
done < <(echo "$PUBLICATION_PROFILES" | tr ',' '\n' | sed 's/[[:space:]]//g' | grep -v '^$' || true)
if [[ "$dispatched" == 0 ]]; then
echo "no profile in '$PUBLICATION_PROFILES'" >&2
exit 1
fi
echo "dispatched $dispatched profile(s) to the library index"
33 changes: 28 additions & 5 deletions docs/publishing-automation.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ Prepare release (workflow_dispatch, optional version)
-> merge to main
-> Tag release creates the vX.Y.Z tag and GitHub Release (via the App)
-> Publish release packages: build -> TestPyPI -> assets attached
-> MIP (final releases only) -> health report
-> MIP index (final releases only) and library index (every release) -> health report
```

### 1. Dispatch "Prepare release"
Expand Down Expand Up @@ -473,10 +473,15 @@ race — the second waits for the first to finish.
`reusable-request-mip-publication.yml` (called from a publishing repository
with `secrets: inherit`) mints an App token scoped to `mip` and dispatches
one `repository_dispatch` per profile in the (possibly comma-separated)
`mip-profile` input — `audiocomponents` sends two in the same release. This only
happens for **final releases**: `request-mip-publication`'s `if:` requires
`prerelease == 'false'`, so a `.devN`/`rcN` build never reaches the MIP
index, only TestPyPI.
`mip-profile` input — `audiocomponents` sends two in the same release. That
dispatch happens for **final releases only**: the request's `prerelease` input
is true for a `.devN`/`rcN`/`aN`/`bN`, and then the MIP leg is skipped, so a
pre-release never reaches the MIP index (mip installs its `latest.json`).
Since `publishing-v15` the same request also dispatches each profile to the
library index, `PyDevices/lib`, **for every release, pre-releases included**
(see [The library index](#the-library-index)). A repository whose profile the
MIP lockfile doesn't know (palettes and pdwidgets, now folders of `pydevices`)
sets `legacy-index: false` and sends only the library leg.

The queue consumer, `reusable-synchronize-mip-package.yml`:

Expand Down Expand Up @@ -518,6 +523,24 @@ The queue consumer, `reusable-synchronize-mip-package.yml`:
`deploy-complete-mip-index` job in the caller workflow deploys it via
`actions/deploy-pages@v4`.

### The library index

`PyDevices/lib` serves the library index (`https://pydevices.github.io/lib`,
`lib.omnipython.org` once that domain points at it) and is built beside the MIP
index, not from it. Its `publish.yml` takes the same `publish-package-release`
dispatch, queues in the `omnipython-lib-publication-queue` group (no
cancel-in-progress), records the release's profile and tag in `pins.json`, and
rebuilds every package from the pins with `tools/libbuild.py` from the
`pydevices` checkout: `pydevices`, `pydevices-desktop`, `bledev`, `midiports`,
the `usb-*` packages, every board config and driver, `appkit`, `palettes` and
`pdwidgets` from the `pydevices` tag (a `palettes-vX.Y.Z` or `pdwidgets-vX.Y.Z`
tag replaces just that folder), plus `pygraphics`, `audioinstruments` and
`audioeffects`. It deploys with `actions/deploy-pages`, then commits `pins.json`
so the file says what is live. A profile is refused if its repository isn't the
one `pins.json` names for it. To rebuild without a release, run `publish.yml`
by hand with the inputs empty; to try a library commit before it is released,
give `test-root-ref`, which deploys the build and records nothing.

### Manual dispatch

`process-mip-publication-request.yml` also accepts `workflow_dispatch` for
Expand Down
Loading