Manual Release #40
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: 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 }} |