diff --git a/.github/workflows/auto-release.yml b/.github/workflows/auto-release.yml new file mode 100644 index 0000000..8e80270 --- /dev/null +++ b/.github/workflows/auto-release.yml @@ -0,0 +1,184 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ============================================================================ +# SNAPSHOT BUILD & CHANGELOG ACCUMULATION +# ============================================================================ +# +# PURPOSE: +# -------- +# Builds SNAPSHOT artifacts and accumulates changelog entries when PRs are +# merged to main. Does NOT create version tags - use cut-release.yml for that. +# +# HOW IT WORKS: +# ------------- +# 1. Triggered when a PR is merged to main +# 2. Builds SNAPSHOT artifacts (version calculated from git-semver-plugin) +# 3. Updates CHANGELOG.md with new commits under "Unreleased" section +# 4. Commits changelog updates back to main +# +# VERSIONING: +# ----------- +# Uses git-semver-plugin to calculate version from conventional commits: +# - fix: → patch bump +# - feat: → minor bump +# - feat!: or BREAKING CHANGE → major bump +# +# Version remains X.Y.Z-SNAPSHOT until cut-release.yml is triggered. +# +# RELATED WORKFLOWS: +# ------------------ +# - build-and-publish.yml: Builds and publishes Docker images +# - cut-release.yml: Creates version tags and releases (manual) +# - release-publish.yml: Official ASF releases (manual, after vote) + +name: Snapshot Build + +on: + push: + branches: + - main + +permissions: + contents: write + +env: + JAVA_VERSION: '25' + JAVA_DISTRIBUTION: 'temurin' + +jobs: + snapshot: + name: Build Snapshot & Update Changelog + runs-on: ubuntu-latest + # Skip if this is a release commit or changelog update + if: "!startsWith(github.event.head_commit.message, 'chore(release):') && !startsWith(github.event.head_commit.message, 'chore(changelog):')" + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up JDK ${{ env.JAVA_VERSION }} + uses: actions/setup-java@v4 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: ${{ env.JAVA_DISTRIBUTION }} + cache: 'gradle' + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Calculate version + id: version + run: | + VERSION=$(./gradlew printVersion --quiet) + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Current SNAPSHOT version: $VERSION" + + - name: Build SNAPSHOT + run: ./gradlew build + + - name: Generate changelog entries + id: changelog + run: | + # Get changelog from git-semver-plugin + CHANGELOG_CONTENT=$(./gradlew printChangeLog --quiet) + + # Create or update CHANGELOG.md with Unreleased section + if [ -f CHANGELOG.md ]; then + # Check if Unreleased section exists + if grep -q "## \[Unreleased\]" CHANGELOG.md; then + # Replace existing Unreleased section + # Create temp file with new unreleased content + cat > CHANGELOG_NEW.md << 'HEADER' + # Changelog + + All notable changes to this project will be documented in this file. + + The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), + and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + ## [Unreleased] + + HEADER + echo "$CHANGELOG_CONTENT" >> CHANGELOG_NEW.md + echo "" >> CHANGELOG_NEW.md + # Append everything after the old Unreleased section (previous releases) + sed -n '/^## \[[0-9]/,$p' CHANGELOG.md >> CHANGELOG_NEW.md 2>/dev/null || true + mv CHANGELOG_NEW.md CHANGELOG.md + else + # No Unreleased section, add it at the top + cat > CHANGELOG_NEW.md << 'HEADER' + # Changelog + + All notable changes to this project will be documented in this file. + + The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), + and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + ## [Unreleased] + + HEADER + echo "$CHANGELOG_CONTENT" >> CHANGELOG_NEW.md + echo "" >> CHANGELOG_NEW.md + # Append existing content (skip any header) + tail -n +2 CHANGELOG.md >> CHANGELOG_NEW.md 2>/dev/null || true + mv CHANGELOG_NEW.md CHANGELOG.md + fi + else + # Create new CHANGELOG.md + cat > CHANGELOG.md << 'HEADER' + # Changelog + + All notable changes to this project will be documented in this file. + + The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), + and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + ## [Unreleased] + + HEADER + echo "$CHANGELOG_CONTENT" >> CHANGELOG.md + fi + + - name: Check for changelog changes + id: changes + run: | + if git diff --quiet CHANGELOG.md 2>/dev/null; then + echo "changed=false" >> $GITHUB_OUTPUT + else + echo "changed=true" >> $GITHUB_OUTPUT + fi + + - name: Commit changelog updates + if: steps.changes.outputs.changed == 'true' + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add CHANGELOG.md + git commit -m "chore(changelog): update unreleased changes" + git push origin main + + - name: Summary + run: | + echo "### Snapshot Build Complete" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Version:** ${{ steps.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Changelog:** Updated with latest commits" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "To create a release, run the **Cut Release** workflow." >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index eacba70..beaf99a 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -127,9 +127,12 @@ jobs: runs-on: ubuntu-latest steps: - # Checkout the repository code + # Checkout the repository code with full history + # Full history is required for git-semver-plugin to calculate version - name: Checkout code uses: actions/checkout@v4 + with: + fetch-depth: 0 # Set up Java environment using centralized configuration # See .github/actions/setup-java/action.yml to update Java version @@ -207,9 +210,12 @@ jobs: packages: write steps: - # Checkout the repository code + # Checkout the repository code with full history + # Full history is required for git-semver-plugin to calculate version - name: Checkout code uses: actions/checkout@v4 + with: + fetch-depth: 0 # Set up Java environment using centralized configuration # See .github/actions/setup-java/action.yml to update Java version @@ -218,14 +224,14 @@ jobs: # Extract version and determine image tags # Outputs: - # - version: Project version from build.gradle.kts + # - version: Project version from git-semver-plugin # - tags: Comma-separated list of Docker tags to apply # - is_release: Whether this is a release build (from version tag) - name: Extract metadata id: meta run: | - # Get version from build.gradle.kts - VERSION=$(grep '^version = ' build.gradle.kts | sed 's/version = "\(.*\)"/\1/') + # Get version from git-semver-plugin (derived from git tags + commits) + VERSION=$(./gradlew printVersion --quiet) echo "version=$VERSION" >> $GITHUB_OUTPUT # Determine image tags based on trigger type @@ -235,7 +241,7 @@ jobs: echo "tags=$TAG_VERSION,latest" >> $GITHUB_OUTPUT echo "is_release=true" >> $GITHUB_OUTPUT else - # For main branch, append short commit SHA for traceability + # For main branch, use SNAPSHOT version with short commit SHA SHORT_SHA=$(echo ${{ github.sha }} | cut -c1-7) echo "tags=$VERSION-$SHORT_SHA,latest" >> $GITHUB_OUTPUT echo "is_release=false" >> $GITHUB_OUTPUT diff --git a/.github/workflows/cut-release.yml b/.github/workflows/cut-release.yml new file mode 100644 index 0000000..4b7f1f5 --- /dev/null +++ b/.github/workflows/cut-release.yml @@ -0,0 +1,232 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ============================================================================ +# CUT RELEASE WORKFLOW +# ============================================================================ +# +# PURPOSE: +# -------- +# Manually triggered workflow to create a new version release. +# Calculates version from conventional commits, creates tag, and publishes. +# +# WHEN TO USE: +# ------------ +# - When you're ready to create a new development release +# - After accumulating meaningful changes in main +# - This is for development releases, NOT official ASF releases +# +# WHAT IT DOES: +# ------------- +# 1. Calculates version from conventional commits since last tag +# 2. Moves "Unreleased" changelog entries to new version section +# 3. Creates version tag (e.g., v1.0.0) +# 4. Creates GitHub Release with changelog +# 5. Triggers build-and-publish.yml to publish Docker images +# +# FOR OFFICIAL ASF RELEASES: +# -------------------------- +# Use release-publish.yml instead, which requires: +# - RC tag (v1.0.0-rc1) +# - 72-hour ASF vote +# - Publishes to apache/solr-mcp namespace +# +# RELATED WORKFLOWS: +# ------------------ +# - auto-release.yml: Accumulates changelog on merge (no tags) +# - build-and-publish.yml: Publishes Docker images on tag +# - release-publish.yml: Official ASF releases (after vote) + +name: Cut Release + +on: + workflow_dispatch: + inputs: + version_override: + description: 'Override calculated version (leave empty to auto-calculate)' + required: false + type: string + dry_run: + description: 'Dry run (no tag/release created)' + required: false + type: boolean + default: false + +permissions: + contents: write + +env: + JAVA_VERSION: '25' + JAVA_DISTRIBUTION: 'temurin' + +jobs: + release: + name: Create Release + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up JDK ${{ env.JAVA_VERSION }} + uses: actions/setup-java@v4 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: ${{ env.JAVA_DISTRIBUTION }} + cache: 'gradle' + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Calculate version + id: version + run: | + if [ -n "${{ inputs.version_override }}" ]; then + VERSION="${{ inputs.version_override }}" + echo "Using override version: $VERSION" + else + # Get version from git-semver-plugin and remove SNAPSHOT suffix + SNAPSHOT_VERSION=$(./gradlew printVersion --quiet) + VERSION=$(echo "$SNAPSHOT_VERSION" | sed 's/-SNAPSHOT.*//') + echo "Calculated version: $VERSION (from $SNAPSHOT_VERSION)" + fi + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Check if tag already exists + id: check_tag + run: | + if git rev-parse "v${{ steps.version.outputs.version }}" >/dev/null 2>&1; then + echo "exists=true" >> $GITHUB_OUTPUT + echo "::error::Tag v${{ steps.version.outputs.version }} already exists!" + exit 1 + else + echo "exists=false" >> $GITHUB_OUTPUT + fi + + - name: Generate release changelog + id: changelog + run: | + VERSION="${{ steps.version.outputs.version }}" + DATE=$(date +%Y-%m-%d) + + # Get changelog content from git-semver-plugin + CHANGELOG_CONTENT=$(./gradlew printChangeLog --quiet) + + # Save for GitHub Release body + echo "$CHANGELOG_CONTENT" > RELEASE_NOTES.md + + # Update CHANGELOG.md - move Unreleased to new version + if [ -f CHANGELOG.md ]; then + cat > CHANGELOG_NEW.md << HEADER + # Changelog + + All notable changes to this project will be documented in this file. + + The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), + and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + ## [Unreleased] + + ## [$VERSION] - $DATE + + HEADER + echo "$CHANGELOG_CONTENT" >> CHANGELOG_NEW.md + echo "" >> CHANGELOG_NEW.md + # Append previous releases (everything after old Unreleased section) + sed -n '/^## \[[0-9]/,$p' CHANGELOG.md >> CHANGELOG_NEW.md 2>/dev/null || true + mv CHANGELOG_NEW.md CHANGELOG.md + else + cat > CHANGELOG.md << HEADER + # Changelog + + All notable changes to this project will be documented in this file. + + The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), + and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + ## [Unreleased] + + ## [$VERSION] - $DATE + + HEADER + echo "$CHANGELOG_CONTENT" >> CHANGELOG.md + fi + + - name: Show dry run results + if: inputs.dry_run + run: | + echo "### Dry Run Results" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Version:** ${{ steps.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY + echo "**Tag:** v${{ steps.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Changelog:**" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + cat RELEASE_NOTES.md >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**CHANGELOG.md preview:**" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + head -50 CHANGELOG.md >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + + - name: Configure git + if: "!inputs.dry_run" + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Commit changelog and create tag + if: "!inputs.dry_run" + run: | + VERSION="${{ steps.version.outputs.version }}" + + # Commit changelog update + git add CHANGELOG.md + git commit -m "chore(release): release version $VERSION" + + # Create annotated tag + git tag -a "v$VERSION" -m "Release version $VERSION" + + - name: Push changes and tag + if: "!inputs.dry_run" + run: | + git push origin main + git push origin "v${{ steps.version.outputs.version }}" + + - name: Create GitHub Release + if: "!inputs.dry_run" + uses: softprops/action-gh-release@v2 + with: + tag_name: "v${{ steps.version.outputs.version }}" + name: "Release ${{ steps.version.outputs.version }}" + body_path: RELEASE_NOTES.md + draft: false + prerelease: false + + - name: Summary + if: "!inputs.dry_run" + run: | + echo "### Release Created Successfully!" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Version:** ${{ steps.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY + echo "**Tag:** v${{ steps.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Next Steps:**" >> $GITHUB_STEP_SUMMARY + echo "- Docker images will be published automatically by build-and-publish.yml" >> $GITHUB_STEP_SUMMARY + echo "- For official ASF release, create RC tag and run release-publish.yml after vote" >> $GITHUB_STEP_SUMMARY diff --git a/build.gradle.kts b/build.gradle.kts index 201bc32..5f0aecd 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -26,10 +26,44 @@ plugins { alias(libs.plugins.errorprone) alias(libs.plugins.spotless) alias(libs.plugins.jib) + alias(libs.plugins.git.semver) } group = "org.apache.solr" -version = "1.0.0-SNAPSHOT" + +// ============================================================================ +// Semantic Versioning Configuration (git-semver-plugin) +// ============================================================================ +// +// Version is automatically derived from git tags and conventional commits. +// The plugin analyzes commits since the last tag to determine the next version: +// - fix: commits → patch bump (1.0.0 → 1.0.1) +// - feat: commits → minor bump (1.0.0 → 1.1.0) +// - feat!: or BREAKING CHANGE → major bump (1.0.0 → 2.0.0) +// +// Commands: +// ./gradlew printVersion - Show current calculated version +// ./gradlew printChangeLog - Show changelog from commits +// +// Initial Version Setup: +// ---------------------- +// To start at version 1.0.0, create a baseline tag: +// git tag v0.0.0 -m "Initial version baseline" +// git push origin v0.0.0 +// +// Then any feat: commit will bump to 0.1.0, or use the cut-release workflow +// with version_override to set 1.0.0 explicitly for the first release. +// +semver { + // Use "SNAPSHOT" suffix for development builds + defaultPreRelease = "SNAPSHOT" + // Tag format: v1.0.0 + releaseTagNameFormat = "v%s" + // Release commit message format (used by cut-release workflow) + releaseCommitTextFormat = "chore(release): release version %s" +} + +version = semver.version java { toolchain { diff --git a/dev-docs/WORKFLOWS.md b/dev-docs/WORKFLOWS.md index e988d3d..f9429cc 100644 --- a/dev-docs/WORKFLOWS.md +++ b/dev-docs/WORKFLOWS.md @@ -7,11 +7,53 @@ This guide explains when and how to use each GitHub Actions workflow in the proj | Workflow | Purpose | Trigger | Status | Use For | |------------------------------------------------|-----------------------|----------------------|------------|------------------------| | [build-and-publish.yml](#build-and-publishyml) | Development CI/CD | Automatic (push/PR) | ✅ Active | Daily development | +| [auto-release.yml](#auto-releaseyml) | Snapshot builds | Automatic (merge) | ✅ Active | Changelog accumulation | +| [cut-release.yml](#cut-releaseyml) | Cut new release | Manual | ✅ Active | Version tagging | | [release-publish.yml](#release-publishyml) | Official ASF releases | Manual (after vote) | ✅ Active | Production releases | | [nightly-build.yml](#nightly-buildyml) | Nightly builds | Scheduled (2 AM UTC) | ✅ Active | Latest unstable builds | | [atr-release-test.yml](#atr-release-testyml) | ATR testing | Manual (safe mode) | ✅ Ready | Testing ATR workflow | | [atr-release.yml](#atr-releaseyml) | ATR production | Manual (blocked) | ⚠️ Blocked | Future ATR releases | +## Semantic Versioning + +This project uses the [git-semver-plugin](https://github.com/jmongard/Git.SemVersioning.Gradle) for automatic version management based on [Conventional Commits](https://www.conventionalcommits.org/). + +### How Versioning Works + +``` +git tag v1.0.0 + │ + ├── fix: handle null values → patch bump (1.0.1) + ├── feat: add new search filter → minor bump (1.1.0) + └── feat!: breaking API change → major bump (2.0.0) + +Current version: 1.1.0-SNAPSHOT (calculated from commits) +``` + +### Version Commands + +```bash +# Check current calculated version +./gradlew printVersion + +# View changelog from commits +./gradlew printChangeLog + +# Create a release (tag + commit) +./gradlew releaseVersion +``` + +### Commit Message Format + +Use conventional commit prefixes to control version bumps: + +| Prefix | Version Bump | Example | +|--------|--------------|---------| +| `fix:` | Patch (0.0.X) | `fix: handle null pointer in search` | +| `feat:` | Minor (0.X.0) | `feat: add faceted search support` | +| `feat!:` or `BREAKING CHANGE:` | Major (X.0.0) | `feat!: redesign query API` | +| `docs:`, `chore:`, `test:`, `ci:` | No bump | `docs: update README` | + ## Decision Tree: Which Workflow Should I Use? ``` @@ -19,37 +61,107 @@ This guide explains when and how to use each GitHub Actions workflow in the proj │ START: What do you need to do? │ └────────────────────┬────────────────────────────────────────┘ │ - ┌────────────────┼────────────────┐ - │ │ │ - ▼ ▼ ▼ -┌─────────┐ ┌──────────┐ ┌──────────┐ -│ Develop │ │ Release │ │ Test │ -│ Code │ │ Official │ │ ATR │ -└────┬────┘ └─────┬────┘ └─────┬────┘ - │ │ │ - │ │ │ - ▼ ▼ ▼ -┌─────────────┐ ┌───────────┐ ┌──────────┐ -│build-and- │ │ release- │ │atr- │ -│publish.yml │ │ publish │ │release- │ -│ │ │ .yml │ │test.yml │ -│✅ Automatic │ │ │ │ │ -│ on push │ │✅ Manual │ │✅ Manual │ -│ │ │after vote │ │safe mode │ -└─────────────┘ └─────┬─────┘ └──────────┘ + ┌────────────────┼────────────────┬───────────────┐ + │ │ │ │ + ▼ ▼ ▼ ▼ +┌─────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ +│ Develop │ │ Cut a │ │ Official │ │ Test │ +│ Code │ │ Release │ │ ASF Rel │ │ ATR │ +└────┬────┘ └─────┬────┘ └─────┬────┘ └─────┬────┘ + │ │ │ │ + ▼ ▼ ▼ ▼ +┌─────────────┐ ┌───────────┐ ┌───────────┐ ┌──────────┐ +│build-and- │ │cut- │ │ release- │ │atr- │ +│publish.yml │ │release │ │ publish │ │release- │ +│+ auto- │ │ .yml │ │ .yml │ │test.yml │ +│release.yml │ │ │ │ │ │ │ +│✅ Automatic │ │✅ Manual │ │✅ Manual │ │✅ Manual │ +│ on merge │ │ trigger │ │after vote │ │safe mode │ +└─────────────┘ └─────┬─────┘ └───────────┘ └──────────┘ │ - │ After ATR - │ onboarding? + ▼ + ┌───────────────┐ + │ Creates tag │ + │ v1.0.0 │ + │ + CHANGELOG │ + └───────┬───────┘ │ ▼ - ┌──────────┐ - │atr- │ - │release │ - │ .yml │ - │ │ - │⚠️ Future │ - │ (blocked)│ - └──────────┘ + ┌───────────────┐ + │build-and- │ + │publish.yml │ + │(triggered by │ + │ v* tag) │ + └───────────────┘ +``` + +## Complete Release Flow + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ DEVELOPMENT CYCLE │ +├─────────────────────────────────────────────────────────────────────┤ +│ │ +│ PR Created │ +│ │ │ +│ ▼ │ +│ ┌──────────────────────┐ │ +│ │ build-and-publish │ Builds project, runs tests │ +│ │ (PR validation) │ Uploads JAR artifacts (NO Docker images) │ +│ └──────────────────────┘ │ +│ │ +│ PR Merged to main │ +│ │ │ +│ ▼ │ +│ ┌──────────────────────┐ │ +│ │ auto-release.yml │ Builds SNAPSHOT │ +│ │ (Snapshot Build) │ Updates CHANGELOG.md (Unreleased) │ +│ │ │ NO tag created │ +│ └──────────────────────┘ │ +│ │ +│ ... more PRs merged, changelog accumulates ... │ +│ │ +├─────────────────────────────────────────────────────────────────────┤ +│ DEVELOPMENT RELEASE │ +├─────────────────────────────────────────────────────────────────────┤ +│ │ +│ Manual: "Cut Release" triggered │ +│ │ │ +│ ▼ │ +│ ┌──────────────────────┐ │ +│ │ cut-release.yml │ Calculates version (e.g., 1.1.0) │ +│ │ │ Moves Unreleased → [1.1.0] in CHANGELOG │ +│ │ │ Creates tag v1.1.0 │ +│ │ │ Creates GitHub Release │ +│ └──────────┬───────────┘ │ +│ │ │ +│ ▼ │ +│ ┌──────────────────────┐ │ +│ │ build-and-publish │ Triggered by v* tag │ +│ │ (tag trigger) │ Publishes Docker 1.1.0 to personal/GHCR │ +│ └──────────────────────┘ │ +│ │ +├─────────────────────────────────────────────────────────────────────┤ +│ OFFICIAL ASF RELEASE │ +├─────────────────────────────────────────────────────────────────────┤ +│ │ +│ Manual: Create RC tag │ +│ │ git tag v1.1.0-rc1 │ +│ │ │ +│ ▼ │ +│ ┌──────────────────────┐ │ +│ │ 72-hour ASF Vote │ Email to dev@solr.apache.org │ +│ │ (3+ PMC +1 votes) │ Wait for vote to pass │ +│ └──────────┬───────────┘ │ +│ │ │ +│ ▼ │ +│ ┌──────────────────────┐ │ +│ │ release-publish.yml │ Manual trigger after vote │ +│ │ (Official Release) │ Publishes to apache/solr-mcp │ +│ │ │ Publishes to MCP Registry │ +│ └──────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────┘ ``` --- @@ -85,9 +197,20 @@ on: #### What It Does +**On Pull Requests** (build + test only, NO publishing): +1. **Builds** the project with Gradle +2. **Runs tests** and generates coverage reports +3. **Uploads artifacts** to GitHub Actions (downloadable): + - JAR files (`solr-mcp-*.jar`) + - Test results + - Coverage reports +4. ❌ **NO Docker images** are published for PRs + +**On Push to Main / Tags** (full CI/CD): 1. **Builds** the project with Gradle 2. **Runs tests** and generates coverage reports -3. **Publishes Docker images** to: +3. **Uploads artifacts** (same as PRs) +4. **Publishes Docker images** to: - GitHub Container Registry: `ghcr.io/OWNER/solr-mcp:VERSION-SHA` - Docker Hub: `DOCKERHUB_USERNAME/solr-mcp:VERSION-SHA` (if secrets configured) @@ -131,6 +254,167 @@ gh workflow run build-and-publish.yml --- +### auto-release.yml + +**Purpose**: Build SNAPSHOT artifacts and accumulate changelog entries on merge to main + +#### When to Use + +- ✅ Automatic on every merge to `main` +- ✅ Builds and validates SNAPSHOT artifacts +- ✅ Accumulates changelog entries under "Unreleased" section +- ✅ Does NOT create version tags (use `cut-release.yml` for that) + +#### When NOT to Use + +- ❌ This is fully automatic - no manual intervention needed +- ❌ For creating releases (use `cut-release.yml`) +- ❌ For official ASF releases (use `release-publish.yml` after vote) + +#### Triggers + +```yaml +on: + push: + branches: + - main +``` + +#### What It Does + +1. **Builds SNAPSHOT** artifacts with calculated version +2. **Updates CHANGELOG.md** with new commits under "Unreleased" section +3. **Commits changelog** updates back to main +4. **Does NOT create tags** - changes accumulate until `cut-release.yml` is triggered + +#### How It Works + +``` +PR Merged to main + │ + ▼ +┌──────────────────────┐ +│ 1. Build SNAPSHOT │ ./gradlew build +│ (validates code) │ Version: 1.1.0-SNAPSHOT +└──────────┬───────────┘ + │ + ▼ +┌──────────────────────┐ +│ 2. Update changelog │ Adds commits to "Unreleased" +│ (accumulate) │ section in CHANGELOG.md +└──────────┬───────────┘ + │ + ▼ +┌──────────────────────┐ +│ 3. Commit changelog │ "chore(changelog): update +│ (if changed) │ unreleased changes" +└──────────────────────┘ + +No tag created - use cut-release.yml when ready to release +``` + +#### Skipping Changelog Updates + +Changelog and release commits are automatically skipped to prevent infinite loops: + +```yaml +if: "!startsWith(github.event.head_commit.message, 'chore(release):') && + !startsWith(github.event.head_commit.message, 'chore(changelog):')" +``` + +--- + +### cut-release.yml + +**Purpose**: Manually create a new version release with tag and finalized changelog + +#### When to Use + +- ✅ When you're ready to create a new development release +- ✅ After accumulating meaningful changes in main +- ✅ When you want to publish versioned Docker images + +#### When NOT to Use + +- ❌ For official ASF releases (use `release-publish.yml` after vote) +- ❌ For every merge (changes should accumulate first) + +#### Triggers + +```yaml +on: + workflow_dispatch: + inputs: + version_override: # Optional: override calculated version + dry_run: # Optional: preview without creating release +``` + +#### What It Does + +1. **Calculates version** from conventional commits since last tag +2. **Finalizes changelog** - moves "Unreleased" to new version section +3. **Creates version tag** (e.g., `v1.0.0`) +4. **Creates GitHub Release** with changelog +5. **Triggers build-and-publish.yml** to publish Docker images + +#### How It Works + +``` +Manual Trigger: "Cut Release" + │ + ▼ +┌──────────────────────┐ +│ 1. Calculate version │ feat: → minor bump +│ (from commits) │ fix: → patch bump +└──────────┬───────────┘ + │ + ▼ +┌──────────────────────┐ +│ 2. Finalize changelog│ [Unreleased] → [1.1.0] - 2024-01-15 +│ │ +└──────────┬───────────┘ + │ + ▼ +┌──────────────────────┐ +│ 3. Create tag │ git tag v1.1.0 +│ + GitHub Release │ git push --tags +└──────────┬───────────┘ + │ + ▼ +┌──────────────────────┐ +│ build-and-publish │ Triggered by v* tag +│ publishes Docker │ → ghcr.io/*/solr-mcp:1.1.0 +└──────────────────────┘ +``` + +#### How to Use + +**Via GitHub UI:** +1. Go to Actions → Cut Release → Run workflow +2. Optionally override version or enable dry run +3. Click "Run workflow" + +**Via CLI:** +```bash +# Auto-calculate version from commits +gh workflow run cut-release.yml + +# Override version +gh workflow run cut-release.yml -f version_override=1.0.0 + +# Dry run (preview only) +gh workflow run cut-release.yml -f dry_run=true +``` + +#### Version Override + +Use `version_override` for special cases: +- First release: `version_override=1.0.0` +- Forcing a major bump: `version_override=2.0.0` +- Specific version needed: `version_override=1.2.3` + +--- + ### release-publish.yml **Purpose**: Official Apache Software Foundation release publishing @@ -493,16 +777,17 @@ gh workflow run atr-release.yml \ ## Workflow Comparison Matrix -| Feature | build-and-publish | release-publish | nightly-build | atr-release-test | atr-release | -|----------------------|-------------------|-----------------|--------------------|------------------|-------------| -| **Status** | ✅ Active | ✅ Active | ✅ Active | ✅ Ready | ⚠️ Blocked | -| **Trigger** | Automatic | Manual | Scheduled | Manual | Manual | -| **Docker Namespace** | Personal/GHCR | `apache/*` | `apache/*-nightly` | Test | `apache/*` | -| **MCP Registry** | ❌ No | ✅ Yes | ❌ No | ❌ No | ✅ Yes | -| **ASF Vote** | ❌ Not required | ✅ Required | ❌ Not required | ❌ Not required | ✅ Required | -| **Signing** | ❌ No | ⚠️ Manual | ❌ No | ⚠️ Simulated | ✅ Automated | -| **Production Ready** | ❌ No | ✅ Yes | ❌ No | ❌ No | ⚠️ Future | -| **Can Test Now** | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes | ❌ No | +| Feature | build-and-publish | auto-release | cut-release | release-publish | nightly-build | atr-release | +|----------------------|-------------------|-----------------|-----------------|-----------------|--------------------| ------------| +| **Status** | ✅ Active | ✅ Active | ✅ Active | ✅ Active | ✅ Active | ⚠️ Blocked | +| **Trigger** | Automatic | Auto (merge) | Manual | Manual | Scheduled | Manual | +| **Creates Tags** | ❌ No | ❌ No | ✅ Yes | ❌ No | ❌ No | ❌ No | +| **Changelog** | ❌ No | ✅ Accumulates | ✅ Finalizes | ❌ No | ❌ No | ❌ No | +| **Docker Namespace** | Personal/GHCR | N/A | Personal/GHCR | `apache/*` | `apache/*-nightly` | `apache/*` | +| **MCP Registry** | ❌ No | ❌ No | ❌ No | ✅ Yes | ❌ No | ✅ Yes | +| **ASF Vote** | ❌ Not required | ❌ Not required | ❌ Not required | ✅ Required | ❌ Not required | ✅ Required | +| **Signing** | ❌ No | ❌ No | ❌ No | ⚠️ Manual | ❌ No | ✅ Automated | +| **Official Release** | ❌ No | ❌ No | ❌ No | ✅ Yes | ❌ No | ✅ Yes | --- @@ -510,15 +795,46 @@ gh workflow run atr-release.yml \ ### Scenario 1: I merged a PR and want to test the changes -**Use**: `build-and-publish.yml` (automatic) +**Use**: `build-and-publish.yml` + `auto-release.yml` (both automatic) ```bash -# Workflow runs automatically on merge to main -# Find your images at: -# - ghcr.io/apache/solr-mcp:1.0.0-SNAPSHOT-a1b2c3d +# 1. Merge your PR with conventional commit messages +git merge feature-branch # e.g., "feat: add new search filter" +git push origin main + +# 2. auto-release.yml automatically: +# - Builds SNAPSHOT artifacts +# - Updates CHANGELOG.md (Unreleased section) +# - NO tag created yet + +# 3. build-and-publish.yml automatically: +# - Builds Docker image with SNAPSHOT version +# - Publishes to ghcr.io/*/solr-mcp:X.Y.Z-SNAPSHOT-SHA +``` + +### Scenario 2: I want to create a version release + +**Use**: `cut-release.yml` (manual trigger) + +```bash +# 1. Ensure changes have accumulated in main +./gradlew printVersion # Check calculated version +./gradlew printChangeLog # Preview changelog + +# 2. Trigger the Cut Release workflow +gh workflow run cut-release.yml + +# Or with version override for first release: +gh workflow run cut-release.yml -f version_override=1.0.0 + +# 3. Workflow automatically: +# - Creates tag v1.0.0 +# - Finalizes CHANGELOG.md +# - Creates GitHub Release +# - Triggers Docker publish to personal/GHCR ``` -### Scenario 2: I want to create an official release +### Scenario 3: I want to create an official ASF release **Use**: `release-publish.yml` (manual after vote) @@ -535,7 +851,7 @@ gh workflow run release-publish.yml \ -f release_candidate=rc1 ``` -### Scenario 3: I want to test the latest unreleased code +### Scenario 4: I want to test the latest unreleased code **Use**: `nightly-build.yml` (automatic daily) @@ -544,7 +860,7 @@ gh workflow run release-publish.yml \ docker pull apache/solr-mcp-nightly:latest-nightly ``` -### Scenario 4: I want to prepare for ATR +### Scenario 5: I want to prepare for ATR **Use**: `atr-release-test.yml` (manual testing) @@ -554,7 +870,7 @@ gh workflow run atr-release-test.yml \ -f dry_run=true # Safe mode - no uploads ``` -### Scenario 5: I'm ready to use ATR for releases +### Scenario 6: I'm ready to use ATR for releases **Use**: `atr-release.yml` (blocked - see prerequisites) @@ -636,7 +952,14 @@ gh secret set ASF_USERNAME --body "your-asf-id" ## Quick Command Reference ```bash +# Semantic versioning commands (git-semver-plugin) +./gradlew printVersion # Show current calculated version +./gradlew printChangeLog # Show changelog from commits + # Trigger workflows manually +gh workflow run cut-release.yml # Cut a new release +gh workflow run cut-release.yml -f version_override=1.0.0 # First release +gh workflow run cut-release.yml -f dry_run=true # Preview only gh workflow run build-and-publish.yml gh workflow run release-publish.yml -f release_version=1.0.0 -f release_candidate=rc1 gh workflow run nightly-build.yml @@ -664,5 +987,5 @@ git push origin :refs/tags/v1.0.0-rc1 # Delete remote --- -**Last Updated**: 2025-01-12 +**Last Updated**: 2026-01-06 **Workflows Version**: Compatible with all workflows as of this date \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e4463b7..c2b47e5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,6 +5,7 @@ spring-dependency-management = "1.1.7" errorprone-plugin = "4.2.0" jib = "3.4.5" spotless = "7.0.2" +git-semver = "0.18.0" # Main dependencies spring-ai = "1.1.2" @@ -111,4 +112,5 @@ spring-boot = { id = "org.springframework.boot", version.ref = "spring-boot" } spring-dependency-management = { id = "io.spring.dependency-management", version.ref = "spring-dependency-management" } errorprone = { id = "net.ltgt.errorprone", version.ref = "errorprone-plugin" } jib = { id = "com.google.cloud.tools.jib", version.ref = "jib" } -spotless = { id = "com.diffplug.spotless", version.ref = "spotless" } \ No newline at end of file +spotless = { id = "com.diffplug.spotless", version.ref = "spotless" } +git-semver = { id = "com.github.jmongard.git-semver-plugin", version.ref = "git-semver" } \ No newline at end of file