From c781711402216d3f13e9fe907448d7fcf31029f5 Mon Sep 17 00:00:00 2001 From: Brad Barnett <127794626+bdbarnett@users.noreply.github.com> Date: Tue, 20 Oct 2026 23:34:21 -0500 Subject: [PATCH] Release dispatches also go to the library index (PyDevices/lib), pre-releases included; the MIP index still takes finals only --- .../reusable-publish-release-packages.yml | 6 ++- .../reusable-request-mip-publication.yml | 53 ++++++++++++++++++- docs/publishing-automation.md | 33 ++++++++++-- 3 files changed, 83 insertions(+), 9 deletions(-) diff --git a/.github/workflows/reusable-publish-release-packages.yml b/.github/workflows/reusable-publish-release-packages.yml index 8a2e7f4..930667d 100644 --- a/.github/workflows/reusable-publish-release-packages.yml +++ b/.github/workflows/reusable-publish-release-packages.yml @@ -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') @@ -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 diff --git a/.github/workflows/reusable-request-mip-publication.yml b/.github/workflows/reusable-request-mip-publication.yml index 04023b2..2235432 100644 --- a/.github/workflows/reusable-request-mip-publication.yml +++ b/.github/workflows/reusable-request-mip-publication.yml @@ -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 @@ -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 @@ -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 }} @@ -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" diff --git a/docs/publishing-automation.md b/docs/publishing-automation.md index 51dd5f9..dde1ecd 100644 --- a/docs/publishing-automation.md +++ b/docs/publishing-automation.md @@ -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" @@ -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`: @@ -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