diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2396b2b9..596ca423 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -39,7 +39,7 @@ on: required: true type: string release_branch: - description: 'Release branch (e.g., release/2.3)' + description: 'Release branch (e.g., release/2.3._)' required: true type: string next_snapshot: @@ -59,6 +59,7 @@ defaults: env: RELEASE_VERSION: ${{ inputs.release_version }} RELEASE_TAG: v${{ inputs.release_version }} + RC_TAG: v${{ inputs.release_version }}_RC jobs: # ============================================================ @@ -117,6 +118,15 @@ jobs: echo "::error::Tag v${{ inputs.release_version }} already exists on remote" exit 1 fi + # Check for leftover RC tag + if git rev-parse "${RC_TAG}" >/dev/null 2>&1; then + echo "::error::RC tag ${RC_TAG} already exists locally. Clean up with: git tag -d ${RC_TAG}" + exit 1 + fi + if git ls-remote --tags origin "refs/tags/${RC_TAG}" | grep -q .; then + echo "::error::RC tag ${RC_TAG} already exists on remote. Clean up with: git push origin :refs/tags/${RC_TAG}" + exit 1 + fi echo "Tag does not exist - OK" - name: Check if release branch exists @@ -338,18 +348,18 @@ jobs: echo "sha=${SHA}" >> $GITHUB_OUTPUT echo "Release commit: ${SHA}" - - name: Create tag + - name: Create RC tag if: ${{ inputs.dry_run != true }} run: | - git tag -a "${RELEASE_TAG}" -m "Release ${RELEASE_TAG}" - echo "Created tag ${RELEASE_TAG}" + git tag -a "${RC_TAG}" -m "Release candidate ${RELEASE_TAG}" + echo "Created RC tag ${RC_TAG}" - - name: Push branch and tag + - name: Push branch and RC tag if: ${{ inputs.dry_run != true }} run: | git push origin "${{ inputs.release_branch }}" - git push origin "${RELEASE_TAG}" - echo "Pushed branch and tag" + git push origin "${RC_TAG}" + echo "Pushed branch and RC tag" - name: Dry run summary if: ${{ inputs.dry_run == true }} @@ -357,7 +367,7 @@ jobs: echo "::warning::DRY RUN - Branch and tag NOT pushed" echo "Would have pushed:" echo " - Branch: ${{ inputs.release_branch }}" - echo " - Tag: ${RELEASE_TAG}" + echo " - RC Tag: ${RC_TAG}" # ============================================================ # Job 5: Stage to Maven Central (does NOT release) @@ -368,10 +378,10 @@ jobs: runs-on: ubuntu-latest if: ${{ inputs.dry_run != true }} steps: - - name: Checkout release tag + - name: Checkout RC tag uses: actions/checkout@v6 with: - ref: ${{ env.RELEASE_TAG }} + ref: ${{ env.RC_TAG }} - name: Cache Java binaries id: cache-java @@ -547,6 +557,46 @@ jobs: git push || echo "⚠️ Push failed - update catalog manually at https://github.com/btraceio/jbang-catalog" fi + # ============================================================ + # Job 5d: Finalize release tag (RC -> final) + # ============================================================ + finalize-tag: + name: Finalize Release Tag + needs: [prepare-release, wait-for-maven] + runs-on: ubuntu-latest + if: ${{ inputs.dry_run != true }} + steps: + - name: Checkout at release SHA + uses: actions/checkout@v6 + with: + ref: ${{ needs.prepare-release.outputs.release_sha }} + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Fetch tags + run: git fetch origin --tags + + - name: Configure Git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Create final tag + run: | + git tag -a "${RELEASE_TAG}" -m "Release ${RELEASE_TAG}" + echo "Created final tag ${RELEASE_TAG}" + + - name: Push final tag + run: | + git push origin "${RELEASE_TAG}" + echo "Pushed final tag ${RELEASE_TAG}" + + - name: Delete RC tag + run: | + git tag -d "${RC_TAG}" || true + git push origin ":refs/tags/${RC_TAG}" || true + echo "Deleted RC tag ${RC_TAG}" + # ============================================================ # Job 6: Build distribution packages # ============================================================ @@ -555,10 +605,10 @@ jobs: needs: prepare-release runs-on: ubuntu-latest steps: - - name: Checkout release tag + - name: Checkout release commit uses: actions/checkout@v6 with: - ref: ${{ inputs.dry_run == true && inputs.commit_sha || env.RELEASE_TAG }} + ref: ${{ inputs.dry_run == true && inputs.commit_sha || env.RC_TAG }} - name: Cache Java binaries id: cache-java @@ -612,7 +662,7 @@ jobs: # ============================================================ create-github-release: name: Create GitHub Release - needs: [build-distributions, wait-for-maven] + needs: [build-distributions, finalize-tag] runs-on: ubuntu-latest if: ${{ inputs.dry_run != true }} steps: @@ -628,6 +678,25 @@ jobs: name: release-distributions path: distributions + - name: Download Maven artifact + continue-on-error: true + run: | + VERSION="${{ inputs.release_version }}" + BASE_URL="https://repo1.maven.org/maven2/io/btrace/btrace/${VERSION}" + mkdir -p maven-artifacts + MAX_ATTEMPTS=40 + for i in $(seq 1 $MAX_ATTEMPTS); do + if curl -fSL "${BASE_URL}/btrace-${VERSION}.jar" \ + -o maven-artifacts/btrace-${VERSION}.jar; then + echo "Downloaded btrace-${VERSION}.jar" + ls -la maven-artifacts/ + exit 0 + fi + echo "Attempt ${i}/${MAX_ATTEMPTS} failed, retrying in 30s..." + sleep 30 + done + echo "::warning::Failed to download Maven JAR after ${MAX_ATTEMPTS} attempts (~20 minutes). GitHub release will proceed without it." + - name: Check for no-release-notes label id: check-label run: | @@ -653,6 +722,7 @@ jobs: distributions/btrace-v${{ inputs.release_version }}-sdkman-bin.zip distributions/*.deb distributions/*.rpm + maven-artifacts/* # ============================================================ # Job 8: Update SDKMan @@ -861,7 +931,7 @@ jobs: # ============================================================ summary: name: Release Summary - needs: [create-github-release, update-sdkman, update-milestones, update-jbang-catalog] + needs: [create-github-release, update-sdkman, update-milestones, update-jbang-catalog, finalize-tag] runs-on: ubuntu-latest if: always() steps: @@ -915,6 +985,7 @@ jobs: echo "| Prepare Release | ${{ needs.prepare-release.result || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY echo "| Stage Maven | ${{ needs.stage-maven.result || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY echo "| Wait for Maven | ${{ needs.wait-for-maven.result || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY + echo "| Finalize Tag | ${{ needs.finalize-tag.result || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY echo "| GitHub Release | ${{ needs.create-github-release.result || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY echo "| SDKMan | ${{ needs.update-sdkman.result || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY echo "| JBang Catalog | ${{ needs.update-jbang-catalog.result || 'skipped' }} |" >> $GITHUB_STEP_SUMMARY diff --git a/scripts/release.sh b/scripts/release.sh index 9fd89bb0..c2fec739 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -11,7 +11,7 @@ # Examples: # ./scripts/release.sh minor # Minor release from develop # ./scripts/release.sh major # Major release from develop -# ./scripts/release.sh patch release/2.3 # Patch release from release/2.3 +# ./scripts/release.sh patch release/2.3._ # Patch release from release/2.3._ # # Environment variables: # DRY_RUN=true Show what would be done without triggering workflow @@ -73,20 +73,20 @@ Arguments: Release Types: major Bump major version (e.g., 2.3.0-SNAPSHOT -> 3.0.0) Next develop: 3.1.0-SNAPSHOT - Creates: release/3.0 branch + Creates: release/3.0._ branch minor Release current version (e.g., 2.3.0-SNAPSHOT -> 2.3.0) Next develop: 2.4.0-SNAPSHOT - Creates: release/2.3 branch + Creates: release/2.3._ branch patch Bump patch version on release branch (e.g., 2.3.1-SNAPSHOT -> 2.3.1) Next release branch: 2.3.2-SNAPSHOT - Requires: release/X.Y branch as source + Requires: release/X.Y._ branch as source Examples: $(basename "$0") minor # Release 2.3.0 from develop $(basename "$0") major # Release 3.0.0 from develop - $(basename "$0") patch release/2.3 # Release 2.3.1 from release/2.3 + $(basename "$0") patch release/2.3._ # Release 2.3.1 from release/2.3._ Environment: DRY_RUN=true Show what would happen without triggering the workflow @@ -224,10 +224,10 @@ pick_commit() { pick_release_branch() { # Get release branches sorted by version (newest first) local branches - branches=$(git branch -a --list '*release/*' | sed 's/.*\(release\/[0-9]*\.[0-9]*\).*/\1/' | sort -t. -k1,1nr -k2,2nr | uniq) + branches=$(git branch -a --list '*release/*' | sed 's/.*\(release\/[0-9]*\.[0-9]*\._\).*/\1/' | sort -t. -k1,1nr -k2,2nr | uniq) if [[ -z "${branches}" ]]; then - error "No release branches found (expected pattern: release/X.Y)" + error "No release branches found (expected pattern: release/X.Y._)" exit 1 fi @@ -410,21 +410,21 @@ calculate_versions() { release_version="$((major + 1)).0.0" next_develop_snapshot="$((major + 1)).1.0-SNAPSHOT" next_release_snapshot="$((major + 1)).0.1-SNAPSHOT" - release_branch="release/$((major + 1)).0" + release_branch="release/$((major + 1)).0._" ;; minor) - # Minor: X.Y.Z-SNAPSHOT -> X.Y.Z (just drop SNAPSHOT) - release_version="${major}.${minor}.${patch}" + # Minor: X.Y.Z-SNAPSHOT -> X.Y.0 (always .0 for minor releases) + release_version="${major}.${minor}.0" next_develop_snapshot="${major}.$((minor + 1)).0-SNAPSHOT" - next_release_snapshot="${major}.${minor}.$((patch + 1))-SNAPSHOT" - release_branch="release/${major}.${minor}" + next_release_snapshot="${major}.${minor}.1-SNAPSHOT" + release_branch="release/${major}.${minor}._" ;; patch) # Patch: X.Y.Z-SNAPSHOT -> X.Y.Z release_version="${major}.${minor}.${patch}" next_develop_snapshot="" # Not updated for patch releases next_release_snapshot="${major}.${minor}.$((patch + 1))-SNAPSHOT" - release_branch="release/${major}.${minor}" + release_branch="release/${major}.${minor}._" ;; *) error "Invalid release type: ${release_type}" @@ -463,8 +463,8 @@ validate_source_ref() { fi ;; patch) - # Must be from a release/X.Y branch or a commit from one - if [[ "${source_ref}" =~ ^release/[0-9]+\.[0-9]+$ ]]; then + # Must be from a release/X.Y._ branch or a commit from one + if [[ "${source_ref}" =~ ^release/[0-9]+\.[0-9]+\._$ ]]; then # It's a branch name, check if it exists if ! git show-ref --verify --quiet "refs/heads/${source_ref}" && \ ! git show-ref --verify --quiet "refs/remotes/origin/${source_ref}"; then @@ -480,7 +480,7 @@ validate_source_ref() { fi fi else - error "Patch releases must be from a release/X.Y branch." + error "Patch releases must be from a release/X.Y._ branch." error "Got: ${source_ref}" exit 1 fi @@ -506,6 +506,19 @@ check_tag_exists() { error "Tag v${tag} already exists on remote." exit 1 fi + + # Check for leftover RC tag (locally and on remote) + if git rev-parse "v${tag}_RC" &> /dev/null; then + error "RC tag v${tag}_RC already exists locally." + error "Clean up with: git tag -d v${tag}_RC" + exit 1 + fi + + if git ls-remote --tags origin "refs/tags/v${tag}_RC" | grep -q .; then + error "RC tag v${tag}_RC already exists on remote." + error "Clean up with: git push origin :refs/tags/v${tag}_RC && git tag -d v${tag}_RC" + exit 1 + fi } ####################################### @@ -642,7 +655,7 @@ main() { ;; patch) error "Patch releases require a release branch." - echo "Usage: $(basename "$0") patch release/X.Y" + echo "Usage: $(basename "$0") patch release/X.Y._" exit 1 ;; esac