From 43e5d9f257ab27149bf3b7fded62e47af4edd6c7 Mon Sep 17 00:00:00 2001 From: Daniel Bevenius Date: Tue, 18 Aug 2026 12:19:14 +0200 Subject: [PATCH] ci : update release workflow to be consistent with llama.cpp work in progress... --- .github/workflows/make-release.yml | 87 ++++++++++++++++++++ .github/workflows/release.yml | 39 +++++---- scripts/make-release-checks.sh | 125 +++++++++++++++++++++++++++++ scripts/make-release-desc.sh | 87 ++++++++++++++++++++ 4 files changed, 321 insertions(+), 17 deletions(-) create mode 100644 .github/workflows/make-release.yml create mode 100755 scripts/make-release-checks.sh create mode 100755 scripts/make-release-desc.sh diff --git a/.github/workflows/make-release.yml b/.github/workflows/make-release.yml new file mode 100644 index 000000000..b3ba8ae46 --- /dev/null +++ b/.github/workflows/make-release.yml @@ -0,0 +1,87 @@ +name: Make Release + +on: + workflow_dispatch: + inputs: + commit: + description: 'Commit SHA to release (empty = branch HEAD)' + required: false + default: '' + type: string + dry_run: + description: 'Dry run - validate without creating the tag' + required: true + type: boolean + default: true + +env: + GH_TOKEN: ${{ github.token }} + +permissions: + contents: write + +jobs: + make-release: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + with: + ref: ${{ inputs.commit != '' && inputs.commit || github.ref_name }} + fetch-depth: 0 + + - name: Run release checks + id: checks + run: bash scripts/make-release-checks.sh ${{ github.event.inputs.dry_run == 'true' && '--dry-run' || '' }} + env: + GITHUB_REPOSITORY: ${{ github.repository }} + RELEASE_BRANCH: ${{ github.ref_name }} + + - name: Create release tag + if: ${{ github.event.inputs.dry_run == 'false' }} + run: | + VERSION="${{ steps.checks.outputs.version }}" + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git tag -a "${VERSION}" -m "Release ${VERSION}" + git push origin "${VERSION}" + echo "Created and pushed tag ${VERSION}" + + - name: Generate release description + id: desc + run: bash scripts/make-release-desc.sh "${{ steps.checks.outputs.version }}" + env: + GITHUB_REPOSITORY: ${{ github.repository }} + + - name: Create release + if: ${{ github.event.inputs.dry_run == 'false' }} + uses: ggml-org/action-create-release@v1 + env: + GITHUB_TOKEN: ${{ github.token }} + with: + tag_name: ${{ steps.checks.outputs.version }} + # TODO: remove the prerelease flag once the semantic versioning workflow is ready + # ref: https://github.com/ggml-org/ggml/discussions/1579 + prerelease: true + body: | + > [!NOTE] + > Semantic versioning is still work in progress. + > More info can be found in https://github.com/ggml-org/ggml/discussions/1579 + + ${{ steps.desc.outputs.nightly }} + + ## ${{ steps.desc.outputs.changelog_title }} + + ${{ steps.desc.outputs.changelog }} + + - name: Dry run summary + if: ${{ github.event.inputs.dry_run == 'true' }} + run: | + if [[ "${{ steps.checks.outputs.checks_passed }}" == "true" ]]; then + echo "Dry run complete - all checks passed." + echo "Would have created tag: ${{ steps.checks.outputs.version }}" + else + echo "::error::Dry run found release check failures. A release tag would not be created." + exit 1 + fi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8dcfeb982..c43a64acf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,10 +1,14 @@ name: Release +# Builds dev releases (b* tags) when manually dispatched with create_release: true. +# Run this as a verification build before making a release (see make-release.yml). +# Pushing a v* tag does not trigger this workflow; releases are made via make-release.yml. + on: workflow_dispatch: inputs: create_release: - description: 'Create new release' + description: 'Create new developer release' required: true type: boolean pre_release_tag: @@ -12,10 +16,6 @@ on: required: false type: string - push: - tags: - - 'v*' - env: BRANCH_NAME: ${{ github.head_ref || github.ref_name }} VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite" @@ -55,18 +55,10 @@ jobs: echo "BRANCH_NAME: ${{ env.BRANCH_NAME }}" echo "CUSTOM_TAG: $CUSTOM_TAG" - if [[ "${{ github.ref_type }}" == "tag" ]]; then - echo "Using pushed tag name" - TAG_NAME="${{ github.ref_name }}" - SHOULD_RELEASE="true" - elif [[ -n "$CUSTOM_TAG" ]]; then + if [[ -n "$CUSTOM_TAG" ]]; then echo "Using custom tag" TAG_NAME="${CUSTOM_TAG}" SHOULD_RELEASE="true" - elif [[ "${{ github.event.inputs.create_release }}" == "true" ]]; then - echo "Manual release requested" - SHOULD_RELEASE="true" - TAG_NAME="b${BUILD_NUMBER}" elif [[ "${{ env.BRANCH_NAME }}" == "master" ]]; then echo "Using master branch format" TAG_NAME="b${BUILD_NUMBER}" @@ -78,6 +70,11 @@ jobs: SHOULD_RELEASE="false" fi + if [[ "${{ github.event.inputs.create_release }}" == "true" ]]; then + echo "Manual release requested" + SHOULD_RELEASE="true" + fi + echo "Final tag name: $TAG_NAME" echo "Should release: $SHOULD_RELEASE" echo "name=$TAG_NAME" >> $GITHUB_OUTPUT @@ -584,7 +581,7 @@ jobs: name: whisper-${{ needs.determine-tag.outputs.tag_name }}-xcframework.zip release: - if: ${{ github.event.inputs.create_release == 'true' || github.event.inputs.pre_release_tag != '' || startsWith(github.ref, 'refs/tags/v') }} + if: ${{ github.event.inputs.create_release == 'true' || github.event.inputs.pre_release_tag != '' }} runs-on: ubuntu-latest @@ -620,6 +617,16 @@ jobs: id: move_artifacts run: mkdir -p ./artifact/release && mv ./artifact/*/*.zip ./artifact/release && mv ./artifact/*/*.tar.gz ./artifact/release 2>/dev/null || true + - name: Create and push git tag + run: | + TAG="${{ needs.determine-tag.outputs.tag_name }}" + if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null 2>&1; then + echo "Tag ${TAG} already exists, skipping creation" + else + git tag "${TAG}" + git push origin "${TAG}" + fi + - name: Create release id: create_release uses: ggml-org/action-create-release@v1 @@ -627,8 +634,6 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: ${{ needs.determine-tag.outputs.tag_name }} - prerelease: ${{ github.event.inputs.pre_release_tag != '' }} - draft: true - name: Upload release id: upload_release diff --git a/scripts/make-release-checks.sh b/scripts/make-release-checks.sh new file mode 100755 index 000000000..0dd223f54 --- /dev/null +++ b/scripts/make-release-checks.sh @@ -0,0 +1,125 @@ +#!/bin/bash +# Run all pre-release checks and determine the release version. +# +# Usage: make-release-checks.sh [--dry-run] +# --dry-run: warn on failures instead of aborting +# +# Env (when running in GitHub Actions): +# GH_TOKEN, GITHUB_REPOSITORY, GITHUB_OUTPUT +# RELEASE_BRANCH: when set, HEAD must belong to origin/RELEASE_BRANCH and must +# not be older than 3 days from the branch HEAD (skipped when unset) +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +DRY_RUN=false +CHECKS_PASSED=true +for arg in "$@"; do + case "$arg" in + --dry-run) DRY_RUN=true ;; + *) echo "Unknown argument: $arg"; exit 1 ;; + esac +done + +MAJOR=$(grep "set(WHISPER_VERSION_MAJOR" "$REPO_ROOT/CMakeLists.txt" | grep -oP '\d+') +MINOR=$(grep "set(WHISPER_VERSION_MINOR" "$REPO_ROOT/CMakeLists.txt" | grep -oP '\d+') +PATCH=$(grep "set(WHISPER_VERSION_PATCH" "$REPO_ROOT/CMakeLists.txt" | grep -oP '\d+') +VERSION="v${MAJOR}.${MINOR}.${PATCH}" +echo "Determined version: ${VERSION}" +if [[ -n "${GITHUB_OUTPUT:-}" ]]; then + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" +fi + +SHA=$(git rev-parse HEAD) + +echo "Checking that commit ${SHA} belongs to the release branch..." +if [[ -z "${RELEASE_BRANCH:-}" ]]; then + echo "Warning: RELEASE_BRANCH not set - skipping commit check (local run)" +else + TIP="origin/${RELEASE_BRANCH}" + COMMIT_ERR="" + if ! git rev-parse --verify "${TIP}" >/dev/null 2>&1; then + COMMIT_ERR="branch ${RELEASE_BRANCH} not found on remote" + elif ! git merge-base --is-ancestor "${SHA}" "${TIP}"; then + COMMIT_ERR="commit ${SHA} is not part of branch ${RELEASE_BRANCH}" + else + COMMIT_TS=$(git show -s --format=%ct "${SHA}") + TIP_TS=$(git show -s --format=%ct "${TIP}") + AGE_DAYS=$(( (TIP_TS - COMMIT_TS) / 86400 )) + if (( TIP_TS - COMMIT_TS > 3 * 86400 )); then + COMMIT_ERR="commit ${SHA} is ${AGE_DAYS} day(s) older than the HEAD of ${RELEASE_BRANCH} (max: 3)" + fi + fi + if [[ -n "${COMMIT_ERR}" ]]; then + if [[ "$DRY_RUN" == "true" ]]; then + echo "Warning: ${COMMIT_ERR} (dry run, continuing)." + CHECKS_PASSED=false + else + echo "Error: ${COMMIT_ERR}" + exit 1 + fi + else + echo "Commit ${SHA} is on branch ${RELEASE_BRANCH} and within 3 days of its HEAD - OK" + fi +fi + +echo "Checking that tag ${VERSION} does not already exist..." +if git ls-remote --tags origin "${VERSION}" | grep -q "${VERSION}"; then + echo "Error: tag ${VERSION} already exists on remote" + exit 1 +fi +echo "Tag ${VERSION} does not exist on remote - OK" + +echo "Checking release.yml status for commit ${SHA}..." +if [[ -z "${GITHUB_REPOSITORY:-}" ]]; then + echo "Warning: GITHUB_REPOSITORY not set - skipping CI check (local run)" +else + RUNS=$(gh api "repos/${GITHUB_REPOSITORY}/actions/workflows/release.yml/runs?per_page=100" \ + --jq "[.workflow_runs[] | select(.head_sha == \"${SHA}\" and .conclusion == \"success\")] | length") + if [[ "$RUNS" -eq 0 ]]; then + if [[ "$DRY_RUN" == "true" ]]; then + echo "Warning: no successful release.yml run found for HEAD (${SHA}) (dry run, continuing)." + CHECKS_PASSED=false + else + echo "Error: no successful release.yml run found for HEAD (${SHA})" + echo "The nightly build must complete successfully before making a release." + exit 1 + fi + else + echo "Found successful release.yml run for HEAD." + fi +fi + +MAJOR=$(grep "set(GGML_VERSION_MAJOR" "$REPO_ROOT/ggml/CMakeLists.txt" | grep -oP '\d+') +MINOR=$(grep "set(GGML_VERSION_MINOR" "$REPO_ROOT/ggml/CMakeLists.txt" | grep -oP '\d+') +PATCH=$(grep "set(GGML_VERSION_PATCH" "$REPO_ROOT/ggml/CMakeLists.txt" | grep -oP '\d+') +GGML_VERSION="v${MAJOR}.${MINOR}.${PATCH}" +echo "Local ggml version: ${GGML_VERSION}" + +if ! git clone --depth 1 --branch "${GGML_VERSION}" https://github.com/ggml-org/ggml.git upstream-ggml 2>/dev/null; then + echo "Warning: tag ${GGML_VERSION} not found in upstream ggml - skipping comparison" +else + echo "Comparing local ggml/ src and include with upstream ${GGML_VERSION}..." + DIFF=$(diff -rq "$REPO_ROOT/ggml/src" upstream-ggml/src 2>&1 || true) + DIFF+=$(diff -rq "$REPO_ROOT/ggml/include" upstream-ggml/include 2>&1 || true) + DIFF+=$(diff "$REPO_ROOT/ggml/CMakeLists.txt" upstream-ggml/CMakeLists.txt 2>&1 || true) + rm -rf upstream-ggml + if [[ -n "$DIFF" ]]; then + echo "local ggml/ differs from upstream ${GGML_VERSION}:" + echo "$DIFF" + if [[ "$DRY_RUN" == "true" ]]; then + echo "Warning: would abort release due to ggml mismatch (dry run, continuing)." + CHECKS_PASSED=false + else + echo "Error: ggml must match upstream before making a release." + exit 1 + fi + else + echo "local ggml/ matches upstream ${GGML_VERSION}" + fi +fi + +if [[ -n "${GITHUB_OUTPUT:-}" ]]; then + echo "checks_passed=${CHECKS_PASSED}" >> "$GITHUB_OUTPUT" +fi diff --git a/scripts/make-release-desc.sh b/scripts/make-release-desc.sh new file mode 100755 index 000000000..100f855b3 --- /dev/null +++ b/scripts/make-release-desc.sh @@ -0,0 +1,87 @@ +#!/bin/bash +# Generate the description of a release: the previous release version, the +# change log and the link to the nightly release corresponding to the commit being released. +# +# Usage: make-release-desc.sh +# : current release version (v.., the leading v is optional) +# +# The previous version is the highest plain semver tag (v..) +# strictly below . The change log lists all commits between the +# previous version tag and the release commit, one line per commit. +# +# The release commit is the commit points at when the tag exists, +# HEAD otherwise. The nightly release is the b* tag pointing at that commit +# (release.yml tags the same commit); the link is only generated when that +# tag exists. +# +# Env (when running in GitHub Actions): +# GITHUB_OUTPUT: previous_tag, changelog_title, changelog and nightly are written here +# GITHUB_REPOSITORY: owner/repo, used to build the nightly release URL (skipped when unset) +set -euo pipefail + +if [[ $# -ne 1 ]]; then + echo "Usage: $(basename "$0") " + exit 1 +fi +VERSION="$1" + +# Accept the version with or without the leading v, reject anything else +if [[ "${VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + VERSION="v${VERSION}" +elif [[ ! "${VERSION}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Error: invalid version '${VERSION}' (expected v..)" + exit 1 +fi + +# Make sure all remote tags are available locally (skipped on local runs without origin) +if ! git fetch --tags origin 2>/dev/null; then + echo "Warning: could not fetch tags from origin (local run?)" +fi + +# Release commit: the commit points at when the tag exists, HEAD otherwise. +if ! RELEASE_COMMIT="$(git rev-parse -q --verify "refs/tags/${VERSION}^{commit}" 2>/dev/null)"; then + RELEASE_COMMIT="$(git rev-parse HEAD)" +fi + +echo "Release commit: $(git rev-parse --short "${RELEASE_COMMIT}")" + +PREV="$( { git tag --list; echo "${VERSION}"; } \ + | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' \ + | sort -V \ + | awk -v cur="${VERSION}" '$0 == cur { exit } { prev = $0 } END { print prev }')" + +if [[ -n "${PREV}" ]]; then + CHANGELOG="$(git log --oneline "${PREV}..${RELEASE_COMMIT}")" + CHANGELOG_TITLE="Change log since ${PREV}" +else + CHANGELOG="(no previous release tag found)" + CHANGELOG_TITLE="Change log" +fi + +# Nightly release: the b* tag pointing at the release commit (|| true: no match is not an error) +NIGHTLY_TAG="$(git tag --points-at "${RELEASE_COMMIT}" | grep -E '(^|-)b[0-9]+(-[0-9a-f]{7})?$' | head -n 1 || true)" + +NIGHTLY="" +if [[ -n "${NIGHTLY_TAG}" ]]; then + if [[ -n "${GITHUB_REPOSITORY:-}" ]]; then + NIGHTLY_URL="https://github.com/${GITHUB_REPOSITORY}/releases/tag/${NIGHTLY_TAG}" + NIGHTLY="**Nightly build:** [${NIGHTLY_TAG}](${NIGHTLY_URL})" + echo "Nightly release: ${NIGHTLY_URL}" + fi +else + echo "No nightly release found for commit $(git rev-parse --short "${RELEASE_COMMIT}")" +fi + +echo "Previous version: ${PREV:-none}" +echo "${CHANGELOG}" + +if [[ -n "${GITHUB_OUTPUT:-}" ]]; then + { + echo "previous_tag=${PREV}" + echo "changelog_title=${CHANGELOG_TITLE}" + echo "nightly=${NIGHTLY}" + echo "changelog<> "${GITHUB_OUTPUT}" +fi