mirror of
https://github.com/fromchat-messenger/app.git
synced 2026-09-22 19:15:05 +03:00
86cd889b2e
Use a single glob for platform artifacts and a separate step for Android; multiline pattern values are not supported. Co-authored-by: Cursor <cursoragent@cursor.com>
522 lines
19 KiB
YAML
522 lines
19 KiB
YAML
name: Release
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
- "v*"
|
|
workflow_dispatch:
|
|
inputs:
|
|
version:
|
|
description: "Version to build (e.g. 1.1.4). Overrides tag when set."
|
|
required: true
|
|
type: string
|
|
skip_publish:
|
|
description: "Skip GitHub Release upload"
|
|
required: false
|
|
type: choice
|
|
options:
|
|
- "false"
|
|
- "true"
|
|
default: "false"
|
|
|
|
concurrency:
|
|
group: release-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
env:
|
|
GRADLE_OPTS: >-
|
|
-Dorg.gradle.jvmargs=-Xmx6g
|
|
-Dkotlin.daemon.jvm.options=-Xmx6g
|
|
-Dorg.gradle.parallel=true
|
|
|
|
jobs:
|
|
resolve-version:
|
|
name: Resolve version
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
version: ${{ steps.ver.outputs.version }}
|
|
tag: ${{ steps.ver.outputs.tag }}
|
|
prerelease: ${{ steps.ver.outputs.prerelease }}
|
|
release_label: ${{ steps.ver.outputs.release_label }}
|
|
steps:
|
|
- id: ver
|
|
shell: bash
|
|
run: |
|
|
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
|
V="${{ inputs.version }}"
|
|
else
|
|
V="${GITHUB_REF_NAME#v}"
|
|
fi
|
|
if [[ ! "$V" =~ ^[0-9]+\.[0-9]+\.[0-9]+([.-].*)?$ ]]; then
|
|
echo "Invalid version: $V" >&2
|
|
exit 1
|
|
fi
|
|
if [[ ! "$V" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+) ]]; then
|
|
echo "Cannot derive release label from version: $V" >&2
|
|
exit 1
|
|
fi
|
|
MAJOR="${BASH_REMATCH[1]}"
|
|
MINOR="${BASH_REMATCH[2]}"
|
|
PATCH="${BASH_REMATCH[3]}"
|
|
if [[ "$PATCH" == "0" ]]; then
|
|
RELEASE_LABEL="${MAJOR}.${MINOR}"
|
|
else
|
|
RELEASE_LABEL="${MAJOR}.${MINOR}.${PATCH}"
|
|
fi
|
|
echo "version=$V" >> "$GITHUB_OUTPUT"
|
|
echo "tag=v$V" >> "$GITHUB_OUTPUT"
|
|
echo "release_label=$RELEASE_LABEL" >> "$GITHUB_OUTPUT"
|
|
case "$V" in
|
|
*-beta*) echo "prerelease=true" >> "$GITHUB_OUTPUT" ;;
|
|
*) echo "prerelease=false" >> "$GITHUB_OUTPUT" ;;
|
|
esac
|
|
|
|
desktop-proguard:
|
|
name: Build JAR
|
|
needs: resolve-version
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 45
|
|
env:
|
|
FROMCHAT_PREBUILT_PROGUARD: "0"
|
|
steps:
|
|
- uses: actions/checkout@v5
|
|
- uses: ./.github/actions/setup-java-gradle
|
|
- uses: ./.github/actions/set-version
|
|
with:
|
|
version: ${{ needs.resolve-version.outputs.version }}
|
|
- uses: ./.github/actions/cache-gradle-build
|
|
id: gradle-build-cache
|
|
with:
|
|
scope: build-jar
|
|
- name: Compile and ProGuard release desktop jars
|
|
run: ./gradlew :app:desktop:exportReleaseProguardForCi --no-daemon --console=plain
|
|
- uses: ./.github/actions/save-gradle-build-cache
|
|
if: always()
|
|
with:
|
|
primary-key: ${{ steps.gradle-build-cache.outputs.cache-primary-key }}
|
|
- uses: actions/upload-artifact@v5
|
|
with:
|
|
name: desktop-proguard
|
|
path: app/desktop/build/ci/proguard/
|
|
if-no-files-found: error
|
|
retention-days: 3
|
|
|
|
windows-common:
|
|
name: Windows installer
|
|
needs: resolve-version
|
|
runs-on: windows-latest
|
|
timeout-minutes: 30
|
|
steps:
|
|
- uses: actions/checkout@v5
|
|
- uses: ./.github/actions/setup-java-gradle
|
|
- uses: ./.github/actions/set-version
|
|
with:
|
|
version: ${{ needs.resolve-version.outputs.version }}
|
|
- uses: ./.github/actions/cache-gradle-build
|
|
id: gradle-build-cache
|
|
with:
|
|
scope: windows-installer
|
|
- uses: dtolnay/rust-toolchain@stable
|
|
with:
|
|
targets: x86_64-pc-windows-msvc
|
|
- name: Cache Cargo registry
|
|
uses: actions/cache@v5
|
|
with:
|
|
path: |
|
|
~/.cargo/registry
|
|
~/.cargo/git
|
|
key: cargo-registry-${{ runner.os }}-${{ hashFiles('app/desktop/windows-setup/Cargo.lock') }}
|
|
restore-keys: |
|
|
cargo-registry-${{ runner.os }}-
|
|
- uses: ./.github/actions/cache-windows-rust-tools
|
|
id: rust-tools-cache
|
|
- name: Build Windows installer Rust tooling
|
|
if: steps.rust-tools-cache.outputs.cache-hit != 'true'
|
|
shell: bash
|
|
run: ./gradlew :app:desktop:buildWindowsSetupRust --no-daemon --console=plain
|
|
- uses: actions/upload-artifact@v5
|
|
with:
|
|
name: windows-rust-tools
|
|
path: app/desktop/windows-setup/target/release/*.exe
|
|
if-no-files-found: error
|
|
retention-days: 3
|
|
- uses: ./.github/actions/save-gradle-build-cache
|
|
if: always()
|
|
with:
|
|
primary-key: ${{ steps.gradle-build-cache.outputs.cache-primary-key }}
|
|
|
|
windows-package:
|
|
name: Windows (${{ matrix.label }})
|
|
needs: [resolve-version, desktop-proguard, windows-common]
|
|
env:
|
|
FROMCHAT_PREBUILT_PROGUARD: "1"
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- arch: x64
|
|
label: x64
|
|
runner: windows-latest
|
|
- arch: arm64
|
|
label: ARM
|
|
runner: windows-11-arm
|
|
runs-on: ${{ matrix.runner }}
|
|
timeout-minutes: 60
|
|
steps:
|
|
- uses: actions/checkout@v5
|
|
- name: Ensure ARM64 packaging JDK
|
|
if: matrix.arch == 'arm64'
|
|
shell: cmd
|
|
run: call scripts\ensure-windows-arm64-jdk.cmd
|
|
- uses: ./.github/actions/setup-java-gradle
|
|
if: matrix.arch == 'x64'
|
|
with:
|
|
cache-read-only: "true"
|
|
- uses: gradle/actions/setup-gradle@v5
|
|
if: matrix.arch == 'arm64'
|
|
with:
|
|
gradle-version: wrapper
|
|
cache-read-only: true
|
|
add-job-summary: on-failure
|
|
- uses: ./.github/actions/set-version
|
|
with:
|
|
version: ${{ needs.resolve-version.outputs.version }}
|
|
- uses: ./.github/actions/cache-gradle-build
|
|
id: gradle-build-cache
|
|
with:
|
|
scope: windows-package-${{ matrix.arch }}
|
|
- uses: actions/download-artifact@v5
|
|
with:
|
|
name: desktop-proguard
|
|
path: app/desktop/build/prebuilt/main-release/proguard
|
|
- name: Restore Windows Rust installer tools
|
|
uses: actions/download-artifact@v5
|
|
with:
|
|
name: windows-rust-tools
|
|
path: app/desktop/windows-setup/target/release
|
|
- name: Package Windows ${{ matrix.arch }} installer
|
|
shell: bash
|
|
env:
|
|
FROMCHAT_SKIP_RUST_BUILD: "1"
|
|
run: |
|
|
export FROMCHAT_PACKAGING_JDK="$JAVA_HOME"
|
|
args=(
|
|
:app:desktop:packSetupOnly
|
|
"-PdesktopArch=${{ matrix.arch }}"
|
|
--no-daemon
|
|
--console=plain
|
|
)
|
|
if [ "${{ matrix.arch }}" = "arm64" ]; then
|
|
args+=("-PwindowsArm64")
|
|
fi
|
|
./gradlew "${args[@]}"
|
|
- uses: actions/upload-artifact@v5
|
|
with:
|
|
name: windows-${{ matrix.arch }}
|
|
path: app/desktop/build/distributions/release/*-windows-${{ matrix.arch }}.exe
|
|
if-no-files-found: error
|
|
- uses: ./.github/actions/save-gradle-build-cache
|
|
if: always()
|
|
with:
|
|
primary-key: ${{ steps.gradle-build-cache.outputs.cache-primary-key }}
|
|
|
|
macos:
|
|
name: macOS (${{ matrix.label }})
|
|
needs: [resolve-version, desktop-proguard]
|
|
env:
|
|
FROMCHAT_PREBUILT_PROGUARD: "1"
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- arch: arm64
|
|
label: Apple Silicon
|
|
- arch: x64
|
|
label: Intel
|
|
runs-on: macos-latest
|
|
timeout-minutes: 60
|
|
steps:
|
|
- uses: actions/checkout@v5
|
|
- uses: ./.github/actions/setup-java-gradle
|
|
if: matrix.arch == 'x64'
|
|
with:
|
|
java-architecture: x64
|
|
cache-read-only: "true"
|
|
- uses: ./.github/actions/setup-java-gradle
|
|
if: matrix.arch == 'arm64'
|
|
with:
|
|
cache-read-only: "true"
|
|
- uses: ./.github/actions/set-version
|
|
with:
|
|
version: ${{ needs.resolve-version.outputs.version }}
|
|
- uses: ./.github/actions/cache-gradle-build
|
|
id: gradle-build-cache
|
|
with:
|
|
scope: macos-${{ matrix.arch }}
|
|
- uses: actions/download-artifact@v5
|
|
with:
|
|
name: desktop-proguard
|
|
path: app/desktop/build/prebuilt/main-release/proguard
|
|
- name: Cache DMG background tooling
|
|
uses: actions/cache@v5
|
|
with:
|
|
path: |
|
|
app/desktop/dmg-background/node_modules
|
|
~/.cache/ms-playwright
|
|
key: dmg-tools-${{ runner.arch }}-${{ hashFiles('app/desktop/dmg-background/package-lock.json') }}
|
|
restore-keys: dmg-tools-${{ runner.arch }}-
|
|
- name: Install macOS packaging tools
|
|
run: |
|
|
if ! command -v create-dmg >/dev/null 2>&1; then
|
|
brew install create-dmg
|
|
fi
|
|
DMG_DIR="app/desktop/dmg-background"
|
|
if [[ ! -f "$DMG_DIR/node_modules/playwright/package.json" ]]; then
|
|
npm --prefix "$DMG_DIR" ci --no-fund --no-audit
|
|
fi
|
|
npm --prefix "$DMG_DIR" exec playwright install chromium
|
|
- name: Package macOS ${{ matrix.arch }}
|
|
run: |
|
|
export FROMCHAT_PACKAGING_JDK="$JAVA_HOME"
|
|
./gradlew :app:desktop:packageReleaseMac -PdesktopArch=${{ matrix.arch }} --no-daemon --console=plain
|
|
- uses: actions/upload-artifact@v5
|
|
with:
|
|
name: macos-${{ matrix.arch }}
|
|
path: app/desktop/build/distributions/release/*-macOS-${{ matrix.arch }}.dmg
|
|
if-no-files-found: error
|
|
- uses: ./.github/actions/save-gradle-build-cache
|
|
if: always()
|
|
with:
|
|
primary-key: ${{ steps.gradle-build-cache.outputs.cache-primary-key }}
|
|
|
|
linux:
|
|
name: Linux (${{ matrix.label }})
|
|
needs: [resolve-version, desktop-proguard]
|
|
env:
|
|
FROMCHAT_PREBUILT_PROGUARD: "1"
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- arch: x64
|
|
label: x64
|
|
runner: ubuntu-latest
|
|
appimage: appimagetool-x86_64.AppImage
|
|
- arch: arm64
|
|
label: ARM
|
|
runner: ubuntu-24.04-arm
|
|
appimage: appimagetool-aarch64.AppImage
|
|
runs-on: ${{ matrix.runner }}
|
|
timeout-minutes: 45
|
|
steps:
|
|
- uses: actions/checkout@v5
|
|
- uses: ./.github/actions/setup-java-gradle
|
|
with:
|
|
cache-read-only: "true"
|
|
- uses: ./.github/actions/set-version
|
|
with:
|
|
version: ${{ needs.resolve-version.outputs.version }}
|
|
- uses: ./.github/actions/cache-gradle-build
|
|
id: gradle-build-cache
|
|
with:
|
|
scope: linux-${{ matrix.arch }}
|
|
- uses: actions/download-artifact@v5
|
|
with:
|
|
name: desktop-proguard
|
|
path: app/desktop/build/prebuilt/main-release/proguard
|
|
- name: Install Linux packaging tools
|
|
run: |
|
|
sudo apt-get update
|
|
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
|
fakeroot rpm binutils libfuse2 wget
|
|
CACHE_DIR="${RUNNER_TOOL_CACHE}/appimagetool"
|
|
mkdir -p "$CACHE_DIR"
|
|
TOOL="$CACHE_DIR/appimagetool-${{ matrix.arch }}.AppImage"
|
|
if [[ ! -f "$TOOL" ]]; then
|
|
wget -q "https://github.com/AppImage/appimagetool/releases/download/continuous/${{ matrix.appimage }}" -O "$TOOL"
|
|
chmod +x "$TOOL"
|
|
fi
|
|
echo "APPIMAGETOOL=$TOOL" >> "$GITHUB_ENV"
|
|
- name: Package Linux ${{ matrix.arch }}
|
|
run: |
|
|
export FROMCHAT_PACKAGING_JDK="$JAVA_HOME"
|
|
./gradlew :app:desktop:packageReleaseLinux -PdesktopArch=${{ matrix.arch }} --no-daemon --console=plain
|
|
- uses: actions/upload-artifact@v5
|
|
with:
|
|
name: linux-${{ matrix.arch }}
|
|
path: |
|
|
app/desktop/build/distributions/release/*-linux-${{ matrix.arch }}.deb
|
|
app/desktop/build/distributions/release/*-linux-${{ matrix.arch }}.rpm
|
|
app/desktop/build/distributions/release/*-linux-${{ matrix.arch }}.AppImage
|
|
if-no-files-found: error
|
|
- uses: ./.github/actions/save-gradle-build-cache
|
|
if: always()
|
|
with:
|
|
primary-key: ${{ steps.gradle-build-cache.outputs.cache-primary-key }}
|
|
|
|
android:
|
|
name: Android
|
|
needs: resolve-version
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 60
|
|
steps:
|
|
- uses: actions/checkout@v5
|
|
- uses: ./.github/actions/setup-java-gradle
|
|
with:
|
|
cache-read-only: "true"
|
|
- uses: android-actions/setup-android@v4
|
|
- uses: ./.github/actions/set-version
|
|
with:
|
|
version: ${{ needs.resolve-version.outputs.version }}
|
|
- uses: ./.github/actions/cache-gradle-build
|
|
id: gradle-build-cache
|
|
with:
|
|
scope: android
|
|
- name: Cache Android SDK extras
|
|
uses: actions/cache@v5
|
|
with:
|
|
path: |
|
|
~/.android/build-cache
|
|
~/.android/cache
|
|
key: android-sdk-${{ runner.os }}-${{ hashFiles('gradle/libs.versions.toml', 'app/android/build.gradle.kts') }}
|
|
restore-keys: android-sdk-${{ runner.os }}-
|
|
- name: Prepare Android signing
|
|
env:
|
|
KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
|
|
GOOGLE_SERVICES_JSON: ${{ secrets.ANDROID_GOOGLE_SERVICES_JSON }}
|
|
RELEASE_STORE_PASSWORD: ${{ secrets.ANDROID_RELEASE_STORE_PASSWORD }}
|
|
RELEASE_KEY_PASSWORD: ${{ secrets.ANDROID_RELEASE_KEY_PASSWORD }}
|
|
run: |
|
|
if [[ -z "$KEYSTORE_BASE64" || -z "$GOOGLE_SERVICES_JSON" ]]; then
|
|
echo "Missing ANDROID_KEYSTORE_BASE64 or ANDROID_GOOGLE_SERVICES_JSON secrets" >&2
|
|
exit 1
|
|
fi
|
|
mkdir -p app/android/keys
|
|
printf '%s' "$KEYSTORE_BASE64" | base64 -d > app/android/keys/release.jks
|
|
keytool -genkeypair -noprompt \
|
|
-alias key0 -keyalg RSA -keysize 2048 -validity 10000 \
|
|
-dname "CN=FromChat Debug" \
|
|
-keystore app/android/keys/debug.jks \
|
|
-storepass android -keypass android
|
|
{
|
|
echo "releaseStorePassword=${RELEASE_STORE_PASSWORD}"
|
|
echo "releaseKeyPassword=${RELEASE_KEY_PASSWORD}"
|
|
echo "debugStorePassword=android"
|
|
echo "debugKeyPassword=android"
|
|
} > app/android/keys/keystore.properties
|
|
printf '%s' "$GOOGLE_SERVICES_JSON" | base64 -d > app/android/google-services.json
|
|
- name: Build universal Android APK
|
|
run: ./gradlew :app:android:assembleRelease --no-daemon --console=plain
|
|
- uses: ./.github/actions/save-gradle-build-cache
|
|
if: always()
|
|
with:
|
|
primary-key: ${{ steps.gradle-build-cache.outputs.cache-primary-key }}
|
|
- name: Stage APK
|
|
run: |
|
|
mkdir -p release-staging
|
|
APK="$(find app/android/build/outputs/apk/release -name '*release*.apk' | head -n 1)"
|
|
cp "$APK" "release-staging/FromChat-${{ needs.resolve-version.outputs.release_label }}.apk"
|
|
- uses: actions/upload-artifact@v5
|
|
with:
|
|
name: android
|
|
path: release-staging/*.apk
|
|
if-no-files-found: error
|
|
|
|
publish:
|
|
if: ${{ github.event_name != 'workflow_dispatch' || inputs.skip_publish != 'true' }}
|
|
needs:
|
|
- resolve-version
|
|
- windows-package
|
|
- macos
|
|
- linux
|
|
- android
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: write
|
|
steps:
|
|
- uses: actions/checkout@v5
|
|
with:
|
|
sparse-checkout: |
|
|
RELEASE_NOTES.md
|
|
sparse-checkout-cone-mode: false
|
|
- uses: actions/download-artifact@v5
|
|
with:
|
|
pattern: "{windows,macos,linux}-*"
|
|
merge-multiple: true
|
|
path: artifacts
|
|
- uses: actions/download-artifact@v5
|
|
with:
|
|
name: android
|
|
path: artifacts
|
|
- name: Collect release assets
|
|
env:
|
|
LABEL: ${{ needs.resolve-version.outputs.release_label }}
|
|
run: |
|
|
set -euo pipefail
|
|
if [[ ! -d artifacts ]]; then
|
|
echo "No artifacts were downloaded" >&2
|
|
exit 1
|
|
fi
|
|
mkdir -p release-assets
|
|
|
|
pick_one() {
|
|
local pattern="$1"
|
|
local dest="$2"
|
|
local match
|
|
match=$(find artifacts -type f -name "$pattern" | head -n 1)
|
|
if [[ -z "$match" ]]; then
|
|
echo "Missing artifact matching $pattern" >&2
|
|
exit 1
|
|
fi
|
|
cp "$match" "release-assets/$dest"
|
|
}
|
|
|
|
pick_one "*-windows-x64.exe" "FromChat-${LABEL}-x64.exe"
|
|
pick_one "*-windows-arm64.exe" "FromChat-${LABEL}-arm.exe"
|
|
pick_one "*-macOS-arm64.dmg" "FromChat-${LABEL}-apple.dmg"
|
|
pick_one "*-macOS-x64.dmg" "FromChat-${LABEL}-intel.dmg"
|
|
pick_one "*-linux-x64.deb" "FromChat-${LABEL}-x64.deb"
|
|
pick_one "*-linux-arm64.deb" "FromChat-${LABEL}-arm.deb"
|
|
pick_one "*-linux-x64.rpm" "FromChat-${LABEL}-x64.rpm"
|
|
pick_one "*-linux-arm64.rpm" "FromChat-${LABEL}-arm.rpm"
|
|
pick_one "*-linux-x64.AppImage" "FromChat-${LABEL}-x64.AppImage"
|
|
pick_one "*-linux-arm64.AppImage" "FromChat-${LABEL}-arm.AppImage"
|
|
pick_one "*.apk" "FromChat-${LABEL}.apk"
|
|
|
|
if find release-assets -type f -name '*.zip' | grep -q .; then
|
|
echo "Release assets must not be zip archives" >&2
|
|
exit 1
|
|
fi
|
|
ls -la release-assets
|
|
- name: Prepare release notes
|
|
run: |
|
|
VERSION="${{ needs.resolve-version.outputs.version }}"
|
|
{
|
|
echo "# FromChat ${VERSION}"
|
|
echo ""
|
|
cat RELEASE_NOTES.md
|
|
} > release-body.md
|
|
- name: Publish GitHub Release
|
|
uses: softprops/action-gh-release@v2
|
|
with:
|
|
tag_name: ${{ needs.resolve-version.outputs.tag }}
|
|
name: FromChat ${{ needs.resolve-version.outputs.version }}
|
|
body_path: release-body.md
|
|
draft: false
|
|
prerelease: ${{ needs.resolve-version.outputs.prerelease == 'true' }}
|
|
generate_release_notes: false
|
|
files: |
|
|
release-assets/FromChat-${{ needs.resolve-version.outputs.release_label }}.apk
|
|
release-assets/FromChat-${{ needs.resolve-version.outputs.release_label }}-x64.exe
|
|
release-assets/FromChat-${{ needs.resolve-version.outputs.release_label }}-arm.exe
|
|
release-assets/FromChat-${{ needs.resolve-version.outputs.release_label }}-apple.dmg
|
|
release-assets/FromChat-${{ needs.resolve-version.outputs.release_label }}-intel.dmg
|
|
release-assets/FromChat-${{ needs.resolve-version.outputs.release_label }}-x64.deb
|
|
release-assets/FromChat-${{ needs.resolve-version.outputs.release_label }}-arm.deb
|
|
release-assets/FromChat-${{ needs.resolve-version.outputs.release_label }}-x64.rpm
|
|
release-assets/FromChat-${{ needs.resolve-version.outputs.release_label }}-arm.rpm
|
|
release-assets/FromChat-${{ needs.resolve-version.outputs.release_label }}-x64.AppImage
|
|
release-assets/FromChat-${{ needs.resolve-version.outputs.release_label }}-arm.AppImage
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|