From a71dbb3ce221209714afead1b8cbc504572150dd Mon Sep 17 00:00:00 2001 From: lyn1dev <78355114+lyn1dev@users.noreply.github.com> Date: Sun, 8 Nov 2026 14:39:41 +0100 Subject: [PATCH] Add a tag-driven release workflow and cache Gradle in CI The build workflow already covered pushes and pull requests, but releases were still assembled by hand: tag a commit, download the CI artifact, and upload the jar to a new release. This adds a release workflow that builds the mod on a clean runner and publishes it, so a released jar can never come from a dirty or stale build. build.yml gains: - Gradle caching and wrapper validation via gradle/actions, which also makes the two-OS matrix noticeably faster. - A concurrency group so a new push cancels the superseded PR run. - fail-fast: false, so a Windows failure does not hide the Linux result. - Read-only token permissions and a workflow_dispatch trigger. - Artifacts named after the mod version and commit instead of a shared "Artifacts" name that collides across branches. release.yml runs on v* tags (and manually for an existing tag), builds with the same JDK 25 / Gradle setup, warns when the tag and mod_version disagree, and either creates the release with generated notes or replaces the assets of one that already exists. --- .github/workflows/build.yml | 49 +++++++++++++-- .github/workflows/release.yml | 109 ++++++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 213890e..56dec04 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,13 +2,35 @@ # and submitted pull request. This can help catch issues that only occur on # certain platforms or Java versions, and provides a first line of defence # against bad commits. +# +# Tag pushes (v*) additionally run release.yml, which publishes a GitHub +# Release with the remapped mod jar. name: build -on: [pull_request, push] + +on: + push: + branches: + - '**' + pull_request: + workflow_dispatch: + +# Read-only token: this workflow only builds and uploads artifacts. +permissions: + contents: read + +# Superseded runs are wasted CI time. On pull requests a new push cancels the +# previous run; on branch pushes the runs are left alone so every commit keeps +# a result (useful for release bookkeeping). +concurrency: + group: build-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: build: strategy: + # A failure on one OS/Java version should not hide the result of the other. + fail-fast: false matrix: # Use these Java versions java: [ @@ -20,21 +42,38 @@ jobs: steps: - name: checkout repository uses: actions/checkout@v7 + - name: validate gradle wrapper uses: gradle/actions/wrapper-validation@v6 + - name: setup jdk ${{ matrix.java }} uses: actions/setup-java@v6 with: java-version: ${{ matrix.java }} distribution: 'microsoft' + + - name: setup gradle + uses: gradle/actions/setup-gradle@v6 + - name: make gradle wrapper executable if: ${{ runner.os != 'Windows' }} run: chmod +x ./gradlew + + - name: read mod version + id: version + shell: bash + run: | + version=$(grep -E '^mod_version=' gradle.properties | cut -d'=' -f2- | tr -d '\r') + echo "mod_version=${version}" >> "$GITHUB_OUTPUT" + - name: build - run: ./gradlew build + run: ./gradlew build --stacktrace + - name: capture build artifacts - if: ${{ runner.os == 'Linux' && matrix.java == '25' }} # Only upload artifacts built from latest java on one OS + if: ${{ runner.os == 'Linux' && matrix.java == 25 }} # Only upload artifacts built from latest java on one OS uses: actions/upload-artifact@v7 with: - name: Artifacts - path: build/libs/ \ No newline at end of file + name: advancedcrosshair-${{ steps.version.outputs.mod_version }}-${{ github.sha }} + path: build/libs/*.jar + if-no-files-found: error + retention-days: 30 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..c3f1f33 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,109 @@ +# Publishes a GitHub Release whenever a version tag (v*) is pushed. +# +# The release is created from the same Gradle build CI runs, so a release can +# never contain a jar that did not compile on a clean runner. Re-running the +# workflow for an existing tag updates the assets instead of failing. + +name: release + +on: + push: + tags: + - 'v*' + workflow_dispatch: + inputs: + tag: + description: 'Existing tag to publish (e.g. v1.3.1)' + required: true + +permissions: + contents: write + +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false + +jobs: + release: + runs-on: ubuntu-22.04 + steps: + # On a tag push the tag is github.ref_name; on a manual run it comes from + # the input, because GITHUB_REF_NAME would otherwise be the branch. + - name: resolve tag + id: tag + env: + INPUT_TAG: ${{ inputs.tag }} + REF_NAME: ${{ github.ref_name }} + run: | + tag="${INPUT_TAG:-$REF_NAME}" + echo "name=${tag}" >> "$GITHUB_OUTPUT" + echo "Publishing ${tag}" + + - name: checkout repository + uses: actions/checkout@v7 + with: + ref: refs/tags/${{ steps.tag.outputs.name }} + fetch-depth: 0 # --generate-notes needs the previous tags + + - name: validate gradle wrapper + uses: gradle/actions/wrapper-validation@v6 + + - name: setup jdk 25 + uses: actions/setup-java@v6 + with: + java-version: '25' + distribution: 'microsoft' + + - name: setup gradle + uses: gradle/actions/setup-gradle@v6 + + - name: make gradle wrapper executable + run: chmod +x ./gradlew + + - name: build + run: ./gradlew build --stacktrace + + - name: read mod version + id: version + run: | + version=$(grep -E '^mod_version=' gradle.properties | cut -d'=' -f2- | tr -d '\r') + echo "mod_version=${version}" >> "$GITHUB_OUTPUT" + + # The mod_version is the source of truth in the jar; the tag is only the + # label. A mismatch is usually a forgotten bump, so surface it loudly but + # do not block an otherwise valid release. + - name: check tag matches mod version + env: + TAG_NAME: ${{ steps.tag.outputs.name }} + MOD_VERSION: ${{ steps.version.outputs.mod_version }} + run: | + tag="${TAG_NAME#v}" + case "$tag" in + "$MOD_VERSION"|"$MOD_VERSION"-*) + echo "Tag $TAG_NAME matches mod_version $MOD_VERSION." ;; + *) + echo "::warning::Tag $TAG_NAME does not match mod_version $MOD_VERSION in gradle.properties." ;; + esac + + # Ship the remapped mod jar only, matching the existing release convention + # (sources and the -dev jar are build by-products, not player downloads). + - name: collect release assets + run: | + mkdir -p release-assets + find build/libs -maxdepth 1 -name '*.jar' ! -name '*-dev.jar' ! -name '*-sources.jar' -exec cp {} release-assets/ \; + test -n "$(ls -A release-assets)" || { echo "::error::No release jar found in build/libs"; exit 1; } + ls -l release-assets + + - name: publish release + env: + GH_TOKEN: ${{ github.token }} + TAG_NAME: ${{ steps.tag.outputs.name }} + run: | + if gh release view "$TAG_NAME" >/dev/null 2>&1; then + echo "Release $TAG_NAME already exists; uploading assets." + gh release upload "$TAG_NAME" release-assets/*.jar --clobber + else + gh release create "$TAG_NAME" release-assets/*.jar \ + --title "$TAG_NAME" \ + --generate-notes + fi