Skip to content

Merge pull request #370 from OpenHub-Store/remove-ktkonf-sponsorship #39

Merge pull request #370 from OpenHub-Store/remove-ktkonf-sponsorship

Merge pull request #370 from OpenHub-Store/remove-ktkonf-sponsorship #39

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