Skip to content

Merge pull request #214 from 23rd/feat/linux-hidpi-mutter-fractional #200

Merge pull request #214 from 23rd/feat/linux-hidpi-mutter-fractional

Merge pull request #214 from 23rd/feat/linux-hidpi-mutter-fractional #200

name: Release Desktop App (All Platforms)
on:
push:
tags:
- "v*"
workflow_dispatch:
permissions:
contents: write
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
jobs:
build:
name: Build (${{ matrix.os }} / ${{ matrix.arch }})
runs-on: ${{ matrix.os }}
timeout-minutes: 120
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
arch: amd64
- os: ubuntu-24.04-arm
arch: arm64
- os: windows-latest
arch: amd64
- os: windows-11-arm
arch: arm64
node-version: '22' # npm 11 (Node 24) has ECOMPROMISED bugs on Windows ARM64
- os: macos-latest
arch: arm64
jbr-download-url: 'https://github.com/kdroidFilter/JetBrainsRuntime/releases/download/v25.0.2b329.66-rtl/jdk-macos-aarch64.tar.gz'
- os: macos-15-intel
arch: amd64
jbr-download-url: 'https://github.com/kdroidFilter/JetBrainsRuntime/releases/download/v25.0.2b329.66-rtl/jdk-macos-x64.tar.gz'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_VERSION: ${{ github.ref_name }}
steps:
- name: Checkout Repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
fetch-depth: 0
- name: Normalize version for manual runs
if: github.event_name == 'workflow_dispatch'
shell: bash
run: |
set -euo pipefail
tag="$(git describe --tags --abbrev=0)"
echo "RELEASE_VERSION=$tag" >> "$GITHUB_ENV"
- name: Setup Nucleus
uses: ./.github/actions/setup-nucleus
with:
jbr-version: '25.0.2b329.66'
jbr-download-url: ${{ matrix.jbr-download-url || '' }}
packaging-tools: 'true'
flatpak: 'true'
snap: 'true'
setup-gradle: 'true'
setup-node: 'true'
node-version: ${{ matrix.node-version || '24' }}
- name: Build packages
shell: bash
run: ./gradlew :example:packageReleaseDistributionForCurrentOS --stacktrace --no-daemon
- name: Create sandboxed app ZIP (macOS only)
if: runner.os == 'macOS'
shell: bash
run: |
SANDBOXED_APP="$(find example/build/compose/binaries -path '*/app-sandboxed/*.app' -type d | head -1)"
if [[ -n "$SANDBOXED_APP" ]]; then
APP_NAME="$(basename "$SANDBOXED_APP")"
# Derive name from existing ZIP to keep consistent naming
EXISTING_ZIP="$(find example/build/compose/binaries -name '*.zip' -type f | head -1)"
if [[ -n "$EXISTING_ZIP" ]]; then
BASE="$(basename "$EXISTING_ZIP" .zip)"
SANDBOXED_ZIP="example/build/compose/binaries/sandboxed-${BASE}.zip"
else
ARCH="$(uname -m | sed 's/x86_64/x64/;s/arm64/arm64/')"
SANDBOXED_ZIP="example/build/compose/binaries/sandboxed-${APP_NAME%.app}-${ARCH}.zip"
fi
echo "==> Zipping sandboxed .app: $SANDBOXED_APP → $SANDBOXED_ZIP"
ditto -c -k --keepParent "$SANDBOXED_APP" "$SANDBOXED_ZIP"
else
echo "==> No sandboxed .app found (no PKG target?), skipping"
fi
- name: Upload packaged artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: release-assets-${{ runner.os }}-${{ matrix.arch }}
path: |
example/build/compose/binaries/**/*.deb
example/build/compose/binaries/**/*.rpm
example/build/compose/binaries/**/*.snap
example/build/compose/binaries/**/*.flatpak
example/build/compose/binaries/**/*.AppImage
example/build/compose/binaries/**/*.appimage
example/build/compose/binaries/**/*.msi
example/build/compose/binaries/**/*.exe
example/build/compose/binaries/**/*.appx
!example/build/compose/binaries/**/runtime/**
example/build/compose/binaries/**/*.7z
example/build/compose/binaries/**/*.pkg
example/build/compose/binaries/**/*.dmg
example/build/compose/binaries/**/*.zip
example/build/compose/binaries/sandboxed-*.zip
example/build/compose/binaries/**/*.tar
example/build/compose/binaries/**/*.tar.gz
example/build/compose/binaries/**/*.blockmap
example/build/compose/binaries/**/signing-metadata.json
example/build/compose/binaries/**/packaging-metadata.json
!example/build/compose/binaries/**/app/**
if-no-files-found: error
universal-macos:
name: Universal macOS Binary
needs: [build]
if: needs.build.result == 'success'
runs-on: macos-latest
timeout-minutes: 45
env:
HAS_SIGNING_CERTS: ${{ secrets.MAC_CERTIFICATES_P12 != '' }}
HAS_PROVISIONING: ${{ secrets.MAC_PROVISIONING_PROFILE != '' }}
HAS_NOTARIZATION: ${{ secrets.MAC_NOTARIZATION_APPLE_ID != '' }}
steps:
- name: Checkout Repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
sparse-checkout: |
.github/actions
example/packaging/macos
fetch-depth: 1
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '24'
- name: Setup macOS signing
id: signing
if: env.HAS_SIGNING_CERTS == 'true'
uses: ./.github/actions/setup-macos-signing
with:
certificate-base64: ${{ secrets.MAC_CERTIFICATES_P12 }}
certificate-password: ${{ secrets.MAC_CERTIFICATES_PASSWORD }}
- name: Decode provisioning profiles
if: env.HAS_PROVISIONING == 'true'
shell: bash
run: |
echo "${{ secrets.MAC_PROVISIONING_PROFILE }}" | base64 -d > "$RUNNER_TEMP/embedded.provisionprofile"
echo "PROVISIONING=$RUNNER_TEMP/embedded.provisionprofile" >> "$GITHUB_ENV"
if [[ -n "${{ secrets.MAC_RUNTIME_PROVISIONING_PROFILE }}" ]]; then
echo "${{ secrets.MAC_RUNTIME_PROVISIONING_PROFILE }}" | base64 -d > "$RUNNER_TEMP/runtime-embedded.provisionprofile"
echo "RUNTIME_PROVISIONING=$RUNNER_TEMP/runtime-embedded.provisionprofile" >> "$GITHUB_ENV"
fi
- name: Download macOS arm64 artifacts
uses: actions/download-artifact@v4
with:
name: release-assets-macOS-arm64
path: artifacts/release-assets-macOS-arm64
- name: Download macOS amd64 artifacts
uses: actions/download-artifact@v4
with:
name: release-assets-macOS-amd64
path: artifacts/release-assets-macOS-amd64
- name: Build universal binary
uses: ./.github/actions/build-macos-universal
with:
arm64-path: artifacts/release-assets-macOS-arm64
x64-path: artifacts/release-assets-macOS-amd64
output-path: artifacts/release-assets-macOS-universal
signing-identity: ${{ secrets.MAC_DEVELOPER_ID_APPLICATION }}
app-store-identity: ${{ secrets.MAC_APP_STORE_APPLICATION }}
installer-identity: ${{ secrets.MAC_APP_STORE_INSTALLER }}
keychain-path: ${{ steps.signing.outputs.keychain-path }}
entitlements-file: example/packaging/macos/entitlements.plist
runtime-entitlements-file: example/packaging/macos/runtime-entitlements.plist
sandboxed-entitlements-file: example/packaging/macos/entitlements-sandbox.plist
sandboxed-runtime-entitlements-file: example/packaging/macos/runtime-entitlements-sandbox.plist
provisioning-profile: ${{ env.PROVISIONING }}
runtime-provisioning-profile: ${{ env.RUNTIME_PROVISIONING }}
- name: Notarize DMG
if: env.HAS_NOTARIZATION == 'true'
shell: bash
run: |
set -euo pipefail
DMG="$(find artifacts/release-assets-macOS-universal -name '*.dmg' -type f | head -1)"
if [[ -z "$DMG" ]]; then
echo "::warning::No DMG found to notarize"
exit 0
fi
echo "==> Submitting DMG for notarization: $DMG"
xcrun notarytool submit "$DMG" \
--apple-id "${{ secrets.MAC_NOTARIZATION_APPLE_ID }}" \
--password "${{ secrets.MAC_NOTARIZATION_PASSWORD }}" \
--team-id "${{ secrets.MAC_NOTARIZATION_TEAM_ID }}" \
--wait
echo "==> Stapling DMG"
xcrun stapler staple "$DMG"
- name: Notarize ZIP
if: env.HAS_NOTARIZATION == 'true'
shell: bash
run: |
set -euo pipefail
ZIP="$(find artifacts/release-assets-macOS-universal -name '*.zip' -type f | head -1)"
if [[ -z "$ZIP" ]]; then
echo "::warning::No ZIP found to notarize"
exit 0
fi
echo "==> Submitting ZIP for notarization: $ZIP"
xcrun notarytool submit "$ZIP" \
--apple-id "${{ secrets.MAC_NOTARIZATION_APPLE_ID }}" \
--password "${{ secrets.MAC_NOTARIZATION_PASSWORD }}" \
--team-id "${{ secrets.MAC_NOTARIZATION_TEAM_ID }}" \
--wait
# ZIP is NOT stapled: re-zipping would invalidate the blockmap and break
# differential auto-updates. Gatekeeper verifies notarization online anyway.
echo "==> Skipping staple for ZIP (preserves blockmap for auto-update)"
- name: Cleanup keychain
if: ${{ always() && env.HAS_SIGNING_CERTS == 'true' }}
shell: bash
run: security delete-keychain "${{ steps.signing.outputs.keychain-path }}" || true
- name: Upload universal macOS artifacts
uses: actions/upload-artifact@v4
with:
name: release-assets-macOS-universal
path: artifacts/release-assets-macOS-universal
if-no-files-found: error
bundle-windows:
name: Windows APPX Bundle
needs: [build]
if: needs.build.result == 'success'
runs-on: windows-latest
timeout-minutes: 15
steps:
- name: Checkout Repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
sparse-checkout: |
.github/actions
example/packaging
fetch-depth: 1
- name: Download Windows amd64 artifacts
uses: actions/download-artifact@v4
with:
name: release-assets-Windows-amd64
path: artifacts/release-assets-Windows-amd64
- name: Download Windows arm64 artifacts
uses: actions/download-artifact@v4
with:
name: release-assets-Windows-arm64
path: artifacts/release-assets-Windows-arm64
- name: Build APPX Bundle
uses: ./.github/actions/build-windows-appxbundle
with:
amd64-path: artifacts/release-assets-Windows-amd64
arm64-path: artifacts/release-assets-Windows-arm64
output-path: artifacts/release-assets-Windows-bundle
certificate-password: 'ChangeMe-Temp123!'
- name: Upload Windows bundle artifact
uses: actions/upload-artifact@v4
with:
name: release-assets-Windows-bundle
path: artifacts/release-assets-Windows-bundle
if-no-files-found: error
publish:
name: Publish Release
needs: [build, universal-macos, bundle-windows]
if: ${{ !cancelled() && needs.build.result == 'success' }}
runs-on: ubuntu-latest
timeout-minutes: 30
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- name: Checkout Repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
sparse-checkout: .github/actions
fetch-depth: 1
- name: Download all build artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
pattern: release-assets-*
- name: Determine version and channel
shell: bash
run: |
set -euo pipefail
TAG="${GITHUB_REF_NAME}"
VERSION="${TAG#v}"
echo "TAG=$TAG" >> "$GITHUB_ENV"
echo "VERSION=$VERSION" >> "$GITHUB_ENV"
if [[ "$VERSION" == *"-alpha"* ]]; then
echo "CHANNEL=alpha" >> "$GITHUB_ENV"
elif [[ "$VERSION" == *"-beta"* ]]; then
echo "CHANNEL=beta" >> "$GITHUB_ENV"
else
echo "CHANNEL=latest" >> "$GITHUB_ENV"
fi
if [[ "$VERSION" == *"-alpha"* ]] || [[ "$VERSION" == *"-beta"* ]]; then
echo "RELEASE_TYPE=prerelease" >> "$GITHUB_ENV"
else
echo "RELEASE_TYPE=release" >> "$GITHUB_ENV"
fi
- name: List downloaded artifacts
shell: bash
run: find artifacts -type f | head -100
- name: Generate update YML files
uses: ./.github/actions/generate-update-yml
with:
artifacts-path: artifacts
version: ${{ env.VERSION }}
channel: ${{ env.CHANNEL }}
- name: Show generated YML files
shell: bash
run: |
echo "=== Generated update YML files ==="
for f in artifacts/update-yml/*.yml; do
echo "--- $(basename "$f") ---"
cat "$f"
echo
done
- name: Publish release
uses: ./.github/actions/publish-release
with:
artifacts-path: artifacts
tag: ${{ env.TAG }}
release-type: ${{ env.RELEASE_TYPE }}