hammer-editor/.github/workflows/prepare-release.yml
Adam Brown 3465c79041
Fix release jobs failing with 403 by granting contents:write (#768)
The linux, macos, server, android, and set-release-body jobs in the
release workflow were missing a `permissions: contents: write` block.
With the repository's default read-only GITHUB_TOKEN, the
ncipollo/release-action step in those jobs failed with
"Error 403: Resource not accessible by integration" when trying to
create/update the release.

The windows, snap, and flatpak jobs already declared this permission
and succeeded, which is why only some jobs failed. Add the same block
to the remaining jobs so every release-creating job can write to the
release.
2026-07-21 09:02:35 -07:00

609 lines
19 KiB
YAML

name: Releases
on:
push:
tags:
- 'v*'
jobs:
tests:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Set up JDK
uses: actions/setup-java@v5
with:
distribution: 'adopt'
java-version: 21
cache: gradle
- name: Run Unit Tests
run: ./gradlew koverXmlReport
- name: Run Server Unit Tests
run: ./gradlew :server:test
- name: Run Server Migration Tests
run: ./gradlew :server:verifySqlDelightMigration
- name: Run Round-Trip Sync Integration Tests
id: integration-tests
run: ./gradlew :integrationTests:jvmTest
- name: Upload integration test report on failure
if: failure() && steps.integration-tests.outcome == 'failure'
uses: actions/upload-artifact@v7
with:
name: integration-tests-report
path: |
integrationTests/build/reports/tests/jvmTest/
integrationTests/build/test-results/jvmTest/
windows:
needs: tests
runs-on: windows-latest
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Set up JBR
uses: ./.github/actions/setup-jbr
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Gradle caching
uses: actions/cache@v6
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Package Windows ".msi" distribution
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0
with:
timeout_minutes: 30
max_attempts: 3
shell: pwsh
command: ./gradlew :desktop:packageReleaseMsi
- name: Rename MSI Artifact
shell: cmd
run: |
setlocal enabledelayedexpansion
set "pattern=hammer-*.msi"
set "newName=hammer.msi"
set "foundFile="
for /r %%i in (%pattern%) do (
set "foundFile=%%i"
)
if defined foundFile (
echo Renaming !foundFile! to %newName%
ren "!foundFile!" "%newName%"
) else (
echo No file matching pattern found.
)
endlocal
echo Successfully renamed file to "hammer.msi"
- name: Package Windows ".exe" distribution
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0
with:
timeout_minutes: 30
max_attempts: 3
shell: pwsh
command: ./gradlew :desktop:packageReleaseExe
- name: Rename EXE Artifact
shell: cmd
run: |
setlocal enabledelayedexpansion
set "pattern=hammer-*.exe"
set "newName=hammer.exe"
set "foundFile="
for /r %%i in (%pattern%) do (
set "foundFile=%%i"
)
if defined foundFile (
echo Renaming !foundFile! to %newName%
ren "!foundFile!" "%newName%"
) else (
echo No file matching pattern found.
)
endlocal
echo Successfully renamed file to "hammer.exe"
- name: Create Release
uses: ncipollo/release-action@339a81892b84b4eeb0f6e744e4574d79d0d9b8dd # v1.21.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
draft: true
prerelease: true
artifactErrorsFailBuild: true
allowUpdates: true
artifacts: "${{ github.workspace }}\\desktop\\build\\installers\\main-release\\msi\\hammer.msi,${{ github.workspace }}\\desktop\\build\\installers\\main-release\\exe\\hammer.exe"
linux:
needs: tests
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Set up JBR
uses: ./.github/actions/setup-jbr
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Gradle caching
uses: actions/cache@v6
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
# appimagetool and the AppImage runtime are pinned in linuxDistributions.kt;
# cache them so buildDistAppImage doesn't re-download them every release.
- name: Cache appimagetool
uses: actions/cache@v6
with:
path: |
appimagetool-x86_64.AppImage
appimage-runtime-x86_64
key: appimagetool-1.9.1-runtime-20251108
# buildDistAppImage generates the .zsync delta file with the system zsyncmake;
# appimagetool's bundled copy is broken, so install the system zsync package.
- name: Install zsync
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0
with:
timeout_minutes: 10
max_attempts: 3
command: |
sudo apt-get update
sudo apt-get install -y zsync
- name: Package Linux ".deb" distribution
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0
with:
timeout_minutes: 30
max_attempts: 3
command: ./gradlew :desktop:packageReleaseDeb
- name: Rename DEB Artifact
run: |
cd $GITHUB_WORKSPACE/desktop/build/installers/main-release/deb/
# find the file matching the pattern and store its name in a variable
file=$(find . -type f -name 'hammer_*.deb' -print -quit)
# if no file was found, exit with an error message
if [ -z "$file" ]; then
echo "Error: no file matching the pattern found."
exit 1
fi
# rename the file to "hammer.deb"
mv "$file" "${file/hammer_*/hammer}.deb"
echo "File renamed successfully."
- name: Package Linux ".rpm" distribution
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0
with:
timeout_minutes: 30
max_attempts: 3
command: ./gradlew :desktop:packageReleaseRpm
- name: Rename RPM Artifact
run: |
cd $GITHUB_WORKSPACE/desktop/build/installers/main-release/rpm/
# find the file matching the pattern and store its name in a variable
file=$(find . -type f -name 'hammer-*.rpm' -print -quit)
# if no file was found, exit with an error message
if [ -z "$file" ]; then
echo "Error: no file matching the pattern found."
exit 1
fi
# rename the file to "hammer.rpm"
mv "$file" "hammer.rpm"
echo "File renamed successfully."
- name: Build Linux ".appimage" distribution
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0
with:
timeout_minutes: 30
max_attempts: 3
command: ./gradlew buildDistAppImage
- name: Create Release
uses: ncipollo/release-action@339a81892b84b4eeb0f6e744e4574d79d0d9b8dd # v1.21.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
draft: true
prerelease: true
artifactErrorsFailBuild: true
allowUpdates: true
artifacts: "${{ github.workspace }}/desktop/build/installers/main-release/deb/hammer.deb,${{ github.workspace }}/desktop/build/installers/main-release/rpm/hammer.rpm,${{ github.workspace }}/desktop/build/installers/main-release/appimage/hammer.AppImage,${{ github.workspace }}/desktop/build/installers/main-release/appimage/hammer.AppImage.zsync"
macos:
needs: tests
runs-on: macos-latest
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Set up JBR
uses: ./.github/actions/setup-jbr
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Gradle caching
uses: actions/cache@v6
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Package MacOS ".dmg" distribution
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0
with:
timeout_minutes: 30
max_attempts: 3
command: ./gradlew :desktop:packageReleaseDmg
- name: Rename DMG Artifact
run: |
cd $GITHUB_WORKSPACE/desktop/build/installers/main-release/dmg/
# find the file matching the pattern and store its name in a variable
file=$(find . -type f -name 'hammer-*.dmg' -print -quit)
# if no file was found, exit with an error message
if [ -z "$file" ]; then
echo "Error: no file matching the pattern found."
exit 1
fi
# rename the file to "hammer.dmg"
mv "$file" "${file/hammer-*/hammer}.dmg"
echo "File renamed successfully."
- name: Package MacOS ".pkg" distribution
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0
with:
timeout_minutes: 30
max_attempts: 3
command: ./gradlew :desktop:packageReleasePkg
- name: Rename PKG Artifact
run: |
cd $GITHUB_WORKSPACE/desktop/build/installers/main-release/pkg/
# find the file matching the pattern and store its name in a variable
file=$(find . -type f -name 'hammer-*.pkg' -print -quit)
# if no file was found, exit with an error message
if [ -z "$file" ]; then
echo "Error: no file matching the pattern found."
exit 1
fi
# rename the file to "hammer.pkg"
mv "$file" "hammer.pkg"
echo "File renamed successfully."
echo "Contents of pkg directory:"
ls -la
- name: Create Release
uses: ncipollo/release-action@339a81892b84b4eeb0f6e744e4574d79d0d9b8dd # v1.21.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
draft: true
prerelease: true
artifactErrorsFailBuild: true
allowUpdates: true
artifacts: "${{ github.workspace }}/desktop/build/installers/main-release/dmg/hammer.dmg,${{ github.workspace }}/desktop/build/installers/main-release/pkg/hammer.pkg"
server:
needs: tests
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Set up JDK
uses: actions/setup-java@v5
with:
distribution: 'adopt'
java-version: 21
- name: Gradle caching
uses: actions/cache@v6
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Package Server distributions
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0
with:
timeout_minutes: 30
max_attempts: 3
command: ./gradlew :server:assembleDist
- name: Rename tar Artifact
run: |
file=$(find . -type f -name 'server-*.tar' -print -quit)
if [ -z "$file" ]; then
echo "Error: no file matching the pattern found."
exit 1
fi
mv "$file" "${file/server-*/server}.tar"
echo "File renamed successfully."
- name: Rename zip Artifact
run: |
file=$(find . -type f -name 'server-*.zip' -print -quit)
if [ -z "$file" ]; then
echo "Error: no file matching the pattern found."
exit 1
fi
mv "$file" "${file/server-*/server}.zip"
echo "File renamed successfully."
- name: Create Release
uses: ncipollo/release-action@339a81892b84b4eeb0f6e744e4574d79d0d9b8dd # v1.21.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
draft: true
prerelease: true
artifactErrorsFailBuild: true
allowUpdates: true
artifacts: "${{ github.workspace }}/server/build/distributions/*.*"
android:
needs: tests
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Set up JDK
uses: actions/setup-java@v5
with:
distribution: 'adopt'
java-version: 21
- name: Gradle caching
uses: actions/cache@v6
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Restore key
working-directory: ${{ github.workspace }}
run: |
echo "${{ secrets.RELEASE_KEYSTORE }}" > release.keystore.asc
echo ${{ secrets.RELEASE_STORE_PASSWORD_BASE64 }} | base64 -d > passphrase
gpg -d --passphrase-file passphrase --batch release.keystore.asc > release.keystore
- name: Build APK
env:
RELEASE_STORE_FILE: ${{ github.workspace }}/release.keystore
RELEASE_STORE_PASSWORD: ${{ secrets.RELEASE_STORE_PASSWORD }}
RELEASE_KEY_ALIAS: ${{ secrets.RELEASE_KEY_ALIAS }}
RELEASE_KEY_PASSWORD: ${{ secrets.RELEASE_KEY_PASSWORD }}
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0
with:
timeout_minutes: 30
max_attempts: 3
# Build the F-Droid flavor for the GitHub release APK: it enables the
# public-storage projects feature (adds the storage permissions). The
# Google Play build is produced separately via publish-google-play.yml.
command: ./gradlew :android:assembleRelease -Pfdroid=true
- name: Rename APK Artifact
run: |
cd $GITHUB_WORKSPACE/android/build/outputs/apk/release/
# find the file matching the pattern and store its name in a variable
file=$(find . -type f -name '*.apk' -print -quit)
# if no file was found, exit with an error message
if [ -z "$file" ]; then
echo "Error: no APK file found."
exit 1
fi
# rename the file to "hammer.apk"
mv "$file" "hammer.apk"
echo "File renamed successfully."
- name: Create Release
uses: ncipollo/release-action@339a81892b84b4eeb0f6e744e4574d79d0d9b8dd # v1.21.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
draft: true
prerelease: true
artifactErrorsFailBuild: true
allowUpdates: true
artifacts: "${{ github.workspace }}/android/build/outputs/apk/release/hammer.apk"
snap:
needs: tests
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Set up JBR
uses: ./.github/actions/setup-jbr
with:
token: ${{ secrets.GITHUB_TOKEN }}
cache: gradle
- name: Grant Permission to Execute
run: chmod +x gradlew
- name: Build Snap
uses: snapcore/action-build@3bdaa03e1ba6bf59a65f84a751d943d549a54e79 # v1.3.0
id: build-snap
- name: Rename Snap Artifact
run: |
mv "${{ steps.build-snap.outputs.snap }}" hammer.snap
echo "File renamed successfully."
- name: Create Release
uses: ncipollo/release-action@339a81892b84b4eeb0f6e744e4574d79d0d9b8dd # v1.21.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
draft: true
prerelease: true
artifactErrorsFailBuild: true
allowUpdates: true
artifacts: hammer.snap
flatpak:
needs: tests
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Set up JBR
uses: ./.github/actions/setup-jbr
with:
token: ${{ secrets.GITHUB_TOKEN }}
cache: gradle
- name: Install Flatpak and flatpak-builder
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0
with:
timeout_minutes: 10
max_attempts: 3
command: |
sudo apt-get update
sudo apt-get install -y flatpak flatpak-builder
flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
- name: Cache Flatpak SDK and runtime
uses: actions/cache@v6
with:
path: ~/.local/share/flatpak
key: flatpak-sdk-runtime-24.08
- name: Install Flatpak SDK and runtime
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0
with:
timeout_minutes: 15
max_attempts: 3
command: |
flatpak install --user -y flathub org.freedesktop.Platform//24.08
flatpak install --user -y flathub org.freedesktop.Sdk//24.08
- name: Grant Permission to Execute
run: chmod +x gradlew
- name: Build Flatpak
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0
with:
timeout_minutes: 30
max_attempts: 3
command: ./gradlew buildDistFlatpak
- name: Create Release
uses: ncipollo/release-action@339a81892b84b4eeb0f6e744e4574d79d0d9b8dd # v1.21.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
draft: true
prerelease: true
artifactErrorsFailBuild: true
allowUpdates: true
artifacts: "${{ github.workspace }}/desktop/build/installers/main-release/flatpak/hammer.flatpak"
set-release-body:
runs-on: ubuntu-latest
needs: [ windows, linux, macos, android, server, snap, flatpak ]
if: success()
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Fetch tags
run: git fetch --depth=1 origin +refs/tags/*:refs/tags/*
- name: Get last tag message
run: |
TAG_NAME=${GITHUB_REF#refs/tags/}
TAG_MESSAGE=$(git tag -n90 --format='%(contents)' "$TAG_NAME" )
echo "Tag: $TAG_NAME"
echo -e "Tag message:\n$TAG_MESSAGE"
echo "tag_message<<EOF" >> $GITHUB_ENV
echo "$TAG_MESSAGE" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
- name: Set release body
uses: ncipollo/release-action@339a81892b84b4eeb0f6e744e4574d79d0d9b8dd # v1.21.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
draft: true
prerelease: true
allowUpdates: true
body: ${{ env.tag_message }}