Skip to content

Manual Release

Manual Release #40

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: Build installers
run: |
set -eo pipefail
build_installer_from_zip() {
local zipfile="$1"
local suffix="$2"
local temp_dir=$(mktemp -d)
# Extract to temporary directory to avoid conflicts
unzip -q "$zipfile" -d "$temp_dir"
local payload_root=""
for CANDIDATE in "$temp_dir/release_package_manylinux_${suffix}/opd-mcp" "$temp_dir/opd-mcp"; do
if [ -d "$CANDIDATE" ]; then
payload_root="$CANDIDATE"
break
fi
done
if [ -z "$payload_root" ]; then
echo "Error: unable to locate payload root for manylinux_${suffix}" >&2
rm -rf "$temp_dir"
exit 1
fi
local dep_count
dep_count=$(find "$payload_root/dependencies" -type f | wc -l)
echo "manylinux_${suffix} dependencies: $dep_count files"
if [ "$dep_count" -eq 0 ]; then
echo "Error: manylinux_${suffix} payload has no dependencies at $payload_root/dependencies" >&2
rm -rf "$temp_dir"
exit 1
fi
chmod +x scripts/build_installer.sh
./scripts/build_installer.sh "$payload_root"
mv build-output/opd-mcp-installer.sh "build-output/opd-mcp-${{ needs.prepare.outputs.version }}-manylinux_${suffix}-installer.sh"
# Clean up temporary directory
rm -rf "$temp_dir"
}
BUILT=false
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 "Building installer from manylinux_2_34 package: $PKG_234"
build_installer_from_zip "$PKG_234" "2_34"
BUILT=true
fi
fi
if [ "${{ 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 "Building installer from manylinux_2_28 package: $PKG_228"
build_installer_from_zip "$PKG_228" "2_28"
BUILT=true
fi
fi
if [ "$BUILT" = false ]; then
echo "Error: No manylinux payload available for installer build" >&2
exit 1
fi
- name: Upload manylinux_2_34 installer artifact
if: ${{ inputs.include_manylinux_2_34 }}
uses: actions/upload-artifact@v4
with:
name: installer-manylinux_2_34-${{ needs.prepare.outputs.version }}
path: build-output/opd-mcp-${{ needs.prepare.outputs.version }}-manylinux_2_34-installer.sh
retention-days: 1
- name: Upload manylinux_2_28 installer artifact
if: ${{ inputs.include_manylinux_2_28 }}
uses: actions/upload-artifact@v4
with:
name: installer-manylinux_2_28-${{ needs.prepare.outputs.version }}
path: build-output/opd-mcp-${{ needs.prepare.outputs.version }}-manylinux_2_28-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 manylinux_2_34 installer artifact
if: ${{ inputs.include_manylinux_2_34 }}
uses: actions/download-artifact@v4
with:
name: installer-manylinux_2_34-${{ needs.prepare.outputs.version }}
path: packages
- name: Download manylinux_2_28 installer artifact
if: ${{ inputs.include_manylinux_2_28 }}
uses: actions/download-artifact@v4
with:
name: installer-manylinux_2_28-${{ 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
FILES=""
if [[ "${{ inputs.include_manylinux_2_34 }}" == "true" ]]; then
INST_234=$(find packages -name "opd-mcp-*-manylinux_2_34-installer.sh" -type f | head -1)
PKG_234=$(find packages -name "opd-mcp-*-manylinux_2_34*.zip" -type f | head -1)
if [ -z "$INST_234" ] || [ -z "$PKG_234" ]; then
echo "Error: Missing manylinux_2_34 artifacts" >&2
exit 1
fi
FILES+="$INST_234"$'\n'"$PKG_234"
echo "installer_234=$INST_234" >> "$GITHUB_OUTPUT"
echo "package_234=$PKG_234" >> "$GITHUB_OUTPUT"
fi
if [[ "${{ inputs.include_manylinux_2_28 }}" == "true" ]]; then
INST_228=$(find packages -name "opd-mcp-*-manylinux_2_28-installer.sh" -type f | head -1)
PKG_228=$(find packages -name "opd-mcp-*-manylinux_2_28*.zip" -type f | head -1)
if [ -z "$INST_228" ] || [ -z "$PKG_228" ]; then
echo "Error: Missing manylinux_2_28 artifacts" >&2
exit 1
fi
if [ -n "$FILES" ]; then FILES+=$'\n'; fi
FILES+="$INST_228"$'\n'"$PKG_228"
echo "installer_228=$INST_228" >> "$GITHUB_OUTPUT"
echo "package_228=$PKG_228" >> "$GITHUB_OUTPUT"
fi
if [[ -z "$FILES" ]]; then
echo "Error: No artifacts to release" >&2
exit 1
fi
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 }}