Repository navigation
Finalize latest prerelease #185
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Finalize latest prerelease | |
| # Recreates the rolling "latest" prerelease after the per-platform CI builds finish on | |
| # master. The platform workflows only upload build artifacts; this job gathers them and | |
| # (re)creates the "latest" release in one place. | |
| # | |
| # Why recreate instead of editing the existing release: | |
| # * The Releases page sorts by created_at, which cannot be PATCHed. Deleting and | |
| # recreating resets created_at so "latest" returns to the top of the list. | |
| # * It moves the "latest" git tag to the current commit (plain `gh release edit | |
| # --target` does not move an existing tag). | |
| # * It keeps only the current commit's assets, so stale tarballs left in cached build | |
| # directories never accumulate. | |
| # This mirrors what scikit-ci-addons' publish_github_release used to do for us. | |
| # | |
| # A delete+recreate must not run inside the five parallel build jobs (linux, linux-arm64, | |
| # mac-arm64, mac-x86_64, win) or they would wipe each other's assets, so it lives here as | |
| # a single coordinator that runs after the builds. GITHUB_TOKEN is used on purpose: | |
| # release/tag events it creates do not trigger other workflows, so there is no build loop. | |
| on: | |
| workflow_run: | |
| workflows: | |
| - "CI Linux" | |
| - "CI macOS" | |
| - "CI Windows" | |
| types: | |
| - completed | |
| # Serialize per commit so the three build completions don't recreate the release at once. | |
| concurrency: | |
| group: finalize-latest-${{ github.event.workflow_run.head_sha }} | |
| cancel-in-progress: false | |
| jobs: | |
| finalize: | |
| name: Recreate latest prerelease | |
| # Only for successful master-push builds in the QIICR repo. Tagged (stable) releases | |
| # are published by the build workflows themselves and must not be touched here. | |
| if: >- | |
| github.repository_owner == 'QIICR' && | |
| github.event.workflow_run.event == 'push' && | |
| github.event.workflow_run.head_branch == 'master' && | |
| github.event.workflow_run.conclusion == 'success' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write # manage the "latest" release and its tag | |
| actions: read # list sibling build runs and download their artifacts | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_REPO: ${{ github.repository }} | |
| SHA: ${{ github.event.workflow_run.head_sha }} | |
| # Workflow files whose artifacts together make up a complete "latest" release. | |
| BUILD_WORKFLOWS: "cmake-linux.yml cmake-macos.yml cmake-win.yml" | |
| steps: | |
| - name: Collect platform packages for this commit | |
| id: collect | |
| run: | | |
| set -euo pipefail | |
| mkdir -p dist | |
| # This workflow fires once per build-workflow completion. Only proceed once all | |
| # three have a successful run for this commit; earlier firings defer. | |
| for wf in $BUILD_WORKFLOWS; do | |
| run_id=$(gh run list --workflow "$wf" --branch master --event push --limit 50 \ | |
| --json databaseId,headSha,conclusion \ | |
| --jq "[.[] | select(.headSha == \"$SHA\" and .conclusion == \"success\")][0].databaseId") | |
| if [ -z "$run_id" ] || [ "$run_id" = "null" ]; then | |
| echo "::notice::$wf has no successful run for $SHA yet; deferring to a later build completion." | |
| echo "ready=false" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| echo "Downloading artifacts from $wf run $run_id" | |
| gh run download "$run_id" --dir dist | |
| done | |
| echo "ready=true" >> "$GITHUB_OUTPUT" | |
| - name: Recreate the latest prerelease | |
| if: steps.collect.outputs.ready == 'true' | |
| run: | | |
| set -euo pipefail | |
| SHORT=${SHA:0:7} | |
| # Keep only packages built from this exact commit. The date+sha suffix comes from | |
| # cmake/dcmqiVersion.cmake (rev-parse --short=7), e.g. dcmqi-1.5.4-linux-20260618-b4b9fe5.tar.gz. | |
| ASSETS=() | |
| while IFS= read -r f; do | |
| ASSETS+=("$f") | |
| done < <(find dist -type f \( -name "*-$SHORT.tar.gz" -o -name "*-$SHORT.zip" \) | sort) | |
| if [ "${#ASSETS[@]}" -eq 0 ]; then | |
| echo "::error::No packages matching commit $SHORT were found under dist/." | |
| find dist -type f || true | |
| exit 1 | |
| fi | |
| echo "Publishing ${#ASSETS[@]} asset(s) for $SHORT:" | |
| printf ' %s\n' "${ASSETS[@]}" | |
| NOW=$(date -u '+%Y-%m-%d %H:%M UTC') | |
| # Move the rolling "latest" tag to this commit, then replace the release. | |
| # | |
| # The tag is moved via the git refs API (PATCH, or POST the first time it is | |
| # created) rather than `gh release delete --cleanup-tag`, which deletes the ref and | |
| # immediately recreates the same one; that delete/recreate race intermittently | |
| # returns "HTTP 403: Resource not accessible by integration" on create. | |
| # Reusing the "latest" tag keeps the .../releases/download/latest/... URLs stable, | |
| # and the release's created_at follows the target commit's date, so it sorts to the top. | |
| gh api -X PATCH "repos/$GH_REPO/git/refs/tags/latest" -f sha="$SHA" -F force=true >/dev/null 2>&1 \ | |
| || gh api -X POST "repos/$GH_REPO/git/refs" -f ref="refs/tags/latest" -f sha="$SHA" >/dev/null | |
| # Delete only the release (keep the tag we just moved), then recreate it at this commit. | |
| gh release delete latest --yes || true | |
| gh release create latest "${ASSETS[@]}" \ | |
| --prerelease \ | |
| --target "$SHA" \ | |
| --title "Latest (updated on $NOW)" \ | |
| --notes "Continuous build from commit \`$SHORT\` on $NOW." |