Skip to content

Finalize latest prerelease #185

Finalize latest prerelease

Finalize latest prerelease #185

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."