Merge pull request #370 from OpenHub-Store/remove-ktkonf-sponsorship #39
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build Desktop Platform Installers | |
| on: | |
| push: | |
| branches: | |
| - generate-installers | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| JAVA_VERSION: '21' | |
| JAVA_DISTRIBUTION: 'temurin' | |
| GRADLE_OPTS: >- | |
| -Dorg.gradle.daemon=false | |
| -Dorg.gradle.parallel=true | |
| -Dorg.gradle.caching=true | |
| -Dorg.gradle.vfs.watch=false | |
| jobs: | |
| build-windows: | |
| runs-on: windows-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Validate Gradle wrapper | |
| uses: gradle/actions/wrapper-validation@v4 | |
| - name: Set up JDK ${{ env.JAVA_VERSION }} | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: ${{ env.JAVA_DISTRIBUTION }} | |
| java-version: ${{ env.JAVA_VERSION }} | |
| - name: Setup Gradle | |
| uses: gradle/actions/setup-gradle@v4 | |
| with: | |
| cache-read-only: false | |
| cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} | |
| gradle-home-cache-cleanup: true | |
| - name: Grant execute permission for gradlew | |
| run: chmod +x gradlew | |
| shell: bash | |
| - name: Build Windows installers (EXE & MSI) | |
| run: | | |
| set -euo pipefail | |
| retry() { | |
| local n=1 max=3 delay=5 | |
| while true; do | |
| echo "Attempt #$n: $*" | |
| "$@" && break | |
| [ $n -ge $max ] && { echo "Failed after $n attempts."; return 1; } | |
| n=$((n+1)); echo "Retrying in ${delay}s..."; sleep $delay; delay=$((delay*2)) | |
| done | |
| } | |
| retry ./gradlew :composeApp:packageExe :composeApp:packageMsi | |
| shell: bash | |
| - name: Upload Windows installers | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: windows-installers | |
| path: | | |
| composeApp/build/compose/binaries/main/exe/*.exe | |
| composeApp/build/compose/binaries/main/msi/*.msi | |
| retention-days: 30 | |
| compression-level: 6 | |
| build-macos: | |
| strategy: | |
| matrix: | |
| include: | |
| - os: macos-15-intel | |
| arch: x64 | |
| - os: macos-latest | |
| arch: arm64 | |
| runs-on: ${{ matrix.os }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Validate Gradle wrapper | |
| uses: gradle/actions/wrapper-validation@v4 | |
| - name: Set up JDK ${{ env.JAVA_VERSION }} | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: ${{ env.JAVA_DISTRIBUTION }} | |
| java-version: ${{ env.JAVA_VERSION }} | |
| - name: Setup Gradle | |
| uses: gradle/actions/setup-gradle@v4 | |
| with: | |
| cache-read-only: false | |
| cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} | |
| gradle-home-cache-cleanup: true | |
| - name: Grant execute permission for gradlew | |
| run: chmod +x gradlew | |
| - name: Build macOS installers (DMG & PKG) | |
| run: | | |
| set -euo pipefail | |
| retry() { | |
| local n=1 max=3 delay=5 | |
| while true; do | |
| echo "Attempt #$n: $*" | |
| "$@" && break | |
| [ $n -ge $max ] && { echo "Failed after $n attempts."; return 1; } | |
| n=$((n+1)); echo "Retrying in ${delay}s..."; sleep $delay; delay=$((delay*2)) | |
| done | |
| } | |
| retry ./gradlew :composeApp:packageDmg :composeApp:packagePkg | |
| shell: bash | |
| - name: Upload macOS installers | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: macos-installers-${{ matrix.arch }} | |
| path: | | |
| composeApp/build/compose/binaries/main/dmg/*.dmg | |
| composeApp/build/compose/binaries/main/pkg/*.pkg | |
| retention-days: 30 | |
| compression-level: 6 | |
| build-linux: | |
| strategy: | |
| matrix: | |
| include: | |
| - os: ubuntu-latest | |
| label: modern | |
| gradle-tasks: >- | |
| :composeApp:packageDeb | |
| :composeApp:packageRpm | |
| :composeApp:packageAppImage | |
| - os: ubuntu-22.04 | |
| label: debian12-compat | |
| gradle-tasks: >- | |
| :composeApp:packageDeb | |
| :composeApp:packageRpm | |
| runs-on: ${{ matrix.os }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Validate Gradle wrapper | |
| uses: gradle/actions/wrapper-validation@v4 | |
| - name: Set up JDK ${{ env.JAVA_VERSION }} | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: ${{ env.JAVA_DISTRIBUTION }} | |
| java-version: ${{ env.JAVA_VERSION }} | |
| - name: Setup Gradle | |
| uses: gradle/actions/setup-gradle@v4 | |
| with: | |
| cache-read-only: false | |
| cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} | |
| gradle-home-cache-cleanup: true | |
| - name: Grant execute permission for gradlew | |
| run: chmod +x gradlew | |
| - name: Build Linux installers | |
| run: | | |
| set -euo pipefail | |
| retry() { | |
| local n=1 max=3 delay=5 | |
| while true; do | |
| echo "Attempt #$n: $*" | |
| "$@" && break | |
| [ $n -ge $max ] && { echo "Failed after $n attempts."; return 1; } | |
| n=$((n+1)); echo "Retrying in ${delay}s..."; sleep $delay; delay=$((delay*2)) | |
| done | |
| } | |
| retry ./gradlew ${{ matrix.gradle-tasks }} | |
| shell: bash | |
| - name: List AppImage build output | |
| if: matrix.label == 'modern' | |
| run: | | |
| echo "=== Listing build output ===" | |
| find composeApp/build/compose/binaries/main -maxdepth 3 -type d 2>/dev/null || echo "Directory not found" | |
| echo "=== All files ===" | |
| find composeApp/build/compose/binaries/main -maxdepth 4 -type f 2>/dev/null | head -30 || echo "No files found" | |
| shell: bash | |
| - name: Build AppImage with appimagetool | |
| if: matrix.label == 'modern' | |
| run: | | |
| set -euo pipefail | |
| # Find the directory containing the app launcher (bin/GitHub-Store) | |
| APP_ROOT="" | |
| for candidate in \ | |
| composeApp/build/compose/binaries/main/app-image/GitHub-Store \ | |
| composeApp/build/compose/binaries/main/app/GitHub-Store \ | |
| composeApp/build/compose/binaries/main/app-image \ | |
| composeApp/build/compose/binaries/main/app; do | |
| if [ -f "$candidate/bin/GitHub-Store" ]; then | |
| APP_ROOT="$candidate" | |
| echo "Found app root at: $candidate" | |
| break | |
| fi | |
| done | |
| if [ -z "$APP_ROOT" ]; then | |
| echo "ERROR: Could not find app launcher (bin/GitHub-Store)" | |
| find composeApp/build/compose/binaries/main -type f -name "GitHub-Store" 2>/dev/null || true | |
| exit 1 | |
| fi | |
| # Download appimagetool | |
| wget -q https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage | |
| chmod +x appimagetool-x86_64.AppImage | |
| # Create AppDir from Compose output | |
| APPDIR="GitHub-Store.AppDir" | |
| mv "$APP_ROOT" "$APPDIR" | |
| # Create AppRun entry point | |
| cat > "$APPDIR/AppRun" << 'EOF' | |
| #!/bin/bash | |
| SELF=$(readlink -f "$0") | |
| HERE=${SELF%/*} | |
| exec "${HERE}/bin/GitHub-Store" "$@" | |
| EOF | |
| chmod +x "$APPDIR/AppRun" | |
| # Create .desktop file | |
| cat > "$APPDIR/github-store.desktop" << 'EOF' | |
| [Desktop Entry] | |
| Type=Application | |
| Name=GitHub Store | |
| Exec=GitHub-Store | |
| Icon=github-store | |
| Categories=Development; | |
| Comment=Cross-platform app store for GitHub releases | |
| EOF | |
| # Copy icon to AppDir root (required by appimagetool) | |
| cp "$APPDIR/lib/GitHub-Store.png" "$APPDIR/github-store.png" | |
| # Build .AppImage | |
| OUTPUT="composeApp/build/compose/binaries/main/GitHub-Store-x86_64.AppImage" | |
| UPINFO="gh-releases-zsync|rainxchzed|Github-Store|latest|*x86_64.AppImage.zsync" | |
| ARCH=x86_64 APPIMAGE_EXTRACT_AND_RUN=1 ./appimagetool-x86_64.AppImage -u "$UPINFO" "$APPDIR" "$OUTPUT" | |
| # appimagetool may place .zsync in the working directory; move it next to the AppImage | |
| ZSYNC_NAME="$(basename "$OUTPUT").zsync" | |
| if [ -f "$ZSYNC_NAME" ] && [ ! -f "$OUTPUT.zsync" ]; then | |
| mv "$ZSYNC_NAME" "$OUTPUT.zsync" | |
| fi | |
| echo "Created AppImage and zsync:" | |
| ls -lh "$OUTPUT" "$OUTPUT.zsync" | |
| shell: bash | |
| - name: Patch deb scripts for headless/WSL compatibility | |
| run: | | |
| set -euo pipefail | |
| for deb in composeApp/build/compose/binaries/main/deb/*.deb; do | |
| [ -f "$deb" ] || continue | |
| echo "Patching: $deb" | |
| tmpdir=$(mktemp -d) | |
| dpkg-deb -R "$deb" "$tmpdir" | |
| for script in "$tmpdir/DEBIAN/postinst" "$tmpdir/DEBIAN/prerm" "$tmpdir/DEBIAN/postrm"; do | |
| [ -f "$script" ] || continue | |
| # Make xdg-desktop-menu / xdg-icon-resource / xdg-mime calls non-fatal | |
| # so install/remove succeeds in headless environments (WSL, containers, servers) | |
| sed -i '/xdg-desktop-menu\|xdg-icon-resource\|xdg-mime/{/|| true$/!s/$/ || true/}' "$script" | |
| done | |
| dpkg-deb -b "$tmpdir" "$deb" | |
| rm -rf "$tmpdir" | |
| echo "Patched successfully: $deb" | |
| done | |
| shell: bash | |
| - name: Upload Linux installers | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: linux-installers-${{ matrix.label }} | |
| path: | | |
| composeApp/build/compose/binaries/main/deb/*.deb | |
| composeApp/build/compose/binaries/main/rpm/*.rpm | |
| retention-days: 30 | |
| compression-level: 6 | |
| - name: Upload Linux AppImage | |
| if: matrix.label == 'modern' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: linux-appimage | |
| path: | | |
| composeApp/build/compose/binaries/main/GitHub-Store-x86_64.AppImage | |
| composeApp/build/compose/binaries/main/GitHub-Store-x86_64.AppImage.zsync | |
| if-no-files-found: error | |
| retention-days: 30 | |
| compression-level: 0 | |
| - name: Build Arch Linux package (.pkg.tar.zst) | |
| if: matrix.label == 'modern' | |
| run: | | |
| set -euo pipefail | |
| VERSION=$(grep 'projectVersionName' gradle/libs.versions.toml | head -1 | sed 's/.*= *"\(.*\)"/\1/') | |
| PKG_NAME="github-store" | |
| PKG_DIR="pkg-root" | |
| # Find the app directory produced by packageAppImage | |
| APP_ROOT="" | |
| for candidate in \ | |
| composeApp/build/compose/binaries/main/app/GitHub-Store \ | |
| composeApp/build/compose/binaries/main/app-image/GitHub-Store; do | |
| if [ -f "$candidate/bin/GitHub-Store" ]; then | |
| APP_ROOT="$candidate" | |
| break | |
| fi | |
| done | |
| # Fall back to the AppDir we created earlier if still present | |
| if [ -z "$APP_ROOT" ] && [ -f "GitHub-Store.AppDir/bin/GitHub-Store" ]; then | |
| APP_ROOT="GitHub-Store.AppDir" | |
| fi | |
| if [ -z "$APP_ROOT" ]; then | |
| echo "ERROR: Could not find app directory for Arch packaging" | |
| exit 1 | |
| fi | |
| # Build the package tree | |
| mkdir -p "$PKG_DIR/opt/github-store" | |
| cp -a "$APP_ROOT"/. "$PKG_DIR/opt/github-store/" | |
| # Launcher symlink | |
| mkdir -p "$PKG_DIR/usr/bin" | |
| ln -s "/opt/github-store/bin/GitHub-Store" "$PKG_DIR/usr/bin/github-store" | |
| # Desktop entry | |
| mkdir -p "$PKG_DIR/usr/share/applications" | |
| cat > "$PKG_DIR/usr/share/applications/github-store.desktop" << 'EOF' | |
| [Desktop Entry] | |
| Type=Application | |
| Name=GitHub Store | |
| Exec=/opt/github-store/bin/GitHub-Store | |
| Icon=github-store | |
| Categories=Development; | |
| Comment=Cross-platform app store for GitHub releases | |
| StartupWMClass=github-store | |
| EOF | |
| sed -i 's/^ //' "$PKG_DIR/usr/share/applications/github-store.desktop" | |
| # Icon | |
| if [ -f "$PKG_DIR/opt/github-store/lib/GitHub-Store.png" ]; then | |
| mkdir -p "$PKG_DIR/usr/share/icons/hicolor/256x256/apps" | |
| cp "$PKG_DIR/opt/github-store/lib/GitHub-Store.png" \ | |
| "$PKG_DIR/usr/share/icons/hicolor/256x256/apps/github-store.png" | |
| fi | |
| # .PKGINFO (pacman metadata — must be a flat file at archive root) | |
| INSTALLED_SIZE=$(du -sb "$PKG_DIR" | cut -f1) | |
| cat > "$PKG_DIR/.PKGINFO" << EOF | |
| pkgname = ${PKG_NAME} | |
| pkgver = ${VERSION}-1 | |
| pkgdesc = Cross-platform app store for GitHub releases | |
| url = https://github.com/OpenHub-Store/GitHub-Store | |
| builddate = $(date +%s) | |
| packager = GitHub Actions | |
| arch = x86_64 | |
| license = GPL-3.0 | |
| size = ${INSTALLED_SIZE} | |
| depend = java-runtime>=21 | |
| depend = hicolor-icon-theme | |
| EOF | |
| sed -i 's/^ //' "$PKG_DIR/.PKGINFO" | |
| # Create .pkg.tar.zst | |
| sudo apt-get install -y zstd | |
| OUTPUT_DIR="composeApp/build/compose/binaries/main/arch" | |
| mkdir -p "$OUTPUT_DIR" | |
| ARCHIVE="${PKG_NAME}-${VERSION}-1-x86_64.pkg.tar.zst" | |
| cd "$PKG_DIR" | |
| # .PKGINFO must come first in the archive | |
| tar --zstd -cf "../${OUTPUT_DIR}/${ARCHIVE}" .PKGINFO opt usr | |
| cd .. | |
| echo "Created Arch package:" | |
| ls -lh "${OUTPUT_DIR}/${ARCHIVE}" | |
| shell: bash | |
| - name: Upload Arch Linux package | |
| if: matrix.label == 'modern' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: linux-arch | |
| path: composeApp/build/compose/binaries/main/arch/*.pkg.tar.zst | |
| if-no-files-found: error | |
| retention-days: 30 | |
| compression-level: 0 |