84c4b461a9
P1 fixes (must-fix):
- AppRun: VLC path corrected to usr/lib/app/linux/vlc (jpackage actual
path), add VLC_PLUGIN_PATH env var for codec discovery
- linuxdeploy: use APPIMAGE_EXTRACT_AND_RUN=1 env var to bypass FUSE
on CI runners instead of fragile pre-extract + symlink approach
- Prerelease classifier: inverted to positive-match stable format
(^v[0-9]+\.[0-9]+\.[0-9]+$) across desktop, Android, and composite
action — eliminates regex drift between allowlists
- assert-stable-release: now accepts inputs (tag, is_prerelease,
is_draft) so workflow_dispatch on bump workflows also validates
P2 fixes (should-fix):
- .gitignore: add linuxdeploy-*.AppImage and extracted dirs
- RPM version: use replace("-", "~") instead of substringBefore("-")
for correct RPM prerelease ordering (1.08.0~rc1 < 1.08.0)
- linux-portable → linux alias moved into collect_assets() in
scripts/asset-name.sh (single source of truth)
- Android cache key: add gradle/libs.versions.toml to hashFiles
- r0adkll/sign-android-release: SHA-pinned to 349ebdef (v1)
P3 fixes (nice-to-have):
- LINUXDEPLOY_OUTPUT_VERSION env var replaced with
APPIMAGE_EXTRACT_AND_RUN (misleading comment + redundant var)
- nick-fields/retry timeout: 40m → 15m (surface real hangs)
- generate_release_notes: true only on Android job (avoid
last-writer-wins race across 6 concurrent uploaders)
- apt-get install rpm: tightened guard to matrix.family == 'linux'
(linux-portable leg doesn't need rpm tooling)
307 lines
12 KiB
YAML
307 lines
12 KiB
YAML
name: Create Release Assets
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
|
|
workflow_dispatch:
|
|
inputs:
|
|
dry_run:
|
|
description: 'Dry run: build assets but do not publish to GH Release or trigger bump workflows'
|
|
type: boolean
|
|
default: false
|
|
test_tag:
|
|
description: 'Synthetic tag name for dry-run (e.g. v1.08.0-dryrun); ignored on tag push'
|
|
type: string
|
|
default: 'v0.0.0-dryrun'
|
|
|
|
permissions:
|
|
contents: write
|
|
|
|
env:
|
|
# Asset naming contract: amethyst-desktop-<version>-<family>-<arch>.<ext>
|
|
# Single source of truth in scripts/asset-name.sh.
|
|
# linuxdeploy pinned release — bump via Dependabot, verify SHA256 via env var below.
|
|
LINUXDEPLOY_URL: https://github.com/linuxdeploy/linuxdeploy/releases/download/1-alpha-20240109-1/linuxdeploy-x86_64.AppImage
|
|
LINUXDEPLOY_SHA256: c86d6540f1df31061f02f539a2d3445f8d7f85cc3994eee1e74cd1ac97b76df0
|
|
|
|
jobs:
|
|
# ---------------------------------------------------------------------------
|
|
# Desktop build matrix. Each leg uploads directly to the GH Release via
|
|
# softprops/action-gh-release@v2 (upsert by tag_name). No artifact round-trip.
|
|
# ---------------------------------------------------------------------------
|
|
build-desktop:
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- { os: macos-13, arch: x64, family: macos, tasks: "packageReleaseDmg" }
|
|
- { os: macos-14, arch: arm64, family: macos, tasks: "packageReleaseDmg" }
|
|
- { os: windows-latest, arch: x64, family: windows, tasks: "packageReleaseMsi createReleaseDistributable" }
|
|
- { os: ubuntu-latest, arch: x64, family: linux, tasks: "packageReleaseDeb packageReleaseRpm" }
|
|
- { os: ubuntu-latest, arch: x64, family: linux-portable, tasks: "createReleaseAppImage createReleaseDistributable" }
|
|
runs-on: ${{ matrix.os }}
|
|
timeout-minutes: 45
|
|
defaults:
|
|
run:
|
|
shell: bash
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Set up JDK 21
|
|
uses: actions/setup-java@v5
|
|
with:
|
|
distribution: 'zulu'
|
|
java-version: 21
|
|
|
|
- name: Resolve tag + version
|
|
id: ver
|
|
env:
|
|
DRY_RUN: ${{ github.event.inputs.dry_run || 'false' }}
|
|
TEST_TAG: ${{ github.event.inputs.test_tag || '' }}
|
|
run: |
|
|
set -euo pipefail
|
|
if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then
|
|
TAG="${TEST_TAG:-v0.0.0-dryrun}"
|
|
else
|
|
TAG="${GITHUB_REF_NAME}"
|
|
fi
|
|
VER="${TAG#v}"
|
|
TOML_VER=$(grep -E '^app\s*=' gradle/libs.versions.toml | head -1 | cut -d'"' -f2)
|
|
# On dry-run we only require that TOML has a version; on real tag push we require exact match.
|
|
if [[ "${GITHUB_EVENT_NAME}" != "workflow_dispatch" ]]; then
|
|
if [[ "$TOML_VER" != "$VER" ]]; then
|
|
echo "::error::gradle/libs.versions.toml app=$TOML_VER but tag is $TAG"
|
|
exit 1
|
|
fi
|
|
fi
|
|
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
|
|
echo "version=$VER" >> "$GITHUB_OUTPUT"
|
|
echo "toml=$TOML_VER" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Install RPM tooling (deb+rpm leg only)
|
|
if: matrix.family == 'linux'
|
|
run: sudo apt-get update && sudo apt-get install -y rpm fakeroot
|
|
|
|
- name: Fetch linuxdeploy (linux-portable only, SHA-verified)
|
|
if: matrix.family == 'linux-portable'
|
|
run: |
|
|
set -euo pipefail
|
|
curl -fsSL --retry 3 "$LINUXDEPLOY_URL" -o packaging/appimage/linuxdeploy-x86_64.AppImage
|
|
actual=$(sha256sum packaging/appimage/linuxdeploy-x86_64.AppImage | awk '{print $1}')
|
|
if [[ "$actual" != "$LINUXDEPLOY_SHA256" ]]; then
|
|
echo "::error::linuxdeploy SHA256 mismatch. Expected $LINUXDEPLOY_SHA256, got $actual"
|
|
exit 1
|
|
fi
|
|
chmod +x packaging/appimage/linuxdeploy-x86_64.AppImage
|
|
|
|
- name: Build desktop artifacts
|
|
uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 # v3.0.2
|
|
with:
|
|
max_attempts: 2
|
|
timeout_minutes: 15
|
|
command: ./gradlew --no-daemon :desktopApp:${{ matrix.tasks }}
|
|
|
|
- name: Build portable archives (windows + linux-portable)
|
|
if: matrix.family == 'windows' || matrix.family == 'linux-portable'
|
|
run: |
|
|
set -euo pipefail
|
|
VER="${{ steps.ver.outputs.version }}"
|
|
APP="desktopApp/build/compose/binaries/main-release/app"
|
|
mkdir -p desktopApp/build/portable
|
|
if [[ "${{ matrix.family }}" == "windows" ]]; then
|
|
( cd "$APP" && 7z a -tzip "../../../../portable/amethyst-desktop-${VER}-windows-x64.zip" Amethyst/ )
|
|
else
|
|
( cd "$APP" && tar czf "../../../../portable/amethyst-desktop-${VER}-linux-x64.tar.gz" Amethyst/ )
|
|
fi
|
|
|
|
- name: Collect + rename assets
|
|
run: |
|
|
set -euo pipefail
|
|
# shellcheck source=scripts/asset-name.sh
|
|
source scripts/asset-name.sh
|
|
# collect_assets normalizes linux-portable → linux internally.
|
|
collect_assets "${{ matrix.family }}" "${{ matrix.arch }}" "${{ steps.ver.outputs.version }}" dist
|
|
|
|
- name: Enforce asset size budget (1 GB per asset)
|
|
run: |
|
|
set -euo pipefail
|
|
fail=0
|
|
for f in dist/*; do
|
|
if [[ -f "$f" ]]; then
|
|
size=$(wc -c < "$f")
|
|
mb=$(( size / 1048576 ))
|
|
if (( size > 1073741824 )); then
|
|
echo "::error file=$f::asset is ${mb} MB — exceeds 1 GB budget"
|
|
fail=1
|
|
else
|
|
echo "OK: $f — ${mb} MB"
|
|
fi
|
|
fi
|
|
done
|
|
[[ "$fail" == 0 ]]
|
|
|
|
- name: Classify release
|
|
id: classify
|
|
run: |
|
|
TAG="${{ steps.ver.outputs.tag }}"
|
|
# Stable = exactly vMAJOR.MINOR.PATCH; everything else is prerelease.
|
|
if [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
echo "prerelease=false" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "prerelease=true" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
- name: Upload to GH Release (skip on dry-run)
|
|
if: github.event_name != 'workflow_dispatch' || github.event.inputs.dry_run != 'true'
|
|
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2.6.2
|
|
with:
|
|
files: dist/*
|
|
tag_name: ${{ steps.ver.outputs.tag }}
|
|
prerelease: ${{ steps.classify.outputs.prerelease }}
|
|
draft: false
|
|
fail_on_unmatched_files: true
|
|
generate_release_notes: false # Android job writes release notes (last-writer-wins race)
|
|
|
|
- name: Dry-run summary
|
|
if: github.event_name == 'workflow_dispatch' && github.event.inputs.dry_run == 'true'
|
|
run: |
|
|
echo "### Dry-run: ${{ matrix.family }}/${{ matrix.arch }}" >> "$GITHUB_STEP_SUMMARY"
|
|
echo "" >> "$GITHUB_STEP_SUMMARY"
|
|
echo '```' >> "$GITHUB_STEP_SUMMARY"
|
|
ls -la dist >> "$GITHUB_STEP_SUMMARY"
|
|
echo '```' >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Android build + sign + direct-upload. Logic preserved from previous workflow;
|
|
# uses softprops/action-gh-release@v2 instead of deprecated upload-release-asset.
|
|
# ---------------------------------------------------------------------------
|
|
deploy-android:
|
|
if: github.event_name != 'workflow_dispatch' # dry-run skips Android (tag-push only)
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 60
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Set up JDK 21
|
|
uses: actions/setup-java@v5
|
|
with:
|
|
distribution: 'zulu'
|
|
java-version: 21
|
|
|
|
- name: Cache gradle
|
|
uses: actions/cache@v5
|
|
with:
|
|
path: |
|
|
~/.gradle/caches
|
|
~/.gradle/wrapper
|
|
key: ${{ runner.os }}-android-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', 'gradle/libs.versions.toml') }}
|
|
restore-keys: |
|
|
${{ runner.os }}-android-gradle-
|
|
|
|
- name: Build AAB
|
|
run: ./gradlew clean bundleRelease --stacktrace
|
|
|
|
- name: Sign AAB (Google Play)
|
|
uses: r0adkll/sign-android-release@349ebdef58775b1e0d8099458af0816dc79b6407 # v1
|
|
with:
|
|
releaseDirectory: amethyst/build/outputs/bundle/playRelease
|
|
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
|
|
alias: ${{ secrets.KEY_ALIAS }}
|
|
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
|
|
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
|
env:
|
|
BUILD_TOOLS_VERSION: "36.0.0"
|
|
|
|
- name: Sign AAB (F-Droid)
|
|
uses: r0adkll/sign-android-release@349ebdef58775b1e0d8099458af0816dc79b6407 # v1
|
|
with:
|
|
releaseDirectory: amethyst/build/outputs/bundle/fdroidRelease
|
|
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
|
|
alias: ${{ secrets.KEY_ALIAS }}
|
|
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
|
|
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
|
env:
|
|
BUILD_TOOLS_VERSION: "36.0.0"
|
|
|
|
- name: Build APK
|
|
run: ./gradlew assembleRelease --stacktrace
|
|
|
|
- name: Sign APK (Google Play)
|
|
uses: r0adkll/sign-android-release@349ebdef58775b1e0d8099458af0816dc79b6407 # v1
|
|
with:
|
|
releaseDirectory: amethyst/build/outputs/apk/play/release
|
|
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
|
|
alias: ${{ secrets.KEY_ALIAS }}
|
|
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
|
|
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
|
env:
|
|
BUILD_TOOLS_VERSION: "36.0.0"
|
|
|
|
- name: Sign APK (F-Droid)
|
|
uses: r0adkll/sign-android-release@349ebdef58775b1e0d8099458af0816dc79b6407 # v1
|
|
with:
|
|
releaseDirectory: amethyst/build/outputs/apk/fdroid/release
|
|
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
|
|
alias: ${{ secrets.KEY_ALIAS }}
|
|
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
|
|
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
|
env:
|
|
BUILD_TOOLS_VERSION: "36.0.0"
|
|
|
|
- name: Collect Android assets (rename to canonical scheme)
|
|
run: |
|
|
set -euo pipefail
|
|
mkdir -p dist
|
|
TAG="${GITHUB_REF_NAME}"
|
|
|
|
# Play APKs (5 variants)
|
|
for variant in universal x86 x86_64 arm64-v8a armeabi-v7a; do
|
|
cp "amethyst/build/outputs/apk/play/release/amethyst-play-${variant}-release-unsigned-signed.apk" \
|
|
"dist/amethyst-googleplay-${variant}-${TAG}.apk"
|
|
done
|
|
|
|
# F-Droid APKs (5 variants)
|
|
for variant in universal x86 x86_64 arm64-v8a armeabi-v7a; do
|
|
cp "amethyst/build/outputs/apk/fdroid/release/amethyst-fdroid-${variant}-release-unsigned-signed.apk" \
|
|
"dist/amethyst-fdroid-${variant}-${TAG}.apk"
|
|
done
|
|
|
|
# AABs
|
|
cp "amethyst/build/outputs/bundle/playRelease/amethyst-play-release.aab" \
|
|
"dist/amethyst-googleplay-${TAG}.aab"
|
|
cp "amethyst/build/outputs/bundle/fdroidRelease/amethyst-fdroid-release.aab" \
|
|
"dist/amethyst-fdroid-${TAG}.aab"
|
|
ls -la dist
|
|
|
|
- name: Classify release
|
|
id: classify
|
|
run: |
|
|
TAG="${GITHUB_REF_NAME}"
|
|
# Stable = exactly vMAJOR.MINOR.PATCH; everything else is prerelease.
|
|
if [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
echo "prerelease=false" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "prerelease=true" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
- name: Upload Android assets to GH Release
|
|
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2.6.2
|
|
with:
|
|
files: dist/*
|
|
tag_name: ${{ github.ref_name }}
|
|
prerelease: ${{ steps.classify.outputs.prerelease }}
|
|
draft: false
|
|
fail_on_unmatched_files: true
|
|
generate_release_notes: true
|
|
|
|
- name: Publish Quartz Lib
|
|
run: ./gradlew publishAllPublicationsToMavenCentral --no-configuration-cache
|
|
env:
|
|
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_USERNAME }}
|
|
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_PASSWORD }}
|
|
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_PRIVATE_KEY }}
|
|
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_PASSWORD }}
|