From ded54d33469818a86707162d2e26a376fc52f716 Mon Sep 17 00:00:00 2001 From: Stanislav Panchenko Date: Wed, 15 Apr 2026 00:17:57 +0500 Subject: [PATCH] =?UTF-8?q?ci:=20GitHub=20Actions=20=E2=80=94=20build=20+?= =?UTF-8?q?=20release=20workflows?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds two workflows modelled after the NanoLimbo setup, adapted for the AbstractMenus stack (JDK 21, Gradle wrapper, shadowJar target). - .github/workflows/build.yml Triggers on pushes to master/main/develop + feature/task/fix branches, pull requests, and manual dispatch. Runs `./gradlew build shadowJar` on Ubuntu with Temurin JDK 21 and the Gradle cache (via gradle/actions/setup-gradle@v4). Publishes JUnit test report (mikepenz/action-junit-report) and the shaded JAR as an artifact (14-day retention). - .github/workflows/release.yml Triggers when a GitHub release is published (and supports manual re-run with a tag input). Rebuilds shadowJar from the tagged commit and attaches `AbstractMenus-.jar` to the release via softprops/action-gh-release@v2. Includes a safety check: if the tag version (with optional leading `v` stripped) does not match `version '...'` in build.gradle, the workflow fails loudly rather than publishing a mismatched asset. - .github/RELEASE.md Short release-flow guide: branching model (GitHub Flow with a develop integration branch), the five-step cut-a-release procedure, how to re-attach a JAR to an existing release, a hotfix flow, and notes on pre-releases. NanoLimbo uses actions/upload-release-asset@v1 which is deprecated; swapped for the actively-maintained softprops/action-gh-release@v2. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/RELEASE.md | 88 +++++++++++++++++++++++++++++++++++ .github/workflows/build.yml | 62 ++++++++++++++++++++++++ .github/workflows/release.yml | 65 ++++++++++++++++++++++++++ 3 files changed, 215 insertions(+) create mode 100644 .github/RELEASE.md create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/RELEASE.md b/.github/RELEASE.md new file mode 100644 index 0000000..fc1c55a --- /dev/null +++ b/.github/RELEASE.md @@ -0,0 +1,88 @@ +# Release flow + +Short guide for cutting a release. + +## Branching model (GitHub Flow) + +One long-lived branch — `master` (or `main`, whichever the repo is on). +Feature work lives on `feature/**`, `task/**`, `fix/**` branches that are +merged back via pull requests. `develop` is kept for staging pre-release +work before a tag. + +- `master` — always deployable, tagged on every release +- `develop` — integration branch for the next release +- `feature/*`, `fix/*`, `task/*` — short-lived, merged into `develop` (or + straight to `master` for hotfixes) through a PR + +Both `master` and `develop` are protected by the [Build workflow](.github/workflows/build.yml) +— every push and pull request runs `./gradlew build shadowJar` and publishes +the JAR as an artifact, plus a JUnit test report. + +## Cutting a release + +1. **Bump the version.** Edit `build.gradle`: + ```gradle + version '1.19.0' + ``` + Commit with a message like `chore(release): 1.19.0`. The [Release workflow](.github/workflows/release.yml) + compares the tag against this field and aborts if they disagree. + +2. **Merge `develop` → `master`** via PR (or push directly if you are the + sole maintainer). + +3. **Create a release on GitHub.** Either through the web UI or the CLI: + ```bash + gh release create 1.19.0 \ + --title "AbstractMenus 1.19.0" \ + --notes-file CHANGELOG.md + ``` + Tag format: either `1.19.0` or `v1.19.0` — the workflow strips the + leading `v` before comparing. + +4. **Release workflow fires automatically** (`on: release: types: + [published]`), rebuilds the shaded JAR from the tagged commit, verifies + the version matches, and uploads `AbstractMenus-1.19.0.jar` as a release + asset. + +5. Done. The release page now has the JAR; users pull it straight from + there. + +## Re-attaching a JAR to an existing release + +If the build failed or the asset was deleted, trigger the Release workflow +manually: + +- GitHub UI → Actions → Release → Run workflow → supply the existing tag + (e.g. `1.18.0`). +- Or via CLI: `gh workflow run release.yml -f tag=1.18.0`. + +The workflow will rebuild from that tag's commit and re-upload. If an +asset with the same name already exists, `softprops/action-gh-release` +overwrites it. + +## Hotfix flow + +For a patch release that doesn't touch `develop`: + +1. Branch from `master`: `git checkout -b fix/bad-null-deref master` +2. Fix, PR, merge. +3. Bump `version` on `master`: `1.19.0` → `1.19.1`. Commit. +4. Cut the GitHub release as in the main flow. The workflow picks up the + new version automatically. +5. Cherry-pick the fix commit back to `develop` so it isn't lost. + +## Pre-releases + +GitHub supports a "Set as a pre-release" checkbox on the release form. +That marks the tag as a pre-release (e.g. `1.19.0-rc1`, `1.19.0-beta.2`) +— the Release workflow still builds and attaches the JAR, users just see +the pre-release badge on the release page. + +For a pre-release version bump, use Gradle-compatible syntax: + +```gradle +version '1.19.0-rc1' +``` + +The workflow's version-match check strips only a leading `v`, so +`1.19.0-rc1` and `v1.19.0-rc1` both work. diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..b7c52f0 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,62 @@ +name: Build + +on: + push: + branches: + - master + - main + - develop + - 'feature/**' + - 'task/**' + - 'fix/**' + pull_request: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + build: + name: Build + test on JDK 21 + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v4 + with: + gradle-home-cache-cleanup: true + + - name: Build + tests + run: ./gradlew --no-daemon build shadowJar + + - name: Publish test report + if: always() + uses: mikepenz/action-junit-report@v5 + with: + report_paths: '**/build/test-results/test/TEST-*.xml' + fail_on_failure: true + require_tests: false + + - name: Resolve JAR path + id: jar + run: echo "path=$(ls build/libs/AbstractMenus-*.jar | head -n1)" >> "$GITHUB_OUTPUT" + + - name: Upload shaded JAR + uses: actions/upload-artifact@v4 + with: + name: AbstractMenus-jar + path: ${{ steps.jar.outputs.path }} + if-no-files-found: error + retention-days: 14 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..0a09a3b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,65 @@ +name: Release + +on: + release: + types: [published] + workflow_dispatch: + inputs: + tag: + description: 'Existing release tag to attach a rebuilt JAR to' + required: true + type: string + +permissions: + contents: write + +jobs: + attach-jar: + name: Build + attach JAR to release + runs-on: ubuntu-latest + steps: + - name: Checkout (release tag) + uses: actions/checkout@v4 + with: + ref: ${{ github.event.release.tag_name || inputs.tag }} + fetch-depth: 0 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v4 + + - name: Verify tag matches project version + run: | + TAG="${{ github.event.release.tag_name || inputs.tag }}" + # Strip a leading 'v' if present (v1.18.0 → 1.18.0) + TAG_VERSION="${TAG#v}" + GRADLE_VERSION=$(grep -E "^version\s+['\"]" build.gradle | sed -E "s/.*['\"]([^'\"]+)['\"].*/\1/") + echo "release tag: $TAG" + echo "tag version: $TAG_VERSION" + echo "gradle version: $GRADLE_VERSION" + if [ "$TAG_VERSION" != "$GRADLE_VERSION" ]; then + echo "::error::Release tag version ($TAG_VERSION) does not match build.gradle version ($GRADLE_VERSION). Bump build.gradle before tagging, or retag." + exit 1 + fi + + - name: Build shaded JAR (skip tests — already run in Build workflow on the tagged commit) + run: ./gradlew --no-daemon shadowJar + + - name: Resolve JAR path + id: jar + run: | + JAR=$(ls build/libs/AbstractMenus-*.jar | head -n1) + echo "path=$JAR" >> "$GITHUB_OUTPUT" + echo "name=$(basename "$JAR")" >> "$GITHUB_OUTPUT" + + - name: Attach JAR to release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ github.event.release.tag_name || inputs.tag }} + files: ${{ steps.jar.outputs.path }} + fail_on_unmatched_files: true