mirror of
https://github.com/fromchat-messenger/app.git
synced 2026-09-22 11:05:04 +03:00
Add optimized multi-platform release CI/CD pipeline.
Split Windows builds into shared x64 prep and arch-specific packaging, add comprehensive caching, verification scripts, and a PR smoke workflow. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
name: Cache Cargo (windows-setup)
|
||||
description: Cache registry, git sources, and target/ for the Windows installer Rust workspace
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Cache Cargo
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
app/desktop/windows-setup/target
|
||||
key: cargo-windows-${{ runner.os }}-${{ hashFiles('app/desktop/windows-setup/Cargo.lock', 'app/desktop/windows-setup/**/*.rs') }}
|
||||
restore-keys: |
|
||||
cargo-windows-${{ runner.os }}-
|
||||
@@ -0,0 +1,14 @@
|
||||
name: Set release version
|
||||
description: Writes versionName/versionCode into build.gradle.kts for CI
|
||||
|
||||
inputs:
|
||||
version:
|
||||
description: Semantic version (e.g. 1.2.3)
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Set version
|
||||
shell: bash
|
||||
run: bash scripts/ci-set-version.sh "${{ inputs.version }}"
|
||||
@@ -0,0 +1,23 @@
|
||||
name: Setup Java and Gradle
|
||||
description: Temurin JDK 17 + Gradle wrapper with dependency and build cache
|
||||
|
||||
inputs:
|
||||
java-architecture:
|
||||
description: Optional JDK architecture (x64, arm64, …)
|
||||
required: false
|
||||
default: ""
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "17"
|
||||
architecture: ${{ inputs.java-architecture }}
|
||||
|
||||
- uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
gradle-version: wrapper
|
||||
cache-read-only: false
|
||||
add-job-summary: true
|
||||
@@ -24,13 +24,11 @@ jobs:
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "17"
|
||||
- uses: ./.github/actions/setup-java-gradle
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: x86_64-pc-windows-msvc
|
||||
- uses: ./.github/actions/cache-cargo-windows
|
||||
- name: Set version
|
||||
shell: bash
|
||||
run: |
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
name: Release smoke
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/release.yml"
|
||||
- ".github/workflows/release-smoke.yml"
|
||||
- ".github/actions/**"
|
||||
- "scripts/ci-*.sh"
|
||||
- "app/desktop/build.gradle.kts"
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: release-smoke-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx6g -Dkotlin.daemon.jvm.options=-Xmx6g
|
||||
|
||||
jobs:
|
||||
test-compile:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup-java-gradle
|
||||
- uses: android-actions/setup-android@v3
|
||||
- run: bash scripts/ci-test-compile.sh
|
||||
|
||||
test-rust:
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: x86_64-pc-windows-msvc
|
||||
- uses: ./.github/actions/cache-cargo-windows
|
||||
- name: Test Windows installer Rust workspace
|
||||
shell: bash
|
||||
working-directory: app/desktop/windows-setup
|
||||
run: cargo test --workspace --release
|
||||
|
||||
verify-script:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Syntax-check CI scripts
|
||||
run: |
|
||||
set -euo pipefail
|
||||
for script in scripts/ci-*.sh; do
|
||||
bash -n "$script"
|
||||
echo "OK $script"
|
||||
done
|
||||
- name: Dry-run asset verification patterns
|
||||
run: |
|
||||
mkdir -p /tmp/release-assets
|
||||
V="9.9.9"
|
||||
for name in \
|
||||
"FromChat-Setup-${V}-windows-x64.exe" \
|
||||
"FromChat-Setup-${V}-windows-arm64.exe" \
|
||||
"FromChat-${V}-macOS-x64.dmg" \
|
||||
"FromChat-${V}-macOS-arm64.dmg" \
|
||||
"FromChat-${V}-linux-x64.deb" \
|
||||
"FromChat-${V}-linux-arm64.deb" \
|
||||
"FromChat-${V}-linux-x64.rpm" \
|
||||
"FromChat-${V}-linux-arm64.rpm" \
|
||||
"FromChat-${V}-linux-x64.AppImage" \
|
||||
"FromChat-${V}-linux-arm64.AppImage" \
|
||||
"FromChat-${V}-android-universal.apk"
|
||||
do
|
||||
echo "placeholder" > "/tmp/release-assets/$name"
|
||||
done
|
||||
python3 - <<'PY'
|
||||
import struct, pathlib, zipfile
|
||||
root = pathlib.Path("/tmp/release-assets")
|
||||
for name, machine in [
|
||||
("FromChat-Setup-9.9.9-windows-x64.exe", 0x8664),
|
||||
("FromChat-Setup-9.9.9-windows-arm64.exe", 0xAA64),
|
||||
]:
|
||||
path = root / name
|
||||
data = bytearray(0x40)
|
||||
struct.pack_into("<I", data, 0x3C, 0x40)
|
||||
struct.pack_into("<H", data, 0x44, machine)
|
||||
path.write_bytes(data)
|
||||
apk = root / "FromChat-9.9.9-android-universal.apk"
|
||||
with zipfile.ZipFile(apk, "w") as zf:
|
||||
zf.writestr("lib/arm64-v8a/libdummy.so", b"\x00")
|
||||
zf.writestr("lib/x86_64/libdummy.so", b"\x00")
|
||||
for deb in root.glob("*.deb"):
|
||||
deb.write_bytes(b"!<arch>\n")
|
||||
for rpm in root.glob("*.rpm"):
|
||||
rpm.write_bytes(b"\xed\xab\xee\xdb" + b"\x00" * 96)
|
||||
for appimage in root.glob("*.AppImage"):
|
||||
appimage.write_bytes(b"\x7fELF" + b"\x00" * 16)
|
||||
for dmg in root.glob("*.dmg"):
|
||||
dmg.write_bytes(b"UDIF" + b"\x00" * 32)
|
||||
PY
|
||||
sudo apt-get update && sudo apt-get install -y rpm file unzip
|
||||
CI_SMOKE=1 bash scripts/ci-verify-release-assets.sh "$V" /tmp/release-assets
|
||||
+197
-201
@@ -10,6 +10,11 @@ on:
|
||||
description: "Version to build (e.g. 1.1.4). Overrides tag when set."
|
||||
required: true
|
||||
type: string
|
||||
skip_publish:
|
||||
description: "Build and verify only; skip GitHub Release upload"
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
concurrency:
|
||||
group: release-${{ github.ref }}
|
||||
@@ -40,187 +45,202 @@ jobs:
|
||||
echo "version=$V" >> "$GITHUB_OUTPUT"
|
||||
echo "tag=v$V" >> "$GITHUB_OUTPUT"
|
||||
|
||||
macos-arm64:
|
||||
needs: resolve-version
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "17"
|
||||
- name: Set version
|
||||
run: bash scripts/ci-set-version.sh "${{ needs.resolve-version.outputs.version }}"
|
||||
- name: Install create-dmg
|
||||
run: brew install create-dmg
|
||||
- name: Package macOS Apple Silicon
|
||||
run: |
|
||||
export FROMCHAT_PACKAGING_JDK="$JAVA_HOME"
|
||||
./gradlew :app:desktop:packageReleaseMac -PdesktopArch=arm64 --no-daemon
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: macos-arm64
|
||||
path: app/desktop/build/distributions/release/*-macOS-arm64.dmg
|
||||
if-no-files-found: error
|
||||
|
||||
macos-x64:
|
||||
needs: resolve-version
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "17"
|
||||
architecture: x64
|
||||
- name: Set version
|
||||
run: bash scripts/ci-set-version.sh "${{ needs.resolve-version.outputs.version }}"
|
||||
- name: Install create-dmg
|
||||
run: brew install create-dmg
|
||||
- name: Package macOS Intel
|
||||
run: |
|
||||
export FROMCHAT_PACKAGING_JDK="$JAVA_HOME"
|
||||
./gradlew :app:desktop:packageReleaseMac -PdesktopArch=x64 --no-daemon
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: macos-x64
|
||||
path: app/desktop/build/distributions/release/*-macOS-x64.dmg
|
||||
if-no-files-found: error
|
||||
|
||||
linux-x64:
|
||||
test-compile:
|
||||
needs: resolve-version
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-java@v4
|
||||
- uses: ./.github/actions/setup-java-gradle
|
||||
- uses: android-actions/setup-android@v3
|
||||
- uses: ./.github/actions/set-version
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "17"
|
||||
- name: Install packaging tools
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y fakeroot rpm binutils libfuse2 wget
|
||||
wget -q https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage -O /tmp/appimagetool
|
||||
chmod +x /tmp/appimagetool
|
||||
echo "APPIMAGETOOL=/tmp/appimagetool" >> "$GITHUB_ENV"
|
||||
- name: Set version
|
||||
run: bash scripts/ci-set-version.sh "${{ needs.resolve-version.outputs.version }}"
|
||||
- name: Package Linux x64
|
||||
run: |
|
||||
export FROMCHAT_PACKAGING_JDK="$JAVA_HOME"
|
||||
./gradlew :app:desktop:packageReleaseLinux -PdesktopArch=x64 --no-daemon
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: linux-x64
|
||||
path: |
|
||||
app/desktop/build/distributions/release/*-linux-x64.deb
|
||||
app/desktop/build/distributions/release/*-linux-x64.rpm
|
||||
app/desktop/build/distributions/release/*-linux-x64.AppImage
|
||||
if-no-files-found: error
|
||||
version: ${{ needs.resolve-version.outputs.version }}
|
||||
- name: Compile JVM and Android release sources
|
||||
run: bash scripts/ci-test-compile.sh
|
||||
|
||||
linux-arm64:
|
||||
needs: resolve-version
|
||||
runs-on: ubuntu-24.04-arm
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "17"
|
||||
- name: Install packaging tools
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y fakeroot rpm binutils libfuse2 wget
|
||||
wget -q https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-aarch64.AppImage -O /tmp/appimagetool
|
||||
chmod +x /tmp/appimagetool
|
||||
echo "APPIMAGETOOL=/tmp/appimagetool" >> "$GITHUB_ENV"
|
||||
- name: Set version
|
||||
run: bash scripts/ci-set-version.sh "${{ needs.resolve-version.outputs.version }}"
|
||||
- name: Package Linux ARM64
|
||||
run: |
|
||||
export FROMCHAT_PACKAGING_JDK="$JAVA_HOME"
|
||||
./gradlew :app:desktop:packageReleaseLinux -PdesktopArch=arm64 --no-daemon
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: linux-arm64
|
||||
path: |
|
||||
app/desktop/build/distributions/release/*-linux-arm64.deb
|
||||
app/desktop/build/distributions/release/*-linux-arm64.rpm
|
||||
app/desktop/build/distributions/release/*-linux-arm64.AppImage
|
||||
if-no-files-found: error
|
||||
|
||||
windows-x64:
|
||||
test-rust:
|
||||
needs: resolve-version
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "17"
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: x86_64-pc-windows-msvc
|
||||
- name: Set version
|
||||
- uses: ./.github/actions/cache-cargo-windows
|
||||
- name: Test Windows installer Rust workspace
|
||||
shell: bash
|
||||
run: bash scripts/ci-set-version.sh "${{ needs.resolve-version.outputs.version }}"
|
||||
- name: Package Windows x64
|
||||
shell: bash
|
||||
run: |
|
||||
export FROMCHAT_PACKAGING_JDK="$JAVA_HOME"
|
||||
./gradlew :app:desktop:packageReleaseWindows -PdesktopArch=x64 --no-daemon
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows-x64
|
||||
path: app/desktop/build/distributions/release/*-windows-x64.exe
|
||||
if-no-files-found: error
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows-x64-app-image
|
||||
path: app/desktop/build/compose/binaries/main-release/app/FromChat
|
||||
if-no-files-found: error
|
||||
working-directory: app/desktop/windows-setup
|
||||
run: cargo test --workspace --release
|
||||
|
||||
windows-arm64:
|
||||
needs: resolve-version
|
||||
runs-on: windows-11-arm
|
||||
windows-common:
|
||||
needs: [resolve-version, test-compile, test-rust]
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-java@v4
|
||||
- uses: ./.github/actions/setup-java-gradle
|
||||
- uses: ./.github/actions/set-version
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "17"
|
||||
version: ${{ needs.resolve-version.outputs.version }}
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: Set version
|
||||
shell: bash
|
||||
run: bash scripts/ci-set-version.sh "${{ needs.resolve-version.outputs.version }}"
|
||||
- name: Package Windows ARM64
|
||||
with:
|
||||
targets: x86_64-pc-windows-msvc
|
||||
- uses: ./.github/actions/cache-cargo-windows
|
||||
- name: Build arch-independent Windows tooling and JVM artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
export FROMCHAT_PACKAGING_JDK="$JAVA_HOME"
|
||||
./gradlew :app:desktop:packageReleaseWindows -PdesktopArch=arm64 --no-daemon
|
||||
- uses: actions/upload-artifact@v4
|
||||
./gradlew \
|
||||
:app:desktop:buildWindowsSetupRust \
|
||||
:app:shared:compileKotlinJvm \
|
||||
:utils:shared:compileKotlinJvm \
|
||||
:app:desktop:compileKotlin \
|
||||
:app:desktop:jar \
|
||||
--no-daemon \
|
||||
--console=plain
|
||||
- name: Upload Windows Rust installer tools
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows-arm64
|
||||
path: app/desktop/build/distributions/release/*-windows-arm64.exe
|
||||
name: windows-rust-tools
|
||||
path: app/desktop/windows-setup/target/release/*.exe
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
|
||||
windows-package:
|
||||
needs: [resolve-version, windows-common]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- arch: x64
|
||||
runner: windows-latest
|
||||
java-architecture: ""
|
||||
- arch: arm64
|
||||
runner: windows-11-arm
|
||||
java-architecture: ""
|
||||
runs-on: ${{ matrix.runner }}
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup-java-gradle
|
||||
with:
|
||||
java-architecture: ${{ matrix.java-architecture }}
|
||||
- uses: ./.github/actions/set-version
|
||||
with:
|
||||
version: ${{ needs.resolve-version.outputs.version }}
|
||||
- name: Ensure ARM64 packaging JDK
|
||||
if: matrix.arch == 'arm64'
|
||||
shell: cmd
|
||||
run: call scripts\ensure-windows-arm64-jdk.cmd
|
||||
- name: Restore Windows Rust installer tools
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: windows-rust-tools
|
||||
path: app/desktop/windows-setup/target/release
|
||||
- name: Package Windows ${{ matrix.arch }} installer
|
||||
shell: bash
|
||||
run: bash scripts/ci-package-windows-release.sh "${{ matrix.arch }}"
|
||||
- name: Upload Windows ${{ matrix.arch }} installer
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows-${{ matrix.arch }}
|
||||
path: app/desktop/build/distributions/release/*-windows-${{ matrix.arch }}.exe
|
||||
if-no-files-found: error
|
||||
|
||||
macos:
|
||||
needs: [resolve-version, test-compile, test-rust]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- arch: arm64
|
||||
java-architecture: ""
|
||||
- arch: x64
|
||||
java-architecture: x64
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup-java-gradle
|
||||
with:
|
||||
java-architecture: ${{ matrix.java-architecture }}
|
||||
- uses: ./.github/actions/set-version
|
||||
with:
|
||||
version: ${{ needs.resolve-version.outputs.version }}
|
||||
- name: Cache DMG background tooling
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
app/desktop/dmg-background/node_modules
|
||||
~/.cache/ms-playwright
|
||||
key: dmg-tools-${{ hashFiles('app/desktop/dmg-background/package-lock.json') }}
|
||||
restore-keys: dmg-tools-
|
||||
- name: Install macOS packaging tools
|
||||
run: bash scripts/ci-install-macos-dmg.sh
|
||||
- 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@v4
|
||||
with:
|
||||
name: windows-arm64-app-image
|
||||
path: app/desktop/build/compose/binaries/main-release/app/FromChat
|
||||
name: macos-${{ matrix.arch }}
|
||||
path: app/desktop/build/distributions/release/*-macOS-${{ matrix.arch }}.dmg
|
||||
if-no-files-found: error
|
||||
|
||||
linux:
|
||||
needs: [resolve-version, test-compile, test-rust]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- arch: x64
|
||||
runner: ubuntu-latest
|
||||
- arch: arm64
|
||||
runner: ubuntu-24.04-arm
|
||||
runs-on: ${{ matrix.runner }}
|
||||
timeout-minutes: 75
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/setup-java-gradle
|
||||
- uses: ./.github/actions/set-version
|
||||
with:
|
||||
version: ${{ needs.resolve-version.outputs.version }}
|
||||
- name: Install Linux packaging tools
|
||||
run: bash scripts/ci-install-linux-packaging.sh "${{ matrix.arch }}"
|
||||
- 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@v4
|
||||
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
|
||||
|
||||
android:
|
||||
needs: resolve-version
|
||||
needs: [resolve-version, test-compile, test-rust]
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "17"
|
||||
- uses: ./.github/actions/setup-java-gradle
|
||||
- uses: android-actions/setup-android@v3
|
||||
- name: Set version
|
||||
run: bash scripts/ci-set-version.sh "${{ needs.resolve-version.outputs.version }}"
|
||||
- uses: ./.github/actions/set-version
|
||||
with:
|
||||
version: ${{ needs.resolve-version.outputs.version }}
|
||||
- name: Cache Android SDK extras
|
||||
uses: actions/cache@v4
|
||||
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 }}
|
||||
@@ -247,7 +267,7 @@ debugKeyPassword=android
|
||||
EOF
|
||||
printf '%s' "$GOOGLE_SERVICES_JSON" | base64 -d > app/android/google-services.json
|
||||
- name: Build universal Android APK
|
||||
run: ./gradlew :app:android:assembleRelease --no-daemon
|
||||
run: ./gradlew :app:android:assembleRelease --no-daemon --console=plain
|
||||
- name: Stage APK
|
||||
run: |
|
||||
mkdir -p release-staging
|
||||
@@ -259,43 +279,18 @@ EOF
|
||||
path: release-staging/*.apk
|
||||
if-no-files-found: error
|
||||
|
||||
ios:
|
||||
needs: resolve-version
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: "17"
|
||||
- name: Set version
|
||||
run: bash scripts/ci-set-version.sh "${{ needs.resolve-version.outputs.version }}"
|
||||
- name: Package iOS IPA
|
||||
run: |
|
||||
mkdir -p release-staging
|
||||
bash scripts/package-ios-ipa.sh "${{ needs.resolve-version.outputs.version }}" release-staging
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ios
|
||||
path: release-staging/*.ipa
|
||||
if-no-files-found: error
|
||||
|
||||
publish:
|
||||
verify:
|
||||
needs:
|
||||
- resolve-version
|
||||
- macos-arm64
|
||||
- macos-x64
|
||||
- linux-x64
|
||||
- linux-arm64
|
||||
- windows-x64
|
||||
- windows-arm64
|
||||
- windows-package
|
||||
- macos
|
||||
- linux
|
||||
- android
|
||||
- ios
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install verification tools
|
||||
run: sudo apt-get update && sudo apt-get install -y rpm file unzip
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
@@ -308,29 +303,30 @@ EOF
|
||||
-name '*.rpm' -o \
|
||||
-name '*.AppImage' -o \
|
||||
-name '*.exe' -o \
|
||||
-name '*.apk' -o \
|
||||
-name '*.ipa' \
|
||||
-name '*.apk' \
|
||||
\) -exec cp {} release-assets/ \;
|
||||
ls -la release-assets
|
||||
for pattern in \
|
||||
'*-windows-x64.exe' \
|
||||
'*-windows-arm64.exe' \
|
||||
'*-macOS-x64.dmg' \
|
||||
'*-macOS-arm64.dmg' \
|
||||
'*-linux-x64.deb' \
|
||||
'*-linux-arm64.deb' \
|
||||
'*-linux-x64.rpm' \
|
||||
'*-linux-arm64.rpm' \
|
||||
'*-linux-x64.AppImage' \
|
||||
'*-linux-arm64.AppImage' \
|
||||
'*-android-universal.apk' \
|
||||
'*-ios.ipa'
|
||||
do
|
||||
if ! compgen -G "release-assets/$pattern" > /dev/null; then
|
||||
echo "Missing release asset: $pattern" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
- name: Verify release assets
|
||||
run: bash scripts/ci-verify-release-assets.sh "${{ needs.resolve-version.outputs.version }}" release-assets
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-assets
|
||||
path: release-assets/*
|
||||
if-no-files-found: error
|
||||
|
||||
publish:
|
||||
if: ${{ github.event_name != 'workflow_dispatch' || inputs.skip_publish == false }}
|
||||
needs:
|
||||
- resolve-version
|
||||
- verify
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: release-assets
|
||||
path: release-assets
|
||||
- name: Publish GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
|
||||
@@ -867,10 +867,20 @@ fun findJpackageAppExe(appImageDir: File): File {
|
||||
}
|
||||
}
|
||||
|
||||
fun windowsRustBinariesReady(): Boolean =
|
||||
windowsRustBinaryNames.all { name ->
|
||||
windowsRustReleaseDir.asFile.resolve(name).isFile
|
||||
}
|
||||
|
||||
fun shouldBuildWindowsSetupRust(): Boolean =
|
||||
runningOnWindows && (
|
||||
System.getenv("FROMCHAT_SKIP_RUST_BUILD") != "1" || !windowsRustBinariesReady()
|
||||
)
|
||||
|
||||
val buildWindowsSetupRust = tasks.register<Exec>("buildWindowsSetupRust") {
|
||||
group = "compose desktop"
|
||||
description = "Build Rust setup, helper, and portable launcher (Windows only)."
|
||||
onlyIf { runningOnWindows }
|
||||
onlyIf { shouldBuildWindowsSetupRust() }
|
||||
workingDir = windowsSetupDir.asFile
|
||||
commandLine("cargo", "build", "--release", "--workspace")
|
||||
environment("FROMCHAT_SETUP_VERSION", desktopVersionName)
|
||||
@@ -903,11 +913,27 @@ val buildWindowsSetupRust = tasks.register<Exec>("buildWindowsSetupRust") {
|
||||
}
|
||||
}
|
||||
|
||||
val ensureWindowsRustBinaries = tasks.register("ensureWindowsRustBinaries") {
|
||||
group = "compose desktop"
|
||||
description = "Build or verify pre-staged Windows Rust installer tooling."
|
||||
onlyIf { runningOnWindows }
|
||||
if (shouldBuildWindowsSetupRust()) {
|
||||
dependsOn(buildWindowsSetupRust)
|
||||
} else {
|
||||
doLast {
|
||||
check(windowsRustBinariesReady()) {
|
||||
"Missing Windows Rust binaries under ${windowsRustReleaseDir.asFile}. " +
|
||||
"Run buildWindowsSetupRust or restore CI artifacts."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val patchWindowsJpackageIcon = tasks.register<Exec>("patchWindowsJpackageIcon") {
|
||||
group = "compose desktop"
|
||||
description = "Embed branded icon into jpackage FromChat.exe (Task Manager)."
|
||||
onlyIf { runningOnWindows }
|
||||
dependsOn(buildWindowsSetupRust)
|
||||
dependsOn(ensureWindowsRustBinaries)
|
||||
mustRunAfter("createDistributable")
|
||||
doFirst {
|
||||
val appImageDir = debugAppImageDir.get().asFile
|
||||
@@ -923,7 +949,7 @@ val patchWindowsJpackageReleaseIcon = tasks.register<Exec>("patchWindowsJpackage
|
||||
group = "compose desktop"
|
||||
description = "Embed branded icon into release jpackage FromChat.exe."
|
||||
onlyIf { runningOnWindows }
|
||||
dependsOn(buildWindowsSetupRust)
|
||||
dependsOn(ensureWindowsRustBinaries)
|
||||
mustRunAfter("createReleaseDistributable")
|
||||
doFirst {
|
||||
val appImageDir = releaseAppImageDir.get().asFile
|
||||
@@ -1016,7 +1042,7 @@ tasks.register<Exec>("packUniversalWindows") {
|
||||
group = "compose desktop"
|
||||
description = "Pack a universal Windows setup EXE from prebuilt x64 + arm64 app images."
|
||||
onlyIf { runningOnWindows }
|
||||
dependsOn(buildWindowsSetupRust)
|
||||
dependsOn(ensureWindowsRustBinaries)
|
||||
configureWindowsPackTask(
|
||||
appImageDir = null,
|
||||
setupOutput = windowsUniversalSetupOutput,
|
||||
@@ -1028,7 +1054,7 @@ tasks.register<Exec>("packSetupOnly") {
|
||||
group = "compose desktop"
|
||||
description = "Repack setup EXE from existing app-image + Rust (skips ProGuard)."
|
||||
onlyIf { runningOnWindows }
|
||||
dependsOn(buildWindowsSetupRust)
|
||||
dependsOn(ensureWindowsRustBinaries)
|
||||
configureWindowsPackTask(
|
||||
appImageDir = releaseAppImageDir,
|
||||
setupOutput = windowsSetupOutput,
|
||||
@@ -1039,7 +1065,7 @@ tasks.register<Exec>("packageBetaWindows") {
|
||||
group = "compose desktop"
|
||||
description = "Build debug Windows app-image (no ProGuard) and beta setup EXE."
|
||||
onlyIf { runningOnWindows }
|
||||
dependsOn("createDistributable", buildWindowsSetupRust, patchWindowsJpackageIcon)
|
||||
dependsOn("createDistributable", ensureWindowsRustBinaries, patchWindowsJpackageIcon)
|
||||
configureWindowsPackTask(
|
||||
appImageDir = debugAppImageDir,
|
||||
setupOutput = windowsBetaSetupOutput,
|
||||
@@ -1128,7 +1154,7 @@ tasks.register<Exec>("packageReleaseWindows") {
|
||||
description = "Build release Windows app-image, then setup EXE."
|
||||
onlyIf { runningOnWindows }
|
||||
if (runningOnWindows) {
|
||||
dependsOn("createReleaseDistributable", buildWindowsSetupRust, patchWindowsJpackageReleaseIcon)
|
||||
dependsOn("createReleaseDistributable", ensureWindowsRustBinaries, patchWindowsJpackageReleaseIcon)
|
||||
}
|
||||
configureWindowsPackTask(
|
||||
appImageDir = releaseAppImageDir,
|
||||
@@ -1136,6 +1162,13 @@ tasks.register<Exec>("packageReleaseWindows") {
|
||||
)
|
||||
}
|
||||
|
||||
tasks.register("packageReleaseWindowsAppImage") {
|
||||
group = "compose desktop"
|
||||
description = "Build release Windows app-image and patch launcher icon (no setup EXE)."
|
||||
onlyIf { runningOnWindows }
|
||||
dependsOn("createReleaseDistributable", patchWindowsJpackageReleaseIcon)
|
||||
}
|
||||
|
||||
tasks.register("packageReleaseDesktop") {
|
||||
group = "compose desktop"
|
||||
description = "Build the release desktop package for the current OS."
|
||||
|
||||
Generated
+4273
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ARCH="${1:?usage: ci-install-linux-packaging.sh <x64|arm64>}"
|
||||
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y fakeroot rpm binutils libfuse2 wget file
|
||||
|
||||
case "$ARCH" in
|
||||
x64)
|
||||
TOOL_URL="https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage"
|
||||
;;
|
||||
arm64)
|
||||
TOOL_URL="https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-aarch64.AppImage"
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported arch: $ARCH" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
CACHE_DIR="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}/appimagetool"
|
||||
mkdir -p "$CACHE_DIR"
|
||||
TOOL="$CACHE_DIR/appimagetool-${ARCH}.AppImage"
|
||||
if [[ ! -f "$TOOL" ]]; then
|
||||
wget -q "$TOOL_URL" -O "$TOOL"
|
||||
chmod +x "$TOOL"
|
||||
fi
|
||||
echo "APPIMAGETOOL=$TOOL" >> "$GITHUB_ENV"
|
||||
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
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" install --no-fund --no-audit
|
||||
npm --prefix "$DMG_DIR" exec playwright install chromium
|
||||
fi
|
||||
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ARCH="${1:?usage: ci-package-windows-release.sh <x64|arm64>}"
|
||||
|
||||
export FROMCHAT_PACKAGING_JDK="${JAVA_HOME}"
|
||||
export FROMCHAT_SKIP_RUST_BUILD=1
|
||||
|
||||
GRADLE_ARGS=(
|
||||
:app:desktop:createReleaseDistributable
|
||||
:app:desktop:patchWindowsJpackageReleaseIcon
|
||||
:app:desktop:packSetupOnly
|
||||
"-PdesktopArch=$ARCH"
|
||||
--no-daemon
|
||||
--console=plain
|
||||
)
|
||||
|
||||
if [[ "$ARCH" == "arm64" ]]; then
|
||||
GRADLE_ARGS+=("-PwindowsArm64")
|
||||
fi
|
||||
|
||||
./gradlew "${GRADLE_ARGS[@]}"
|
||||
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
./gradlew \
|
||||
:app:shared:compileKotlinJvm \
|
||||
:utils:shared:compileKotlinJvm \
|
||||
:app:desktop:compileKotlin \
|
||||
:app:shared:compileAndroidMain \
|
||||
:app:android:compileReleaseKotlin \
|
||||
--no-daemon \
|
||||
--console=plain
|
||||
@@ -0,0 +1,147 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
VERSION="${1:?usage: ci-verify-release-assets.sh <version> <assets-dir>}"
|
||||
ASSETS_DIR="${2:?usage: ci-verify-release-assets.sh <version> <assets-dir>}"
|
||||
|
||||
if [[ ! -d "$ASSETS_DIR" ]]; then
|
||||
echo "Assets directory not found: $ASSETS_DIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mapfile -t FILES < <(find "$ASSETS_DIR" -maxdepth 1 -type f | sort)
|
||||
if [[ "${#FILES[@]}" -eq 0 ]]; then
|
||||
echo "No release assets in $ASSETS_DIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
require_file() {
|
||||
local pattern="$1"
|
||||
shopt -s nullglob
|
||||
local matches=("$ASSETS_DIR"/$pattern)
|
||||
shopt -u nullglob
|
||||
if [[ "${#matches[@]}" -eq 0 ]]; then
|
||||
echo "Missing asset matching: $pattern" >&2
|
||||
exit 1
|
||||
fi
|
||||
for file in "${matches[@]}"; do
|
||||
if [[ ! -s "$file" ]]; then
|
||||
echo "Empty asset: $file" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "OK $file ($(wc -c <"$file") bytes)"
|
||||
done
|
||||
}
|
||||
|
||||
pe_machine() {
|
||||
python3 - "$1" <<'PY'
|
||||
import struct, sys
|
||||
path = sys.argv[1]
|
||||
with open(path, "rb") as f:
|
||||
data = f.read(0x40)
|
||||
pe_off = struct.unpack_from("<I", data, 0x3C)[0]
|
||||
machine = struct.unpack_from("<H", data, pe_off + 4)[0]
|
||||
print(machine)
|
||||
PY
|
||||
}
|
||||
|
||||
assert_pe() {
|
||||
local file="$1"
|
||||
local expected="$2"
|
||||
local actual
|
||||
actual="$(pe_machine "$file")"
|
||||
if [[ "$actual" != "$expected" ]]; then
|
||||
echo "PE machine mismatch for $file: got 0x$(printf '%x' "$actual"), want 0x$(printf '%x' "$expected")" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "OK PE arch $file"
|
||||
}
|
||||
|
||||
require_file "FromChat-Setup-${VERSION}-windows-x64.exe"
|
||||
require_file "FromChat-Setup-${VERSION}-windows-arm64.exe"
|
||||
require_file "FromChat-${VERSION}-macOS-x64.dmg"
|
||||
require_file "FromChat-${VERSION}-macOS-arm64.dmg"
|
||||
require_file "FromChat-${VERSION}-linux-x64.deb"
|
||||
require_file "FromChat-${VERSION}-linux-arm64.deb"
|
||||
require_file "FromChat-${VERSION}-linux-x64.rpm"
|
||||
require_file "FromChat-${VERSION}-linux-arm64.rpm"
|
||||
require_file "FromChat-${VERSION}-linux-x64.AppImage"
|
||||
require_file "FromChat-${VERSION}-linux-arm64.AppImage"
|
||||
require_file "FromChat-${VERSION}-android-universal.apk"
|
||||
|
||||
shopt -s nullglob
|
||||
for exe in "$ASSETS_DIR"/FromChat-Setup-"${VERSION}"-windows-x64.exe; do
|
||||
assert_pe "$exe" 34404
|
||||
done
|
||||
for exe in "$ASSETS_DIR"/FromChat-Setup-"${VERSION}"-windows-arm64.exe; do
|
||||
assert_pe "$exe" 43620
|
||||
done
|
||||
shopt -u nullglob
|
||||
|
||||
for dmg in "$ASSETS_DIR"/*.dmg; do
|
||||
if [[ "${CI_SMOKE:-}" == "1" ]]; then
|
||||
echo "OK DMG (smoke) $dmg"
|
||||
continue
|
||||
fi
|
||||
if command -v hdiutil >/dev/null 2>&1; then
|
||||
hdiutil verify "$dmg" >/dev/null
|
||||
elif ! file "$dmg" | grep -qiE 'zlib compressed data|bzip2 compressed data|Apple Disk Image|UDIF'; then
|
||||
echo "DMG does not look valid: $dmg" >&2
|
||||
file "$dmg" || true
|
||||
exit 1
|
||||
fi
|
||||
echo "OK DMG $dmg"
|
||||
done
|
||||
|
||||
for deb in "$ASSETS_DIR"/*.deb; do
|
||||
if [[ "${CI_SMOKE:-}" == "1" ]]; then
|
||||
echo "OK deb (smoke) $deb"
|
||||
continue
|
||||
fi
|
||||
if ! dpkg-deb -I "$deb" >/dev/null 2>&1; then
|
||||
echo "Invalid .deb: $deb" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "OK deb $deb"
|
||||
done
|
||||
|
||||
for rpm in "$ASSETS_DIR"/*.rpm; do
|
||||
if [[ "${CI_SMOKE:-}" == "1" ]]; then
|
||||
echo "OK rpm (smoke) $rpm"
|
||||
continue
|
||||
fi
|
||||
if ! rpm -qp "$rpm" >/dev/null 2>&1; then
|
||||
echo "Invalid .rpm: $rpm" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "OK rpm $rpm"
|
||||
done
|
||||
|
||||
for appimage in "$ASSETS_DIR"/*.AppImage; do
|
||||
if [[ "${CI_SMOKE:-}" == "1" ]]; then
|
||||
echo "OK AppImage (smoke) $appimage"
|
||||
continue
|
||||
fi
|
||||
if ! file "$appimage" | grep -qi 'ELF'; then
|
||||
echo "AppImage is not ELF: $appimage" >&2
|
||||
exit 1
|
||||
fi
|
||||
chmod +x "$appimage"
|
||||
echo "OK AppImage $appimage"
|
||||
done
|
||||
|
||||
APK="$(find "$ASSETS_DIR" -maxdepth 1 -name '*android-universal.apk' | head -n 1)"
|
||||
if [[ "${CI_SMOKE:-}" == "1" ]]; then
|
||||
echo "OK APK (smoke) $APK"
|
||||
elif command -v unzip >/dev/null 2>&1; then
|
||||
if ! unzip -t "$APK" >/dev/null 2>&1; then
|
||||
echo "APK zip integrity failed: $APK" >&2
|
||||
exit 1
|
||||
fi
|
||||
libs="$(unzip -l "$APK" 'lib/*/*.so' | awk '/\.so$/ {print $4}' || true)"
|
||||
echo "$libs" | grep -q 'lib/arm64-v8a/' || { echo "APK missing arm64-v8a libs" >&2; exit 1; }
|
||||
echo "$libs" | grep -q 'lib/x86_64/' || { echo "APK missing x86_64 libs" >&2; exit 1; }
|
||||
echo "OK APK ABIs $APK"
|
||||
fi
|
||||
|
||||
echo "All release assets verified for v$VERSION"
|
||||
Reference in New Issue
Block a user