Skip to content

Manual Release

Manual Release #35

name: Manual Release
on:
workflow_dispatch:
inputs:
bump_type:
description: "Select semantic version bump type"
required: true
default: patch
type: choice
options:
- major
- minor
- patch
include_manylinux_2_28:
description: "Include manylinux_2_28 build"
required: true
default: true
type: boolean
include_manylinux_2_34:
description: "Include manylinux_2_34 build"
required: true
default: true
type: boolean
permissions:
contents: write
jobs:
prepare:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.next_version }}
last_tag: ${{ steps.version.outputs.last_tag }}
notes_file: ${{ steps.notes.outputs.file }}
steps:
- name: Check out repository
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Determine next version
id: version
run: |
set -eo pipefail
git fetch --tags --force
LAST_TAG=$(git describe --tags --abbrev=0 --match "v*" 2>/dev/null || true)
if [ -z "$LAST_TAG" ]; then
# First release: always start at 1.0.0 regardless of bump type
NEXT_VERSION="1.0.0"
echo "last_tag=" >> "$GITHUB_OUTPUT"
echo "next_version=$NEXT_VERSION" >> "$GITHUB_OUTPUT"
exit 0
else
CURRENT=${LAST_TAG#v}
fi
IFS='.' read -r MAJOR MINOR PATCH <<<"$CURRENT"
case "${{ github.event.inputs.bump_type }}" in
major)
MAJOR=$((MAJOR + 1))
MINOR=0
PATCH=0
;;
minor)
MINOR=$((MINOR + 1))
PATCH=0
;;
*)
PATCH=$((PATCH + 1))
;;
esac
NEXT_VERSION="$MAJOR.$MINOR.$PATCH"
echo "last_tag=$LAST_TAG" >> "$GITHUB_OUTPUT"
echo "next_version=$NEXT_VERSION" >> "$GITHUB_OUTPUT"
- name: Build release notes from conventional commits
id: notes
run: |
set -eo pipefail
NOTES_FILE="release-notes.md"
LAST_TAG="${{ steps.version.outputs.last_tag }}"
if [ -z "$LAST_TAG" ]; then
RANGE="HEAD"
else
RANGE="$LAST_TAG..HEAD"
fi
echo "## Changes" > "$NOTES_FILE"
HAS_CHANGES=false
PATTERN='^(feat|fix|perf|refactor|docs)(\([^)]+\))?:\ (.+)$'
while IFS= read -r SUBJECT; do
if [[ $SUBJECT =~ $PATTERN ]]; then
TYPE=${BASH_REMATCH[1]}
SUMMARY=${BASH_REMATCH[3]}
case "$TYPE" in
feat) LABEL="Feature";;
fix) LABEL="Fix";;
perf) LABEL="Performance";;
refactor) LABEL="Refactor";;
docs) LABEL="Docs";;
esac
echo "- ${LABEL}: ${SUMMARY}" >> "$NOTES_FILE"
HAS_CHANGES=true
fi
done < <(git log $RANGE --pretty=format:"%s")
if [ "$HAS_CHANGES" = false ]; then
echo "- No user-facing conventional commit changes since last release." >> "$NOTES_FILE"
fi
echo "file=$NOTES_FILE" >> "$GITHUB_OUTPUT"
- name: Prepare common release files and models
run: |
./scripts/prepare-common.sh "${{ steps.version.outputs.next_version }}"
- name: Upload common files artifact
id: upload
uses: actions/upload-artifact@v4
with:
name: release-common-${{ steps.version.outputs.next_version }}
path: release_common
retention-days: 1
- name: Upload release notes
uses: actions/upload-artifact@v4
with:
name: release-notes-${{ steps.version.outputs.next_version }}
path: ${{ steps.notes.outputs.file }}
retention-days: 1
package_installer:
needs: [prepare, package_manylinux_2_28, package_manylinux_2_34]
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Download common files artifact
uses: actions/download-artifact@v4
with:
name: release-common-${{ needs.prepare.outputs.version }}
path: release_common
- name: Restore cached models
run: |
mkdir -p models
cp -r release_common/models/* models/
# Ensure universal_config is also consistent if modified in prepare (though it's just a copy)
cp release_common/universal_config.json .
- name: Download manylinux_2_28 package
if: ${{ inputs.include_manylinux_2_28 }}
uses: actions/download-artifact@v4
with:
name: package-manylinux-2_28-${{ needs.prepare.outputs.version }}
path: packages
- name: Download manylinux_2_34 package
if: ${{ inputs.include_manylinux_2_34 }}
uses: actions/download-artifact@v4
with:
name: package-manylinux-2_34-${{ needs.prepare.outputs.version }}
path: packages
- name: Select payload root
run: |
set -eo pipefail
PAYLOAD_ROOT=""
if [ "${{ inputs.include_manylinux_2_34 }}" = "true" ]; then
PKG_234=$(find packages -name "opd-mcp-*-manylinux_2_34*.zip" -type f | head -1)
if [ -n "$PKG_234" ]; then
echo "Using manylinux_2_34 package: $PKG_234"
unzip -q "$PKG_234" -d .
PAYLOAD_ROOT="$(pwd)/release_package_manylinux_2_34/opd-mcp"
fi
fi
if [ -z "$PAYLOAD_ROOT" ] && [ "${{ inputs.include_manylinux_2_28 }}" = "true" ]; then
PKG_228=$(find packages -name "opd-mcp-*-manylinux_2_28*.zip" -type f | head -1)
if [ -n "$PKG_228" ]; then
echo "Using manylinux_2_28 package: $PKG_228"
unzip -q "$PKG_228" -d .
PAYLOAD_ROOT="$(pwd)/release_package_manylinux_2_28/opd-mcp"
fi
fi
if [ -z "$PAYLOAD_ROOT" ]; then
echo "Error: No manylinux payload available for installer build" >&2
exit 1
fi
echo "PAYLOAD_ROOT=$PAYLOAD_ROOT" >> "$GITHUB_ENV"
- name: Build Installer
run: |
chmod +x scripts/build_installer.sh
./scripts/build_installer.sh "$PAYLOAD_ROOT"
# Rename to include version
mv build-output/opd-mcp-installer.sh build-output/opd-mcp-${{ needs.prepare.outputs.version }}-installer.sh
- name: Upload installer artifact
uses: actions/upload-artifact@v4
with:
name: installer-${{ needs.prepare.outputs.version }}
path: build-output/opd-mcp-${{ needs.prepare.outputs.version }}-installer.sh
retention-days: 1
package_manylinux_2_28:
needs: prepare
runs-on: ubuntu-latest
container: registry.access.redhat.com/ubi8/ubi:8.10
steps:
- name: include_manylinux_2_28 flag
run: echo "include_manylinux_2_28=${{ inputs.include_manylinux_2_28 }}"
- name: Check out repository
if: ${{ inputs.include_manylinux_2_28 }}
uses: actions/checkout@v4
- name: Download common files artifact
if: ${{ inputs.include_manylinux_2_28 }}
uses: actions/download-artifact@v4
with:
name: release-common-${{ needs.prepare.outputs.version }}
path: release_common
- name: Build manylinux package
id: build_package
if: ${{ inputs.include_manylinux_2_28 }}
run: |
chmod +x scripts/package-manylinux-2_28.sh
scripts/package-manylinux-2_28.sh "${{ needs.prepare.outputs.version }}"
- name: Upload manylinux package artifact
if: ${{ inputs.include_manylinux_2_28 }}
uses: actions/upload-artifact@v4
with:
name: package-manylinux-2_28-${{ needs.prepare.outputs.version }}
path: ${{ steps.build_package.outputs.asset_path }}
retention-days: 1
package_manylinux_2_34:
needs: prepare
runs-on: ubuntu-latest
container: registry.access.redhat.com/ubi9/ubi:9.7
steps:
- name: include_manylinux_2_34 flag
run: echo "include_manylinux_2_34=${{ inputs.include_manylinux_2_34 }}"
- name: Check out repository
if: ${{ inputs.include_manylinux_2_34 }}
uses: actions/checkout@v4
- name: Download common files artifact
if: ${{ inputs.include_manylinux_2_34 }}
uses: actions/download-artifact@v4
with:
name: release-common-${{ needs.prepare.outputs.version }}
path: release_common
- name: Build manylinux package
id: build_package
if: ${{ inputs.include_manylinux_2_34 }}
run: |
chmod +x scripts/package-manylinux-2_34.sh
scripts/package-manylinux-2_34.sh "${{ needs.prepare.outputs.version }}"
- name: Upload manylinux package artifact
if: ${{ inputs.include_manylinux_2_34 }}
uses: actions/upload-artifact@v4
with:
name: package-manylinux-2_34-${{ needs.prepare.outputs.version }}
path: ${{ steps.build_package.outputs.asset_path }}
retention-days: 1
release:
needs: [prepare, package_installer, package_manylinux_2_28, package_manylinux_2_34]
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
- name: Download installer artifact
uses: actions/download-artifact@v4
with:
name: installer-${{ needs.prepare.outputs.version }}
path: packages
- name: Download manylinux_2_28 package
uses: actions/download-artifact@v4
if: ${{ inputs.include_manylinux_2_28 }}
with:
name: package-manylinux-2_28-${{ needs.prepare.outputs.version }}
path: packages
- name: Download manylinux_2_34 package
uses: actions/download-artifact@v4
if: ${{ inputs.include_manylinux_2_34 }}
with:
name: package-manylinux-2_34-${{ needs.prepare.outputs.version }}
path: packages
- name: Download release notes
uses: actions/download-artifact@v4
with:
name: release-notes-${{ needs.prepare.outputs.version }}
path: .
- name: Find artifacts
id: find_artifacts
run: |
set -eo pipefail
INSTALLER=$(find packages -name "*-installer.sh" -type f | head -1)
if [ -z "$INSTALLER" ]; then
echo "Error: Could not find installer artifact"
exit 1
fi
FILES="$INSTALLER"
if [[ "${{ inputs.include_manylinux_2_28 }}" == "true" ]]; then
PKG_228=$(find packages -name "opd-mcp-*-manylinux_2_28*.zip" -type f | head -1)
if [ -z "$PKG_228" ]; then
echo "Error: Missing manylinux_2_28 package artifact"
exit 1
fi
FILES+=$'\n'"$PKG_228"
echo "package_228=$PKG_228" >> "$GITHUB_OUTPUT"
fi
if [[ "${{ inputs.include_manylinux_2_34 }}" == "true" ]]; then
PKG_234=$(find packages -name "opd-mcp-*-manylinux_2_34*.zip" -type f | head -1)
if [ -z "$PKG_234" ]; then
echo "Error: Missing manylinux_2_34 package artifact"
exit 1
fi
FILES+=$'\n'"$PKG_234"
echo "package_234=$PKG_234" >> "$GITHUB_OUTPUT"
fi
if [[ -z "$FILES" ]] || [[ "$FILES" == "$INSTALLER" && "${{ inputs.include_manylinux_2_28 }}" != "true" && "${{ inputs.include_manylinux_2_34 }}" != "true" ]]; then
echo "Error: No manylinux builds were selected"
exit 1
fi
echo "installer=$INSTALLER" >> "$GITHUB_OUTPUT"
printf 'files<<EOF\n%s\nEOF\n' "$FILES" >> "$GITHUB_OUTPUT"
- name: Create and push tag
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -eo pipefail
git config user.name "github-actions"
git config user.email "github-actions@users.noreply.github.com"
git tag -a "v${{ needs.prepare.outputs.version }}" -m "Release v${{ needs.prepare.outputs.version }}"
git push origin "v${{ needs.prepare.outputs.version }}"
- name: Create GitHub release
uses: softprops/action-gh-release@v1
with:
tag_name: v${{ needs.prepare.outputs.version }}
name: Release v${{ needs.prepare.outputs.version }}
body_path: release-notes.md
files: ${{ steps.find_artifacts.outputs.files }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}