| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364 |
- name: Release
- # Produces versioned distributables for ALL targets a host can build, into a GitHub Release.
- # Each target verifies its own required artifacts. Publishing uses every successful target, so
- # apk/exe are not held hostage by Linux-only packaging dependencies.
- # linux (deb/rpm/AppImage) + iso -> debian:trixie
- # android (apk) -> ubuntu-latest
- # windows (exe) -> windows-latest
- # macos (dmg) -> macos-latest
- # NOTE: iOS cannot be produced — the project has no iOS Kotlin target (commonMain uses
- # JVM-only libs); there is no iOS step.
- on:
- workflow_dispatch:
- push:
- branches:
- - main
- tags:
- - 'v*'
- # Build jobs only read the repo (least privilege — "read only gh" in the build step);
- # only the publish job below requests write to create the release.
- permissions:
- contents: read
- jobs:
- package-linux-iso:
- name: Package (linux-iso)
- runs-on: ubuntu-latest
- container:
- image: debian:trixie
- options: --privileged
- steps:
- - name: Install Debian build dependencies
- shell: bash
- run: |
- set -euo pipefail
- apt-get update
- apt-get install -y --no-install-recommends \
- bash ca-certificates coreutils curl findutils git \
- tar unzip xz-utils zip zstd \
- build-essential pkg-config rpm fakeroot \
- debootstrap squashfs-tools xorriso mtools dpkg-dev \
- grub-common grub-pc-bin grub-efi-amd64-bin debian-archive-keyring \
- wayland-protocols libwayland-dev libwayland-bin \
- libxkbcommon-dev libcairo2-dev libcairo2 libpixman-1-dev libdrm-dev \
- libseat-dev libgbm-dev libegl-dev libgles-dev \
- libcap-dev liblcms2-dev libsystemd-dev libudev-dev libvulkan-dev \
- libxcb1-dev libxcb-composite0-dev libxcb-dri3-dev \
- libxcb-image0-dev libxcb-present-dev libxcb-render0-dev \
- libxcb-render-util0-dev libxcb-res0-dev libxcb-shm0-dev libxcb-xfixes0-dev \
- libxcb-ewmh-dev libxcb-icccm4-dev libxcb-xinput-dev libxcb-errors-dev \
- libx11-xcb-dev libwlroots-0.18-dev
- wayland-scanner --version
- pkg-config --modversion wlroots-0.18
- - uses: actions/checkout@v4
- - name: Set up JDK 17
- uses: actions/setup-java@v4
- with:
- java-version: '17'
- distribution: 'temurin'
- - name: Setup Gradle
- uses: gradle/actions/setup-gradle@v4
- with:
- cache-read-only: false
- gradle-home-cache-cleanup: true
- - name: Cache Kotlin/Native
- uses: actions/cache@v4
- with:
- path: ~/.konan
- key: ${{ runner.os }}-debian-trixie-konan-${{ hashFiles('gradle/libs.versions.toml', 'gradle.properties', 'settings.gradle.kts', 'build.gradle.kts', 'compositor/*.gradle.kts') }}
- restore-keys: |
- ${{ runner.os }}-debian-trixie-konan-
- - name: Cache AppImage tool
- uses: actions/cache@v4
- with:
- path: .gradle/tools
- key: ${{ runner.os }}-debian-trixie-appimagetool-${{ hashFiles('gradle/libs.versions.toml', 'build.gradle.kts') }}
- restore-keys: |
- ${{ runner.os }}-debian-trixie-appimagetool-
- - name: Make gradlew executable
- shell: bash
- run: chmod +x gradlew || true
- - name: Read app name + version
- id: meta
- shell: bash
- run: |
- echo "version=$(grep -E '^app-pkg-version' gradle/libs.versions.toml | sed -E 's/.*"(.*)".*/\1/')" >> "$GITHUB_OUTPUT"
- echo "appname=$(grep -E '^app-name' gradle/libs.versions.toml | sed -E 's/.*"(.*)".*/\1/')" >> "$GITHUB_OUTPUT"
- # One Gradle invocation keeps dependency resolution/configuration cache hot and avoids
- # re-entering packageFullDeb before makeIso. --continue keeps independent artifacts building
- # when one Linux format fails; artifact collection below publishes whatever succeeded.
- - name: Build linux artifacts
- shell: bash
- continue-on-error: true
- run: ./gradlew packageAppImageFile :desktopApp:packageReleaseRpm packageFullDeb makeIso -PdepCheck=false --stacktrace --continue
- # Primary source is releases/ (collectReleases + makeIso). Fall back to jpackage output
- # dirs for partial builds.
- - name: Collect artifacts
- if: always()
- shell: bash
- run: |
- set -euo pipefail
- VER='${{ steps.meta.outputs.version }}'
- APP='${{ steps.meta.outputs.appname }}'
- BASE="$APP-$VER"
- PKG=packages/main-release
- mkdir -p upload
- copy_pkg() {
- local dir="$1" ext="$2"
- for f in "$dir"/*."$ext"; do
- if [ -e "$f" ]; then
- cp -v "$f" "upload/$BASE.$ext"
- return 0
- fi
- done
- return 0
- }
- if compgen -G "releases/*" > /dev/null; then
- cp -av releases/* upload/
- fi
- [ -f "upload/$BASE.deb" ] || copy_pkg "$PKG/deb" deb
- [ -f "upload/$BASE.rpm" ] || copy_pkg "$PKG/rpm" rpm
- [ -f "upload/$BASE.AppImage" ] || copy_pkg "$PKG/appimage" AppImage
- if [ ! -f "upload/$BASE.iso" ]; then
- for f in releases/*.iso; do
- [ -e "$f" ] && cp -v "$f" "upload/$BASE.iso" && break
- done
- fi
- if [ -d reports ] && [ -n "$(ls -A reports 2>/dev/null)" ]; then
- (cd reports && zip -r "../upload/$BASE-reports.zip" .)
- fi
- echo "=== produced for linux-iso ==="
- ls -la upload/
- require_artifact() {
- local file="upload/$1"
- if [ ! -s "$file" ]; then
- echo "::error::required release artifact is missing or empty: $1"
- exit 1
- fi
- }
- for required in "$BASE.deb" "$BASE.rpm" "$BASE.AppImage" "$BASE.iso"; do
- if [ ! -s "upload/$required" ]; then
- echo "::warning::linux artifact was not produced: $required"
- fi
- done
- require_artifact "$BASE.iso"
- - name: Upload build artifacts
- if: always()
- uses: actions/upload-artifact@v4
- with:
- name: dist-linux-iso
- path: upload/
- if-no-files-found: warn
- package:
- name: Package (${{ matrix.target }})
- strategy:
- fail-fast: false
- matrix:
- include:
- - os: ubuntu-latest
- target: android
- - os: windows-latest
- target: windows
- - os: macos-latest
- target: macos
- runs-on: ${{ matrix.os }}
- steps:
- - uses: actions/checkout@v4
- - name: Set up JDK 17
- uses: actions/setup-java@v4
- with:
- java-version: '17'
- distribution: 'temurin'
- - name: Setup Gradle
- uses: gradle/actions/setup-gradle@v4
- with:
- cache-read-only: false
- gradle-home-cache-cleanup: true
- - name: Make gradlew executable
- shell: bash
- run: chmod +x gradlew || true
- - name: Read app name + version
- id: meta
- shell: bash
- run: |
- echo "version=$(grep -E '^app-pkg-version' gradle/libs.versions.toml | sed -E 's/.*"(.*)".*/\1/')" >> "$GITHUB_OUTPUT"
- echo "appname=$(grep -E '^app-name' gradle/libs.versions.toml | sed -E 's/.*"(.*)".*/\1/')" >> "$GITHUB_OUTPUT"
- # Decode the Android release keystore from a base64 secret (optional). Without the
- # secret the APK is debug-signed by the build fallback — still installable.
- - name: Decode Android keystore
- if: matrix.target == 'android'
- shell: bash
- env:
- ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
- run: |
- if [ -n "$ANDROID_KEYSTORE_BASE64" ]; then
- echo "$ANDROID_KEYSTORE_BASE64" | base64 -d > "$RUNNER_TEMP/release.keystore"
- echo "ANDROID_KEYSTORE_FILE=$RUNNER_TEMP/release.keystore" >> "$GITHUB_ENV"
- echo "Android release keystore decoded"
- else
- echo "::warning::no ANDROID_KEYSTORE_BASE64 secret — APK will be debug-signed"
- fi
- # ---------- android (.apk) ----------
- - name: Build android .apk
- if: matrix.target == 'android'
- shell: bash
- env:
- ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
- ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
- ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
- run: ./gradlew :androidApp:assembleRelease -PdepCheck=false --stacktrace
- # ---------- windows (.exe) ----------
- - name: Build windows .exe
- if: matrix.os == 'windows-latest'
- shell: bash
- run: ./gradlew :desktopApp:packageReleaseExe -PdepCheck=false --stacktrace
- # ---------- macos (.dmg) ----------
- # Signing/notarization activate only when the Apple Developer ID secrets are present
- # (see desktopApp.gradle.kts macOS block); otherwise the .dmg is built unsigned.
- - name: Build macOS .dmg
- if: matrix.os == 'macos-latest'
- shell: bash
- env:
- MACOS_SIGN_IDENTITY: ${{ secrets.MACOS_SIGN_IDENTITY }}
- MACOS_BUNDLE_ID: ${{ secrets.MACOS_BUNDLE_ID }}
- MACOS_NOTARY_APPLE_ID: ${{ secrets.MACOS_NOTARY_APPLE_ID }}
- MACOS_NOTARY_PASSWORD: ${{ secrets.MACOS_NOTARY_PASSWORD }}
- MACOS_NOTARY_TEAM_ID: ${{ secrets.MACOS_NOTARY_TEAM_ID }}
- run: ./gradlew :desktopApp:packageReleaseDmg -PdepCheck=false --stacktrace
- # ---------- collect whatever got produced (versioned names) ----------
- # Primary source is releases/ (collectReleases + makeIso). Fall back to jpackage output
- # dirs for partial builds and for platform-specific runners (exe/dmg).
- - name: Collect artifacts
- if: always()
- shell: bash
- run: |
- set -euo pipefail
- VER='${{ steps.meta.outputs.version }}'
- APP='${{ steps.meta.outputs.appname }}'
- BASE="$APP-$VER"
- PKG=packages/main-release
- mkdir -p upload
- copy_pkg() {
- local dir="$1" ext="$2"
- for f in "$dir"/*."$ext"; do
- if [ -e "$f" ]; then
- cp -v "$f" "upload/$BASE.$ext"
- return 0
- fi
- done
- return 0
- }
- if compgen -G "releases/*" > /dev/null; then
- cp -av releases/* upload/
- fi
- copy_pkg "$PKG/exe" exe
- copy_pkg "$PKG/dmg" dmg
- if [ ! -f "upload/$BASE.apk" ]; then
- for f in androidApp/build/outputs/apk/release/*.apk; do
- [ -e "$f" ] && cp -v "$f" "upload/$BASE.apk" && break
- done
- fi
- if [ -d reports ] && [ -n "$(ls -A reports 2>/dev/null)" ]; then
- (cd reports && zip -r "../upload/$BASE-reports.zip" .)
- fi
- echo "=== produced for ${{ matrix.target }} ==="
- ls -la upload/
- require_artifact() {
- local file="upload/$1"
- if [ ! -s "$file" ]; then
- echo "::error::required release artifact is missing or empty: $1"
- exit 1
- fi
- }
- case '${{ matrix.target }}' in
- android)
- require_artifact "$BASE.apk"
- ;;
- windows)
- require_artifact "$BASE.exe"
- ;;
- macos)
- require_artifact "$BASE.dmg"
- ;;
- esac
- - name: Upload build artifacts
- if: always()
- uses: actions/upload-artifact@v4
- with:
- name: dist-${{ matrix.target }}
- path: upload/
- if-no-files-found: warn
- release:
- name: Publish GitHub Release
- needs:
- - package-linux-iso
- - package
- if: always()
- runs-on: ubuntu-latest
- permissions:
- contents: write
- steps:
- - uses: actions/checkout@v4
- - name: Read version
- id: ver
- shell: bash
- run: echo "version=$(grep -E '^app-pkg-version' gradle/libs.versions.toml | sed -E 's/.*"(.*)".*/\1/')" >> "$GITHUB_OUTPUT"
- - name: Download all artifacts
- uses: actions/download-artifact@v4
- with:
- path: dist
- merge-multiple: true
- - name: List collected
- shell: bash
- run: ls -la dist/ || echo "no artifacts produced"
- - name: Publish to GitHub Release
- uses: softprops/action-gh-release@v2
- with:
- tag_name: ${{ github.ref_type == 'tag' && github.ref_name || format('v{0}', steps.ver.outputs.version) }}
- name: mjdev-desktop v${{ steps.ver.outputs.version }}
- files: dist/**
- fail_on_unmatched_files: false
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|