From 38e64d74231bd8706014122c9a9674dc8802afb5 Mon Sep 17 00:00:00 2001 From: mikepapadim Date: Wed, 10 Dec 2025 11:41:26 +0200 Subject: [PATCH 1/5] [CI][docs]Add release automation documentation for Maven Central deployment workflows --- docs/RELEASE-AUTOMATION.md | 58 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 docs/RELEASE-AUTOMATION.md diff --git a/docs/RELEASE-AUTOMATION.md b/docs/RELEASE-AUTOMATION.md new file mode 100644 index 00000000..0dfd5922 --- /dev/null +++ b/docs/RELEASE-AUTOMATION.md @@ -0,0 +1,58 @@ +# GPULlama3.java Release Workflows + +GitHub Actions workflows for automating releases to Maven Central. + +## šŸ“ Files + +Available in `.github/workflows/`: + +| File | Purpose | +|------|---------| +| `prepare-release.yml` | Creates release branch, bumps versions, generates changelog, opens PR | +| `finalize-release.yml` | Creates git tag and GitHub Release when release PR merges | +| `deploy-maven-central.yml` | Deploys to Maven Central when tag is pushed | + +## šŸ”„ Release Flow + +``` +1. PREPARE (manual trigger) + └── Creates release/X.Y.Z branch + PR + +2. REVIEW & MERGE (manual) + └── Review PR, CI runs, merge when ready + +3. FINALIZE (auto on PR merge) + └── Creates tag vX.Y.Z + GitHub Release + +4. DEPLOY (auto on tag push) + └── Publishes to Maven Central +``` + +## šŸš€ Usage + +### Starting a Release + +1. Go to **Actions** → **Prepare GPULlama3 Release** +2. Click **Run workflow** +3. Enter version (e.g., `0.2.3`) and previous version (e.g., `0.2.2`) +4. Review and merge the created PR +5. Everything else is automatic! + +### Manual Override + +```bash +# Just create tag manually (skips prepare/finalize) +git tag -a v0.2.3 -m "Release 0.2.3" +git push origin v0.2.3 +# → deploy-maven-central triggers automatically +``` + +## šŸ” Required Secrets + +| Secret | Description | +|--------|-------------| +| `OSSRH_USERNAME` | Maven Central username | +| `OSSRH_TOKEN` | Maven Central token | +| `GPG_PRIVATE_KEY` | `gpg --armor --export-secret-keys KEY_ID` | +| `GPG_KEYNAME` | GPG key ID | +| `GPG_PASSPHRASE` | GPG passphrase | From ca30221befe31989bb1f6cae53491fc5a1124a8c Mon Sep 17 00:00:00 2001 From: mikepapadim Date: Wed, 10 Dec 2025 11:46:59 +0200 Subject: [PATCH 2/5] [CI] Add workflow to finalize GPULlama3 release with tagging and GitHub release creation --- .github/workflows/finalize-release.yml | 150 +++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 .github/workflows/finalize-release.yml diff --git a/.github/workflows/finalize-release.yml b/.github/workflows/finalize-release.yml new file mode 100644 index 00000000..38d26241 --- /dev/null +++ b/.github/workflows/finalize-release.yml @@ -0,0 +1,150 @@ +name: Finalize GPULlama3 Release + +on: + pull_request: + types: [closed] + branches: [main] + workflow_dispatch: + inputs: + version: + description: 'Release version to tag (e.g., 0.2.3)' + required: true + type: string + +jobs: + create-release-tag: + # Run when release PR merges OR manual trigger + if: | + (github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/')) || + github.event_name == 'workflow_dispatch' + runs-on: [self-hosted, Linux, x64] + permissions: + contents: write + timeout-minutes: 10 + + outputs: + version: ${{ steps.get_version.outputs.version }} + + steps: + - name: Get version + id: get_version + run: | + if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then + echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT + else + BRANCH="${{ github.event.pull_request.head.ref }}" + echo "version=${BRANCH#release/}" >> $GITHUB_OUTPUT + fi + + - name: Checkout main + uses: actions/checkout@v4 + with: + ref: main + fetch-depth: 0 + + - name: Configure Git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Create and push tag + run: | + VERSION="${{ steps.get_version.outputs.version }}" + git tag -a "v${VERSION}" -m "Release ${VERSION}" + git push origin "v${VERSION}" + echo "āœ… Created tag v${VERSION}" + + - name: Extract changelog for release notes + id: changelog + run: | + VERSION="${{ steps.get_version.outputs.version }}" + CHANGELOG_FILE="CHANGELOG.md" + + if [ -f "$CHANGELOG_FILE" ]; then + # Extract section for this version + awk -v ver="## \\[${VERSION}\\]" ' + $0 ~ ver { found=1; next } + found && /^## \[/ { exit } + found { print } + ' "$CHANGELOG_FILE" > /tmp/release_notes.txt + + if [ -s /tmp/release_notes.txt ]; then + echo "Found changelog section for ${VERSION}" + else + echo "See CHANGELOG.md for details." > /tmp/release_notes.txt + fi + else + echo "See commit history for changes." > /tmp/release_notes.txt + fi + + - name: Create GitHub Release + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const version = '${{ steps.get_version.outputs.version }}'; + + let releaseNotes; + try { + releaseNotes = fs.readFileSync('/tmp/release_notes.txt', 'utf8').trim(); + if (!releaseNotes) { + releaseNotes = `Release ${version}`; + } + } catch (e) { + releaseNotes = `Release ${version}`; + } + + // Add installation instructions + releaseNotes += `\n\n---\n\n### šŸ“¦ Installation\n\n`; + releaseNotes += `**Maven**\n\`\`\`xml\n\n io.github.beehive-lab\n gpu-llama3\n ${version}\n\n\`\`\`\n\n`; + releaseNotes += `**Gradle**\n\`\`\`groovy\nimplementation 'io.github.beehive-lab:gpu-llama3:${version}'\n\`\`\`\n\n`; + releaseNotes += `---\n\nšŸ“– [Documentation](https://github.com/beehive-lab/GPULlama3.java#readme) | šŸ”— [Maven Central](https://central.sonatype.com/artifact/io.github.beehive-lab/gpu-llama3/${version})`; + + const { data: release } = await github.rest.repos.createRelease({ + owner: context.repo.owner, + repo: context.repo.repo, + tag_name: `v${version}`, + name: `GPULlama3.java ${version}`, + body: releaseNotes, + draft: false, + prerelease: false + }); + + console.log(`āœ… Created release: ${release.html_url}`); + + - name: Summary + run: | + VERSION="${{ steps.get_version.outputs.version }}" + echo "## šŸŽ‰ Release v${VERSION} Finalized" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Step | Status |" >> $GITHUB_STEP_SUMMARY + echo "|------|--------|" >> $GITHUB_STEP_SUMMARY + echo "| Tag created | āœ… v${VERSION} |" >> $GITHUB_STEP_SUMMARY + echo "| GitHub Release | āœ… Created |" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### šŸ”„ Next:" >> $GITHUB_STEP_SUMMARY + echo "**Deploy to Maven Central** workflow will trigger automatically" >> $GITHUB_STEP_SUMMARY + + cleanup-branch: + needs: create-release-tag + runs-on: [self-hosted, Linux, x64] + permissions: + contents: write + if: github.event_name == 'pull_request' + + steps: + - name: Delete release branch + uses: actions/github-script@v7 + with: + script: | + const branch = '${{ github.event.pull_request.head.ref }}'; + try { + await github.rest.git.deleteRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: `heads/${branch}` + }); + console.log(`āœ… Deleted branch: ${branch}`); + } catch (e) { + console.log(`Could not delete branch: ${e.message}`); + } From 1ae4a2ab9971fdae20dd93a9bffbdf67cc043799 Mon Sep 17 00:00:00 2001 From: mikepapadim Date: Wed, 10 Dec 2025 11:47:08 +0200 Subject: [PATCH 3/5] [CI] Add workflow for preparing GPULlama3 release with version updates and changelog generation --- .github/workflows/prepare-release.yml | 290 ++++++++++++++++++++++++++ 1 file changed, 290 insertions(+) create mode 100644 .github/workflows/prepare-release.yml diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml new file mode 100644 index 00000000..a011495e --- /dev/null +++ b/.github/workflows/prepare-release.yml @@ -0,0 +1,290 @@ +name: Prepare GPULlama3 Release + +on: + workflow_dispatch: + inputs: + version: + description: 'Release version (e.g., 0.2.3)' + required: true + type: string + previous_version: + description: 'Previous version for changelog (e.g., 0.2.2)' + required: true + type: string + dry_run: + description: 'Dry run - show changes without creating PR' + required: false + type: boolean + default: false + +env: + VERSION: ${{ inputs.version }} + PREV_VERSION: ${{ inputs.previous_version }} + +jobs: + prepare-release: + runs-on: [self-hosted, Linux, x64] + permissions: + contents: write + pull-requests: write + timeout-minutes: 15 + env: + JAVA_HOME: /opt/jenkins/jdks/graal-23.1.0/jdk-21.0.3 + + steps: + - name: Validate version format + run: | + if [[ ! "${{ inputs.version }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "āŒ Invalid version format. Expected: X.Y.Z (e.g., 0.2.3)" + exit 1 + fi + echo "āœ… Version format valid: ${{ inputs.version }}" + + - name: Checkout main branch + uses: actions/checkout@v4 + with: + ref: main + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup environment + run: | + echo "$JAVA_HOME/bin" >> $GITHUB_PATH + + - name: Configure Git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Create release branch + run: | + git checkout -b release/${{ env.VERSION }} + echo "āœ… Created branch: release/${{ env.VERSION }}" + + # ============================================ + # VERSION UPDATES + # ============================================ + + - name: Update Maven version + run: | + ./mvnw versions:set -DnewVersion=${{ env.VERSION }} -DgenerateBackupPoms=false + echo "āœ… Maven version updated to ${{ env.VERSION }}" + + - name: Update README.md + run: | + if [ -f "README.md" ]; then + # Update version in Maven dependency example + sed -i 's|[0-9]\+\.[0-9]\+\.[0-9]\+|${{ env.VERSION }}|g' README.md + echo "āœ… Updated README.md" + fi + + - name: Update CITATION.cff + run: | + if [ -f "CITATION.cff" ]; then + sed -i "s/^version: .*/version: ${{ env.VERSION }}/" CITATION.cff + RELEASE_DATE=$(date +"%Y-%m-%d") + sed -i "s/^date-released: .*/date-released: $RELEASE_DATE/" CITATION.cff + echo "āœ… Updated CITATION.cff" + fi + + # ============================================ + # CHANGELOG GENERATION + # ============================================ + + - name: Fetch merged PRs for changelog + id: fetch_prs + uses: actions/github-script@v7 + with: + script: | + const prevVersion = '${{ env.PREV_VERSION }}'; + const newVersion = '${{ env.VERSION }}'; + + let sinceDate; + try { + const { data: releases } = await github.rest.repos.listReleases({ + owner: context.repo.owner, + repo: context.repo.repo, + per_page: 10 + }); + + const prevRelease = releases.find(r => + r.tag_name === `v${prevVersion}` || r.tag_name === prevVersion + ); + if (prevRelease) { + sinceDate = prevRelease.published_at; + console.log(`Found previous release ${prevVersion} from ${sinceDate}`); + } + } catch (e) { + console.log('Could not fetch releases:', e.message); + } + + if (!sinceDate) { + const date = new Date(); + date.setDate(date.getDate() - 90); + sinceDate = date.toISOString(); + console.log(`Using fallback date: ${sinceDate}`); + } + + const { data: prs } = await github.rest.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'closed', + sort: 'updated', + direction: 'desc', + per_page: 100 + }); + + const mergedPRs = prs.filter(pr => + pr.merged_at && new Date(pr.merged_at) > new Date(sinceDate) + ); + + console.log(`Found ${mergedPRs.length} merged PRs since ${sinceDate}`); + + const features = []; + const bugfixes = []; + const models = []; + const performance = []; + const other = []; + + for (const pr of mergedPRs) { + const labels = pr.labels.map(l => l.name.toLowerCase()); + const title = pr.title; + const entry = `- ${title} ([#${pr.number}](${pr.html_url}))`; + + if (labels.some(l => l.includes('bug') || l.includes('fix'))) { + bugfixes.push(entry); + } else if (labels.some(l => l.includes('model')) || title.toLowerCase().includes('model')) { + models.push(entry); + } else if (labels.some(l => l.includes('perf') || l.includes('optim'))) { + performance.push(entry); + } else if (labels.some(l => l.includes('feature') || l.includes('enhancement'))) { + features.push(entry); + } else { + other.push(entry); + } + } + + const today = new Date().toISOString().split('T')[0]; + let changelog = `## [${newVersion}] - ${today}\n\n`; + + if (features.length > 0) { + changelog += '### Features\n\n' + features.join('\n') + '\n\n'; + } + if (models.length > 0) { + changelog += '### Model Support\n\n' + models.join('\n') + '\n\n'; + } + if (performance.length > 0) { + changelog += '### Performance\n\n' + performance.join('\n') + '\n\n'; + } + if (bugfixes.length > 0) { + changelog += '### Bug Fixes\n\n' + bugfixes.join('\n') + '\n\n'; + } + if (other.length > 0) { + changelog += '### Other Changes\n\n' + other.join('\n') + '\n\n'; + } + if (mergedPRs.length === 0) { + changelog += '\n\n'; + } + + const fs = require('fs'); + fs.writeFileSync('/tmp/changelog_entry.txt', changelog); + core.setOutput('pr_count', mergedPRs.length); + + - name: Update CHANGELOG.md + run: | + CHANGELOG_FILE="CHANGELOG.md" + + if [ ! -f "$CHANGELOG_FILE" ]; then + echo "# Changelog" > "$CHANGELOG_FILE" + echo "" >> "$CHANGELOG_FILE" + echo "All notable changes to GPULlama3.java will be documented in this file." >> "$CHANGELOG_FILE" + echo "" >> "$CHANGELOG_FILE" + fi + + if grep -q "^## \[" "$CHANGELOG_FILE"; then + FIRST_VERSION_LINE=$(grep -n "^## \[" "$CHANGELOG_FILE" | head -1 | cut -d: -f1) + { + head -n $((FIRST_VERSION_LINE - 1)) "$CHANGELOG_FILE" + cat /tmp/changelog_entry.txt + tail -n +$FIRST_VERSION_LINE "$CHANGELOG_FILE" + } > /tmp/changelog_new.md + mv /tmp/changelog_new.md "$CHANGELOG_FILE" + else + cat /tmp/changelog_entry.txt >> "$CHANGELOG_FILE" + fi + + echo "āœ… Updated CHANGELOG.md" + + - name: Show changes summary + run: | + echo "## šŸ“‹ Release ${{ env.VERSION }} Preparation" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Files Modified:" >> $GITHUB_STEP_SUMMARY + git diff --name-only | while read file; do + echo "- \`$file\`" >> $GITHUB_STEP_SUMMARY + done + echo "" >> $GITHUB_STEP_SUMMARY + echo "### PRs included: ${{ steps.fetch_prs.outputs.pr_count }}" >> $GITHUB_STEP_SUMMARY + + - name: Commit and push + if: ${{ inputs.dry_run == false }} + run: | + git add -A + git commit -m "Prepare release ${{ env.VERSION }}" + git push origin release/${{ env.VERSION }} + + - name: Create Pull Request + if: ${{ inputs.dry_run == false }} + uses: actions/github-script@v7 + with: + script: | + const version = process.env.VERSION; + const prCount = '${{ steps.fetch_prs.outputs.pr_count }}'; + + const { data: pr } = await github.rest.pulls.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: `Release ${version}`, + head: `release/${version}`, + base: 'main', + body: `## šŸš€ Release ${version} + + ### šŸ“ Changes + - ${prCount} PRs included in changelog + - Version bumped to ${version} + + ### āœ… Review Checklist + - [ ] Version number correct in pom.xml + - [ ] CHANGELOG.md reviewed + - [ ] CI passes + + ### šŸ”„ After Merge + 1. **Finalize Release** → tag \`v${version}\` + GitHub release + 2. **Deploy to Maven Central** → publish artifacts + ` + }); + + console.log(`āœ… Created PR #${pr.number}: ${pr.html_url}`); + + const reviewers = ['mikepapadim', 'orionpapadakis', 'mairooni' , 'stratika' ,'kotselidis']; + try { + await github.rest.pulls.requestReviewers({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: pr.number, + reviewers: reviewers + }); + } catch (e) { + console.log('Could not request reviewers:', e.message); + } + + - name: Dry run output + if: ${{ inputs.dry_run == true }} + run: | + echo "šŸƒ DRY RUN MODE" + echo "" + echo "=== Files changed ===" + git diff --stat + echo "" + echo "=== Changelog entry ===" + cat /tmp/changelog_entry.txt From 1c375f5fa44c5a31f765bcce934f5d6e8e0e88af Mon Sep 17 00:00:00 2001 From: mikepapadim Date: Wed, 10 Dec 2025 11:54:28 +0200 Subject: [PATCH 4/5] [CI] Enhance Maven Central deployment workflow with tag input and summary reporting --- .github/workflows/deploy-maven-central.yml | 24 +++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy-maven-central.yml b/.github/workflows/deploy-maven-central.yml index a723e83c..9a262d5b 100644 --- a/.github/workflows/deploy-maven-central.yml +++ b/.github/workflows/deploy-maven-central.yml @@ -5,14 +5,22 @@ on: tags: - 'v*' - '[0-9]+.[0-9]+.[0-9]+*' + workflow_run: + workflows: ["Finalize GPULlama3 Release"] + types: [completed] workflow_dispatch: inputs: + tag: + description: 'Tag to deploy (e.g., v0.2.3) - leave empty to deploy latest tag' + required: false + type: string dry_run: description: 'Dry run (skip actual deploy)' required: false default: false type: boolean + jobs: deploy: name: Deploy to Maven Central @@ -85,4 +93,18 @@ jobs: --batch-mode env: GPG_KEYNAME: ${{ secrets.GPG_KEYNAME }} - GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} \ No newline at end of file + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + + - name: Deployment Summary + if: ${{ !inputs.dry_run }} + run: | + echo "## šŸš€ Maven Central Deployment" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Detail | Value |" >> $GITHUB_STEP_SUMMARY + echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY + echo "| Version | ${{ steps.version.outputs.version }} |" >> $GITHUB_STEP_SUMMARY + echo "| GroupId | io.github.beehive-lab |" >> $GITHUB_STEP_SUMMARY + echo "| ArtifactId | gpu-llama3 |" >> $GITHUB_STEP_SUMMARY + echo "| Status | āœ… Deployed |" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "šŸ“ [View on Maven Central](https://central.sonatype.com/artifact/io.github.beehive-lab/gpu-llama3/${{ steps.version.outputs.version }})" >> $GITHUB_STEP_SUMMARY \ No newline at end of file From 2f6cc0ff4b277c27ab0f6ea04adcd748e3dea3ce Mon Sep 17 00:00:00 2001 From: mikepapadim Date: Wed, 10 Dec 2025 11:54:33 +0200 Subject: [PATCH 5/5] [CI] Update finalize-release workflow to use dynamic tag reference for checkout --- .github/workflows/finalize-release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/finalize-release.yml b/.github/workflows/finalize-release.yml index 38d26241..ef71adf6 100644 --- a/.github/workflows/finalize-release.yml +++ b/.github/workflows/finalize-release.yml @@ -21,7 +21,7 @@ jobs: permissions: contents: write timeout-minutes: 10 - + outputs: version: ${{ steps.get_version.outputs.version }} @@ -39,7 +39,7 @@ jobs: - name: Checkout main uses: actions/checkout@v4 with: - ref: main + ref: ${{ inputs.tag || github.ref }} fetch-depth: 0 - name: Configure Git