mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-26 05:32:07 -04:00
1247 lines
51 KiB
YAML
1247 lines
51 KiB
YAML
name: Release
|
|
|
|
# Builds the macOS, Windows, and Linux desktop apps, an Android APK, an iOS
|
|
# IPA, a Nintendo Switch SD-ready zip (experimental), Xbox UWP, the Anbernic
|
|
# RG34XXSP (Stock OS 64-bit MOD / PortMaster) and Linux ARM SBC PortMaster
|
|
# handheld ports on the self-hosted Mac runner, and publishes them as a
|
|
# GitHub Release.
|
|
#
|
|
# Versioning:
|
|
# - First ever release is 0.1.0.
|
|
# - Every push to main auto-increments the patch: 0.1.0 -> 0.1.1 -> ... -> 0.1.99,
|
|
# then rolls over to 0.2.0 and keeps going.
|
|
# - To force a specific version, either:
|
|
# * run this workflow manually (Actions tab) and type it into "version", or
|
|
# * put "[release X.Y.Z]" anywhere in the commit message.
|
|
#
|
|
# Branch model: day-to-day work merges to `dev`. Releases stay on `main` only
|
|
# so promoting `dev` -> `main` is the ship gate that cuts a build.
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
# CI/workflow and docs-only changes don't ship anything to users, so they
|
|
# don't earn a release. A push touching these *and* real source still
|
|
# releases; only pushes confined entirely to these paths are skipped.
|
|
paths-ignore:
|
|
- '.github/**'
|
|
- '**.md'
|
|
- 'mobile/ios/app-repo.json'
|
|
workflow_dispatch:
|
|
inputs:
|
|
version:
|
|
description: "Exact version to release (e.g. 0.2.0). Leave blank to auto-increment."
|
|
required: false
|
|
default: ""
|
|
|
|
permissions:
|
|
contents: write
|
|
actions: write
|
|
issues: read
|
|
pull-requests: read
|
|
|
|
concurrency:
|
|
group: release
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
version:
|
|
name: determine release version
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
version: ${{ steps.ver.outputs.version }}
|
|
tag: ${{ steps.ver.outputs.tag }}
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
with:
|
|
fetch-depth: 0
|
|
fetch-tags: true
|
|
- name: Determine version
|
|
id: ver
|
|
env:
|
|
DISPATCH_VERSION: ${{ github.event.inputs.version }}
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
set -euo pipefail
|
|
semver_re='^[0-9]+\.[0-9]+\.[0-9]+$'
|
|
|
|
# 1) Explicit override from a manual run.
|
|
override=""
|
|
if [ -n "${DISPATCH_VERSION:-}" ]; then
|
|
override="$DISPATCH_VERSION"
|
|
else
|
|
# 2) Override from the commit message: [release X.Y.Z]
|
|
msg="$(git log -1 --pretty=%B || true)"
|
|
tag_ver="$(printf '%s' "$msg" | sed -n -E 's/.*\[release[[:space:]]+([0-9]+\.[0-9]+\.[0-9]+)\].*/\1/p' | head -1)"
|
|
if [ -n "$tag_ver" ]; then
|
|
override="$tag_ver"
|
|
fi
|
|
fi
|
|
|
|
if [ -n "$override" ]; then
|
|
if ! printf '%s' "$override" | grep -Eq "$semver_re"; then
|
|
echo "::error::Invalid version override '$override' (expected X.Y.Z)"
|
|
exit 1
|
|
fi
|
|
version="$override"
|
|
echo "Using override version: $version"
|
|
else
|
|
# 3) Auto-increment from the highest existing vX.Y.Z tag.
|
|
latest="$(git tag -l 'v*' \
|
|
| sed -E 's/^v//' \
|
|
| grep -E "$semver_re" \
|
|
| sort -t. -k1,1n -k2,2n -k3,3n \
|
|
| tail -1 || true)"
|
|
if [ -z "$latest" ]; then
|
|
version="0.1.0"
|
|
echo "No existing release tag; starting at $version"
|
|
else
|
|
major="${latest%%.*}"
|
|
rest="${latest#*.}"
|
|
minor="${rest%%.*}"
|
|
patch="${rest##*.}"
|
|
patch=$((patch + 1))
|
|
if [ "$patch" -gt 99 ]; then
|
|
minor=$((minor + 1))
|
|
patch=0
|
|
fi
|
|
version="${major}.${minor}.${patch}"
|
|
echo "Latest was $latest; next is $version"
|
|
fi
|
|
fi
|
|
|
|
tag="v${version}"
|
|
if git rev-parse -q --verify "refs/tags/$tag" >/dev/null; then
|
|
echo "::error::Tag $tag already exists. Pick a different version."
|
|
exit 1
|
|
fi
|
|
if gh release view "$tag" >/dev/null 2>&1; then
|
|
echo "::error::Release $tag already exists. Pick a different version."
|
|
exit 1
|
|
fi
|
|
echo "version=$version" >> "$GITHUB_OUTPUT"
|
|
echo "tag=$tag" >> "$GITHUB_OUTPUT"
|
|
|
|
love-payload:
|
|
name: build release game.love
|
|
needs: version
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
- name: Build shared payload
|
|
run: |
|
|
scripts/pack_love.sh \
|
|
--output dist/payload/game.love \
|
|
--listing dist/payload/love-listing.txt \
|
|
--version "${{ needs.version.outputs.version }}"
|
|
- name: Upload shared payload
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: gen1recomp-release-love
|
|
path: dist/payload/game.love
|
|
if-no-files-found: error
|
|
retention-days: 7
|
|
|
|
linux-arm64:
|
|
name: build Linux arm64 AppImage
|
|
needs: [version, love-payload, shaderfx-bridge]
|
|
# GitHub's free arm64 runner for public repos. It has to be arm64: the
|
|
# AppImage compiles LÖVE natively inside a Debian bullseye arm64
|
|
# container, and the qemu-emulated alternative takes hours.
|
|
runs-on: ubuntu-24.04-arm
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
- name: Download shared payload
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: gen1recomp-release-love
|
|
path: .bazinga/work
|
|
- name: Download the ShaderFX bridge
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: shaderfx-bridge-linux-arm64
|
|
path: dist/native/linux-arm64
|
|
- name: Restore compiled arm64 dependencies
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: .bazinga/cache/linux-arm64
|
|
key: linux-arm64-deps-${{ hashFiles('scripts/linux-arm64/**', 'scripts/build_linux_arm64.sh') }}
|
|
|
|
- name: Build Linux arm64 AppImage
|
|
env:
|
|
SHADERFX_BRIDGE_REQUIRED: "1"
|
|
run: |
|
|
set -euo pipefail
|
|
scripts/build_linux_arm64.sh \
|
|
--version "${{ needs.version.outputs.version }}" \
|
|
--game-love .bazinga/work/game.love
|
|
- name: Verify the AppImage is self-contained and bullseye-compatible
|
|
env:
|
|
SHADERFX_BRIDGE_REQUIRED: "1"
|
|
run: bash scripts/linux-arm64/verify_appimage.sh "dist/linux-arm64/gen1recomp-${{ needs.version.outputs.version }}-linux-arm64.AppImage"
|
|
- name: Upload Linux arm64 release
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: gen1recomp-linux-arm64-release
|
|
path: |
|
|
dist/linux-arm64/gen1recomp-${{ needs.version.outputs.version }}-linux-arm64.AppImage
|
|
dist/linux-arm64/gen1recomp-${{ needs.version.outputs.version }}-linux-arm64.AppImage.sha256
|
|
if-no-files-found: error
|
|
retention-days: 7
|
|
|
|
linux-flatpak:
|
|
name: build Linux Flatpak
|
|
needs: [version, love-payload, shaderfx-bridge]
|
|
runs-on: ubuntu-24.04
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
- name: Install flatpak-builder deps
|
|
run: |
|
|
set -euo pipefail
|
|
sudo apt-get update
|
|
sudo apt-get install -y flatpak flatpak-builder elfutils imagemagick
|
|
- name: Download shared payload
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: gen1recomp-release-love
|
|
path: .bazinga/work
|
|
- name: Download the ShaderFX bridge
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: shaderfx-bridge-linux-x64
|
|
path: dist/native/linux-x64
|
|
- name: Restore the Flatpak runtime
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: ~/.local/share/flatpak
|
|
key: flatpak-runtime-${{ runner.arch }}-${{ hashFiles('flatpak/*.yml', 'scripts/build_flatpak.sh') }}
|
|
|
|
- name: Build Flatpak bundle
|
|
env:
|
|
SHADERFX_BRIDGE_REQUIRED: "1"
|
|
run: |
|
|
set -euo pipefail
|
|
scripts/build_flatpak.sh \
|
|
--version "${{ needs.version.outputs.version }}" \
|
|
--game-love .bazinga/work/game.love
|
|
- name: Upload Flatpak release
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: gen1recomp-linux-flatpak-release
|
|
path: |
|
|
dist/flatpak/gen1recomp-${{ needs.version.outputs.version }}-linux.flatpak
|
|
dist/flatpak/gen1recomp-${{ needs.version.outputs.version }}-linux.flatpak.sha256
|
|
if-no-files-found: error
|
|
retention-days: 7
|
|
|
|
xbox-uwp:
|
|
name: build Xbox UWP release
|
|
needs: [version, love-payload]
|
|
runs-on: windows-2022
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
- name: Download shared payload
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: gen1recomp-release-love
|
|
path: .bazinga/work
|
|
- name: Prepare signing certificate
|
|
shell: pwsh
|
|
env:
|
|
CERTIFICATE_BASE64: ${{ secrets.XBOX_UWP_SIGNING_CERTIFICATE }}
|
|
CERTIFICATE_PASSWORD: ${{ secrets.XBOX_UWP_SIGNING_PASSWORD }}
|
|
CANONICAL_REPOSITORY: ${{ github.repository == 'bryanthaboi/gen1recomp' }}
|
|
run: |
|
|
if ($env:CANONICAL_REPOSITORY -eq 'true' -and
|
|
[string]::IsNullOrWhiteSpace($env:CERTIFICATE_BASE64)) {
|
|
throw 'XBOX_UWP_SIGNING_CERTIFICATE is not configured.'
|
|
}
|
|
if ([string]::IsNullOrWhiteSpace($env:CERTIFICATE_BASE64)) {
|
|
"UWP_PUBLISHER=CN=Gen1Recomp" | Out-File $env:GITHUB_ENV -Append
|
|
exit 0
|
|
}
|
|
$pfx = Join-Path $env:RUNNER_TEMP 'gen1recomp-uwp.pfx'
|
|
[IO.File]::WriteAllBytes($pfx, [Convert]::FromBase64String($env:CERTIFICATE_BASE64))
|
|
$flags = [Security.Cryptography.X509Certificates.X509KeyStorageFlags]::EphemeralKeySet
|
|
$cert = [Security.Cryptography.X509Certificates.X509Certificate2]::new(
|
|
$pfx, $env:CERTIFICATE_PASSWORD, $flags)
|
|
$cer = Join-Path $env:RUNNER_TEMP 'gen1recomp-uwp.cer'
|
|
[IO.File]::WriteAllBytes(
|
|
$cer,
|
|
$cert.Export([Security.Cryptography.X509Certificates.X509ContentType]::Cert))
|
|
Import-Certificate -FilePath $cer -CertStoreLocation Cert:\LocalMachine\TrustedPeople | Out-Null
|
|
"UWP_PFX=$pfx" | Out-File $env:GITHUB_ENV -Append
|
|
"UWP_CERT_THUMBPRINT=$($cert.Thumbprint)" | Out-File $env:GITHUB_ENV -Append
|
|
"UWP_PUBLISHER=$($cert.Subject)" | Out-File $env:GITHUB_ENV -Append
|
|
- name: Build Xbox UWP package
|
|
shell: bash
|
|
run: |
|
|
bash scripts/build_xbox_uwp.sh \
|
|
--release \
|
|
--version "${{ needs.version.outputs.version }}" \
|
|
--publisher "$UWP_PUBLISHER" \
|
|
--game-love .bazinga/work/game.love
|
|
- name: Sign and stage Xbox UWP release
|
|
shell: pwsh
|
|
env:
|
|
CERTIFICATE_PASSWORD: ${{ secrets.XBOX_UWP_SIGNING_PASSWORD }}
|
|
run: |
|
|
if (-not $env:UWP_PFX) {
|
|
exit 0
|
|
}
|
|
scripts/xbox-uwp/stage_release.ps1 `
|
|
-Version '${{ needs.version.outputs.version }}' `
|
|
-Configuration Release `
|
|
-BuildInfo .bazinga/work/xbox-uwp-build-info.json `
|
|
-CertificatePath $env:UWP_PFX `
|
|
-CertificatePassword $env:CERTIFICATE_PASSWORD
|
|
- name: Upload Xbox UWP release
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: gen1recomp-xbox-uwp-release
|
|
path: |
|
|
dist/xbox-uwp/gen1recomp-${{ needs.version.outputs.version }}-xbox-uwp.zip
|
|
dist/xbox-uwp/gen1recomp-${{ needs.version.outputs.version }}-xbox-uwp.zip.sha256
|
|
if-no-files-found: error
|
|
retention-days: 7
|
|
- name: Remove signing certificate
|
|
if: always()
|
|
shell: pwsh
|
|
run: |
|
|
if ($env:UWP_CERT_THUMBPRINT) {
|
|
Remove-Item "Cert:\LocalMachine\TrustedPeople\$env:UWP_CERT_THUMBPRINT" -ErrorAction SilentlyContinue
|
|
}
|
|
if ($env:UWP_PFX) {
|
|
Remove-Item $env:UWP_PFX -Force -ErrorAction SilentlyContinue
|
|
}
|
|
|
|
# Windows Native AOT TLS dialer. The Mac release runner fuses the win64 zip
|
|
# from LÖVE's prebuilt binaries and cannot cross-compile this DLL, so build
|
|
# it here and inject it in the release job before scripts/build.sh win.
|
|
native-tls-win:
|
|
name: build Windows gen1tls.dll
|
|
needs: version
|
|
runs-on: windows-2022
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
- name: Identify TLS build environment
|
|
id: tls-env
|
|
shell: pwsh
|
|
run: |
|
|
"image=$env:ImageOS-$env:ImageVersion" >> $env:GITHUB_OUTPUT
|
|
# Exact keys only: a partial match could ship an outdated native library.
|
|
# Hash the build script too so changes to build flags invalidate the binary.
|
|
- name: Restore finished TLS library
|
|
id: tls-cache
|
|
uses: actions/cache/restore@v4
|
|
with:
|
|
path: dist/native/win-x64/gen1tls.dll
|
|
key: native-tls-v2-${{ runner.os }}-${{ runner.arch }}-${{ steps.tls-env.outputs.image }}-dotnet8-${{ hashFiles('native/tls_dial/**', 'scripts/ci/build_gen1tls.ps1', '**/Directory.Build.*', '**/Directory.Packages.props', '**/NuGet.Config', '**/nuget.config', '**/global.json') }}
|
|
- name: Setup .NET 8
|
|
if: steps.tls-cache.outputs.cache-hit != 'true'
|
|
uses: actions/setup-dotnet@v6
|
|
with:
|
|
dotnet-version: "8.0.x"
|
|
- name: Publish gen1tls (win-x64 Native AOT)
|
|
if: steps.tls-cache.outputs.cache-hit != 'true'
|
|
shell: pwsh
|
|
run: ./scripts/ci/build_gen1tls.ps1
|
|
- name: Verify TLS library
|
|
shell: pwsh
|
|
run: |
|
|
$dll = Get-Item 'dist/native/win-x64/gen1tls.dll' -ErrorAction Stop
|
|
if ($dll.Length -eq 0) { throw 'gen1tls.dll is empty' }
|
|
- name: Cache finished TLS library
|
|
if: steps.tls-cache.outputs.cache-hit != 'true'
|
|
uses: actions/cache/save@v4
|
|
with:
|
|
path: dist/native/win-x64/gen1tls.dll
|
|
key: ${{ steps.tls-cache.outputs.cache-primary-key }}
|
|
- name: Upload gen1tls.dll
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: gen1tls-win-x64
|
|
path: dist/native/win-x64/gen1tls.dll
|
|
if-no-files-found: error
|
|
retention-days: 7
|
|
|
|
shaderfx-bridge:
|
|
name: build ShaderFX bridge (${{ matrix.plat }})
|
|
needs: version
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- plat: win-x64
|
|
runs-on: windows-2022
|
|
lib: librashader_bridge.dll
|
|
- plat: mac
|
|
runs-on: macos-latest
|
|
lib: liblibrashader_bridge.dylib
|
|
- plat: linux-x64
|
|
runs-on: ubuntu-24.04
|
|
lib: liblibrashader_bridge.so
|
|
glibc_triple: x86_64-unknown-linux-gnu.2.17
|
|
- plat: linux-arm64
|
|
runs-on: ubuntu-24.04-arm
|
|
lib: liblibrashader_bridge.so
|
|
glibc_triple: aarch64-unknown-linux-gnu.2.17
|
|
# Built here rather than on the packaging runner so the APK's bridge
|
|
# does not depend on that machine's local Rust/NDK toolchain (#1932).
|
|
- plat: android
|
|
runs-on: ubuntu-24.04
|
|
lib: liblibrashader_bridge.so
|
|
- plat: ios
|
|
runs-on: macos-latest
|
|
lib: liblibrashader_bridge.a
|
|
runs-on: ${{ matrix.runs-on }}
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
- name: Identify bridge build environment
|
|
id: bridge-env
|
|
shell: bash
|
|
env:
|
|
PLAT: ${{ matrix.plat }}
|
|
run: bash scripts/ci/shaderfx_bridge.sh env
|
|
# Keep release versions and game sources out of this key. Only bridge
|
|
# inputs, build instructions, and the native environment affect reuse.
|
|
- name: Restore finished bridge
|
|
id: bridge-cache
|
|
uses: actions/cache/restore@v4
|
|
with:
|
|
path: dist/native/${{ matrix.plat }}
|
|
key: shaderfx-binary-v2-${{ matrix.plat }}-${{ runner.arch }}-${{ matrix.glibc_triple }}-${{ steps.bridge-env.outputs.hash }}-${{ hashFiles('tools/shaderfx-bridge/**', '.cargo/**', 'rust-toolchain*', 'scripts/ci/shaderfx_bridge.sh') }}
|
|
- name: Setup Rust
|
|
if: steps.bridge-cache.outputs.cache-hit != 'true'
|
|
uses: dtolnay/rust-toolchain@stable
|
|
- name: Cache cargo registry and build dir
|
|
if: steps.bridge-cache.outputs.cache-hit != 'true'
|
|
uses: Swatinem/rust-cache@v2
|
|
with:
|
|
workspaces: tools/shaderfx-bridge
|
|
key: ${{ matrix.plat }}-${{ matrix.glibc_triple }}
|
|
- name: Build the bridge
|
|
if: steps.bridge-cache.outputs.cache-hit != 'true'
|
|
shell: bash
|
|
env:
|
|
PLAT: ${{ matrix.plat }}
|
|
LIB: ${{ matrix.lib }}
|
|
GLIBC_TRIPLE: ${{ matrix.glibc_triple }}
|
|
run: bash scripts/ci/shaderfx_bridge.sh build
|
|
- name: Verify bridge libraries
|
|
shell: bash
|
|
env:
|
|
PLAT: ${{ matrix.plat }}
|
|
LIB: ${{ matrix.lib }}
|
|
run: bash scripts/ci/shaderfx_bridge.sh verify
|
|
- name: Cache finished bridge
|
|
if: steps.bridge-cache.outputs.cache-hit != 'true'
|
|
uses: actions/cache/save@v4
|
|
with:
|
|
path: dist/native/${{ matrix.plat }}
|
|
key: ${{ steps.bridge-cache.outputs.cache-primary-key }}
|
|
- name: Upload the bridge
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: shaderfx-bridge-${{ matrix.plat }}
|
|
path: dist/native/${{ matrix.plat }}
|
|
if-no-files-found: error
|
|
retention-days: 7
|
|
|
|
desktop:
|
|
name: build macOS + Linux
|
|
needs: [version, love-payload, shaderfx-bridge]
|
|
runs-on: ${{ fromJSON(github.repository == 'bryanthaboi/gen1recomp' && '["self-hosted", "macOS"]' || '"macos-latest"') }}
|
|
concurrency:
|
|
group: mac-signing-keychain
|
|
cancel-in-progress: false
|
|
steps:
|
|
# The self-hosted runner lives under the machine owner's home
|
|
# directory; mask it first so absolute paths in every later step's
|
|
# output show up as *** in the public workflow logs.
|
|
- name: Mask runner paths
|
|
run: echo "::add-mask::$HOME"
|
|
|
|
- name: Checkout
|
|
uses: actions/checkout@v7
|
|
|
|
- name: Download ShaderFX bridge libraries
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
pattern: shaderfx-bridge-*
|
|
path: dist/native
|
|
|
|
- name: Flatten the ShaderFX bridge artifact layout
|
|
run: |
|
|
set -euo pipefail
|
|
shopt -s nullglob
|
|
for d in dist/native/shaderfx-bridge-*/; do
|
|
d="${d%/}"
|
|
plat="${d#dist/native/shaderfx-bridge-}"
|
|
mkdir -p "dist/native/$plat"
|
|
for f in "$d"/*; do mv "$f" "dist/native/$plat/"; done
|
|
rmdir "$d"
|
|
done
|
|
for plat in mac win-x64 linux-x64 linux-arm64 android; do
|
|
ls "dist/native/$plat" >/dev/null \
|
|
|| { echo "::error::no ShaderFX bridge staged for $plat"; exit 1; }
|
|
done
|
|
ls -lR dist/native
|
|
|
|
# The same game.love the arm64 AppImage and Xbox UWP builds fused, so
|
|
# every release asset ships one identical payload (build.sh's own pack
|
|
# would omit PATCH_NOTES.md and mobile/ios/app-repo.json).
|
|
- name: Download shared payload
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: gen1recomp-release-love
|
|
path: dist/payload
|
|
|
|
- name: Import signing certificate into a temporary keychain
|
|
if: github.repository == 'bryanthaboi/gen1recomp'
|
|
uses: ./.github/actions/mac-signing-keychain
|
|
|
|
- name: Build macOS + Linux
|
|
env:
|
|
SHADERFX_BRIDGE_REQUIRED: "1"
|
|
run: |
|
|
set -euo pipefail
|
|
scripts/build.sh mac --version "${{ needs.version.outputs.version }}" --no-notarize \
|
|
--game-love dist/payload/game.love
|
|
scripts/build.sh linux --version "${{ needs.version.outputs.version }}" \
|
|
--game-love dist/payload/game.love
|
|
|
|
- name: Notarize & staple macOS app
|
|
if: github.repository == 'bryanthaboi/gen1recomp'
|
|
run: |
|
|
set -euo pipefail
|
|
ci_dir="${POKEMON_CI_DIR:-$HOME/.config/pokemon-ci}"
|
|
if [ ! -f "$ci_dir/notary.env" ]; then
|
|
echo "::error::Missing $ci_dir/notary.env. Run scripts/ci-setup-signing.sh on the runner."
|
|
exit 1
|
|
fi
|
|
set -a; . "$ci_dir/notary.env"; set +a
|
|
echo "::add-mask::$APPLE_APP_PASSWORD"
|
|
|
|
app=".bazinga/work/gen1recomp.app"
|
|
zip="dist/mac/gen1recomp-macos.zip"
|
|
[ -d "$app" ] || { echo "::error::signed app not found at $app"; exit 1; }
|
|
if [ -z "${APPLE_ID:-}" ] || [ -z "${APPLE_APP_PASSWORD:-}" ] || [ -z "${APPLE_TEAM_ID:-}" ]; then
|
|
echo "::error::notary.env is missing APPLE_ID / APPLE_APP_PASSWORD / APPLE_TEAM_ID."
|
|
exit 1
|
|
fi
|
|
|
|
echo "Submitting to Apple notary service (can take a few minutes)..."
|
|
xcrun notarytool submit "$zip" \
|
|
--apple-id "$APPLE_ID" \
|
|
--team-id "$APPLE_TEAM_ID" \
|
|
--password "$APPLE_APP_PASSWORD" \
|
|
--wait
|
|
|
|
echo "Stapling ticket to the app..."
|
|
xcrun stapler staple "$app"
|
|
|
|
# Re-zip the now-stapled app (same format build.sh uses).
|
|
rm -f "$zip"
|
|
ditto -c -k --sequesterRsrc --keepParent "$app" "$zip"
|
|
echo "Notarized + stapled ✓"
|
|
|
|
- name: Upload gen1recomp-desktop-release
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: gen1recomp-desktop-release
|
|
path: |
|
|
dist/mac/gen1recomp-macos.zip
|
|
dist/linux/gen1recomp-linux-x86_64.AppImage
|
|
if-no-files-found: error
|
|
retention-days: 7
|
|
|
|
- name: Clean up signing keychain
|
|
if: ${{ always() && github.repository == 'bryanthaboi/gen1recomp' }}
|
|
run: security delete-keychain "$RUNNER_TEMP/pokemon-signing.keychain-db" 2>/dev/null || true
|
|
|
|
android:
|
|
name: build Android
|
|
needs: [version, shaderfx-bridge]
|
|
runs-on: ${{ fromJSON(github.repository == 'bryanthaboi/gen1recomp' && '["self-hosted", "macOS"]' || '"macos-latest"') }}
|
|
steps:
|
|
# The self-hosted runner lives under the machine owner's home
|
|
# directory; mask it first so absolute paths in every later step's
|
|
# output show up as *** in the public workflow logs.
|
|
- name: Mask runner paths
|
|
run: echo "::add-mask::$HOME"
|
|
|
|
- name: Checkout
|
|
uses: actions/checkout@v7
|
|
|
|
- name: Download the Android ShaderFX bridge
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: shaderfx-bridge-android
|
|
path: dist/native/android
|
|
|
|
- name: Materialize Android release signing key
|
|
env:
|
|
KEYSTORE_B64: ${{ secrets.ANDROID_RELEASE_KEYSTORE_B64 }}
|
|
run: |
|
|
set -euo pipefail
|
|
[ -n "$KEYSTORE_B64" ] || {
|
|
echo "::error::ANDROID_RELEASE_KEYSTORE_B64 is required for a publishable Android update"
|
|
exit 1
|
|
}
|
|
python3 - <<'PY'
|
|
import base64, os, pathlib
|
|
encoded = os.environ["KEYSTORE_B64"]
|
|
path = pathlib.Path(os.environ["RUNNER_TEMP"]) / "gen1recomp-android-release.keystore"
|
|
path.write_bytes(base64.b64decode(encoded, validate=True))
|
|
PY
|
|
|
|
- name: Build Android
|
|
env:
|
|
SHADERFX_BRIDGE_REQUIRED: "1"
|
|
SHADERFX_BRIDGE_ANDROID_DIR: ${{ github.workspace }}/dist/native/android
|
|
GEN1RECOMP_ANDROID_KEYSTORE: ${{ runner.temp }}/gen1recomp-android-release.keystore
|
|
GEN1RECOMP_ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_RELEASE_KEYSTORE_PASSWORD }}
|
|
GEN1RECOMP_ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_RELEASE_KEY_ALIAS }}
|
|
GEN1RECOMP_ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_RELEASE_KEY_PASSWORD }}
|
|
run: |
|
|
set -euo pipefail
|
|
export GEN1_ANDROID_SHADOW_DIR="$HOME/.cache/gen1recomp-ci/android-shadow"
|
|
scripts/build_android.sh --release --version "${{ needs.version.outputs.version }}"
|
|
|
|
- name: Upload gen1recomp-android-release
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: gen1recomp-android-release
|
|
path: |
|
|
dist/android/release/**/*.apk
|
|
if-no-files-found: error
|
|
retention-days: 7
|
|
|
|
ios:
|
|
name: build iOS
|
|
needs: [version, shaderfx-bridge]
|
|
runs-on: ${{ fromJSON(github.repository == 'bryanthaboi/gen1recomp' && '["self-hosted", "macOS"]' || '"macos-latest"') }}
|
|
concurrency:
|
|
group: mac-signing-keychain
|
|
cancel-in-progress: false
|
|
steps:
|
|
# The self-hosted runner lives under the machine owner's home
|
|
# directory; mask it first so absolute paths in every later step's
|
|
# output show up as *** in the public workflow logs.
|
|
- name: Mask runner paths
|
|
run: echo "::add-mask::$HOME"
|
|
|
|
- name: Checkout
|
|
uses: actions/checkout@v7
|
|
|
|
- name: Import signing certificate into a temporary keychain
|
|
if: github.repository == 'bryanthaboi/gen1recomp'
|
|
uses: ./.github/actions/mac-signing-keychain
|
|
|
|
- name: Download the iOS ShaderFX bridge
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: shaderfx-bridge-ios
|
|
path: dist/native/ios
|
|
|
|
- name: Install xcbeautify
|
|
run: |
|
|
set -euo pipefail
|
|
brew list xcbeautify >/dev/null 2>&1 || brew install xcbeautify
|
|
|
|
- name: Build iOS
|
|
env:
|
|
CANONICAL_REPOSITORY: ${{ github.repository == 'bryanthaboi/gen1recomp' }}
|
|
SHADERFX_BRIDGE_IOS: ${{ github.workspace }}/dist/native/ios/liblibrashader_bridge.a
|
|
run: |
|
|
set -euo pipefail
|
|
if [ "$CANONICAL_REPOSITORY" = true ]; then
|
|
scripts/build_ios.sh --fetch --device --release \
|
|
--version "${{ needs.version.outputs.version }}"
|
|
else
|
|
scripts/build_ios.sh --fetch --release \
|
|
--version "${{ needs.version.outputs.version }}"
|
|
fi
|
|
|
|
- name: Upload gen1recomp-ios-release
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: gen1recomp-ios-release
|
|
path: |
|
|
dist/ios/gen1recomp++.ipa
|
|
if-no-files-found: error
|
|
retention-days: 7
|
|
|
|
- name: Clean up signing keychain
|
|
if: ${{ always() && github.repository == 'bryanthaboi/gen1recomp' }}
|
|
run: security delete-keychain "$RUNNER_TEMP/pokemon-signing.keychain-db" 2>/dev/null || true
|
|
|
|
switch:
|
|
name: build Switch
|
|
needs: version
|
|
runs-on: ${{ fromJSON(github.repository == 'bryanthaboi/gen1recomp' && '["self-hosted", "macOS"]' || '"macos-latest"') }}
|
|
steps:
|
|
# The self-hosted runner lives under the machine owner's home
|
|
# directory; mask it first so absolute paths in every later step's
|
|
# output show up as *** in the public workflow logs.
|
|
- name: Mask runner paths
|
|
run: echo "::add-mask::$HOME"
|
|
|
|
- name: Checkout
|
|
uses: actions/checkout@v7
|
|
|
|
- name: Restore pinned love-nx binaries
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: .bazinga/love-nx/11.5-nx1
|
|
key: love-nx-11.5-nx1-${{ hashFiles('scripts/switch/love-nx-11.5-nx1.sha256') }}
|
|
|
|
- name: Build Switch
|
|
run: |
|
|
set -euo pipefail
|
|
# Hard-fail gate: Switch ships with every release (never soft-fail).
|
|
# PR CI is path-gated (ubuntu selftest + canonical fused); release
|
|
# always builds Switch regardless of which files changed.
|
|
# Needs native switch-tools (nacptool/elf2nro) and/or Docker on the
|
|
# Mac self-hosted runner; see docs/switch-build.md.
|
|
scripts/build_switch.sh --fetch --fused \
|
|
--version "${{ needs.version.outputs.version }}"
|
|
|
|
- name: Upload gen1recomp-switch-release
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: gen1recomp-switch-release
|
|
path: |
|
|
dist/switch/gen1recomp-${{ needs.version.outputs.version }}-switch.zip
|
|
if-no-files-found: error
|
|
retention-days: 7
|
|
|
|
rg34xxsp:
|
|
name: build Anbernic RG34XXSP port
|
|
needs: [version, shaderfx-bridge]
|
|
runs-on: ${{ fromJSON(github.repository == 'bryanthaboi/gen1recomp' && '["self-hosted", "macOS"]' || '"macos-latest"') }}
|
|
steps:
|
|
# The self-hosted runner lives under the machine owner's home
|
|
# directory; mask it first so absolute paths in every later step's
|
|
# output show up as *** in the public workflow logs.
|
|
- name: Mask runner paths
|
|
run: echo "::add-mask::$HOME"
|
|
|
|
- name: Checkout
|
|
uses: actions/checkout@v7
|
|
|
|
- name: Download the Linux arm64 ShaderFX bridge
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: shaderfx-bridge-linux-arm64
|
|
path: dist/native/linux-arm64
|
|
|
|
- name: Build Anbernic RG34XXSP port
|
|
env:
|
|
SHADERFX_BRIDGE_REQUIRED: "1"
|
|
run: |
|
|
set -euo pipefail
|
|
# Self-contained aarch64 PortMaster-style pack; pulls the LÖVE 11.5
|
|
# runtime from PortMaster-GUI, so it needs no signing/notarization.
|
|
./build-rg34xxsp.sh --version "${{ needs.version.outputs.version }}"
|
|
|
|
- name: Upload gen1recomp-rg34xxsp-release
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: gen1recomp-rg34xxsp-release
|
|
path: |
|
|
dist/rg34xxsp/gen1recomp-rg34xxsp-stockos64-mod.zip
|
|
if-no-files-found: error
|
|
retention-days: 7
|
|
|
|
linux-arm-sbc:
|
|
name: build Linux ARM SBC PortMaster port
|
|
needs: [version, shaderfx-bridge]
|
|
runs-on: ${{ fromJSON(github.repository == 'bryanthaboi/gen1recomp' && '["self-hosted", "macOS"]' || '"macos-latest"') }}
|
|
steps:
|
|
# The self-hosted runner lives under the machine owner's home
|
|
# directory; mask it first so absolute paths in every later step's
|
|
# output show up as *** in the public workflow logs.
|
|
- name: Mask runner paths
|
|
run: echo "::add-mask::$HOME"
|
|
|
|
- name: Checkout
|
|
uses: actions/checkout@v7
|
|
|
|
- name: Download the Linux arm64 ShaderFX bridge
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: shaderfx-bridge-linux-arm64
|
|
path: dist/native/linux-arm64
|
|
|
|
- name: Build Linux ARM SBC PortMaster port
|
|
env:
|
|
# The release workflow must package the commit being released. The
|
|
# script defaults to the latest published release for standalone
|
|
# builds, while this explicit local override keeps CI source-aligned.
|
|
GEN1RECOMP_SOURCE_DIR: ${{ github.workspace }}
|
|
GEN1RECOMP_RELEASE_TAG: v${{ needs.version.outputs.version }}
|
|
SHADERFX_BRIDGE_REQUIRED: "1"
|
|
run: |
|
|
set -euo pipefail
|
|
# Same aarch64 PortMaster-style pack for Linux ARM SBC PortMaster. The build
|
|
# keeps its own cache because the two scripts use different staging
|
|
# layouts and runtime package paths.
|
|
./build-linux-arm-sbc.sh --version "${{ needs.version.outputs.version }}"
|
|
|
|
- name: Upload gen1recomp-linux-arm-sbc-release
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: gen1recomp-linux-arm-sbc-release
|
|
path: |
|
|
dist/linux-arm-sbc/gen1recomp-sbc-portmaster.zip
|
|
if-no-files-found: error
|
|
retention-days: 7
|
|
|
|
notes:
|
|
name: write release notes
|
|
needs: version
|
|
if: github.repository == 'bryanthaboi/gen1recomp'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v7
|
|
with:
|
|
fetch-depth: 0
|
|
fetch-tags: true
|
|
|
|
- name: Write release notes
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
set -euo pipefail
|
|
tag="${{ needs.version.outputs.tag }}"
|
|
|
|
# Issues this release closes. Three sources, deduped by number:
|
|
# 1. GitHub's own "closing issues" links on every PR whose
|
|
# commits are in the range (works for squash, rebase, and
|
|
# merge commits alike) -- including PRs that were merged
|
|
# into a branch this release PR is itself merging in.
|
|
# 2. CLOSES/fixes/resolves text in those PRs' titles + bodies
|
|
# that GitHub didn't turn into a closing link (a PR into a
|
|
# non-default branch never gets one).
|
|
# 3. The same text in raw commit messages, so a direct push
|
|
# with "CLOSES #N #M" still lands in the notes.
|
|
# Scans every commit since the previous tag so a skipped release
|
|
# run doesn't drop issues on the floor.
|
|
prev_tag="$(git tag -l 'v*' --sort=-v:refname | grep -v "^${tag}$" | head -1 || true)"
|
|
range="${prev_tag:+${prev_tag}..}$GITHUB_SHA"
|
|
|
|
# every #N on a line that carries a closing keyword (handles the
|
|
# multi-issue "CLOSES #1 #2 #3" form the tracker uses)
|
|
scan_closes() {
|
|
grep -iE '\b(close[sd]?|fix(es|ed)?|resolve[sd]?)\b' \
|
|
| grep -oE '#[0-9]+' | tr -d '#' || true
|
|
}
|
|
|
|
nums=""
|
|
nums+=" $(git log --pretty=%B "$range" | scan_closes | tr '\n' ' ')"
|
|
|
|
prs="$(git log --pretty=%H "$range" \
|
|
| xargs -I{} gh api "repos/$GITHUB_REPOSITORY/commits/{}/pulls" \
|
|
--jq '.[].number' 2>/dev/null \
|
|
| sort -un || true)"
|
|
for pr in $prs; do
|
|
nums+=" $(gh api graphql \
|
|
-f owner="${GITHUB_REPOSITORY%/*}" \
|
|
-f name="${GITHUB_REPOSITORY#*/}" \
|
|
-F pr="$pr" \
|
|
-f query='
|
|
query($owner:String!, $name:String!, $pr:Int!) {
|
|
repository(owner:$owner, name:$name) {
|
|
pullRequest(number:$pr) {
|
|
closingIssuesReferences(first:50) { nodes { number } }
|
|
}
|
|
}
|
|
}' \
|
|
--jq '.data.repository.pullRequest.closingIssuesReferences.nodes[].number' \
|
|
2>/dev/null | grep -E '^[0-9]+$' | tr '\n' ' ' || true)"
|
|
nums+=" $(gh api "repos/$GITHUB_REPOSITORY/pulls/$pr" \
|
|
--jq '.title + " " + (.body // "")' 2>/dev/null \
|
|
| scan_closes | tr '\n' ' ' || true)"
|
|
done
|
|
|
|
# dedupe, drop anything that is a PR or does not exist, keep
|
|
# titles (gh api prints the response body to stdout on an HTTP
|
|
# error, so only a zero exit counts)
|
|
closed=""
|
|
for n in $(printf '%s' "$nums" | tr ' ' '\n' | grep -E '^[0-9]+$' | sort -un); do
|
|
if title="$(gh api "repos/$GITHUB_REPOSITORY/issues/$n" \
|
|
--jq 'if .pull_request then empty else .title end' \
|
|
2>/dev/null)"; then
|
|
[ -n "$title" ] && closed+="- #$n $title"$'\n'
|
|
fi
|
|
done
|
|
closed="$(printf '%s' "$closed" | grep . | sort -t'#' -k2 -n || true)"
|
|
|
|
# Everyone whose commits are in the range: GitHub login when the
|
|
# commit is linked to an account, the raw git author name when not;
|
|
# CI bots filtered out.
|
|
if [ -n "$prev_tag" ]; then
|
|
commits_endpoint="repos/$GITHUB_REPOSITORY/compare/${prev_tag}...${GITHUB_SHA}?per_page=100"
|
|
else
|
|
# No previous release: include all reachable commits, even the root.
|
|
commits_endpoint="repos/$GITHUB_REPOSITORY/commits?sha=${GITHUB_SHA}&per_page=100"
|
|
fi
|
|
contributors="$(gh api --paginate \
|
|
"$commits_endpoint" \
|
|
--jq '(if type == "array" then . else .commits end)[]
|
|
| if .author and .author.login
|
|
then "@" + .author.login
|
|
else .commit.author.name end' 2>/dev/null \
|
|
| grep -viE '\[bot\]$' | sort -uf | sed 's/^/- /' || true)"
|
|
|
|
# Use the dev -> main promotion PR for this release commit, not an
|
|
# individual feature PR whose commits also happen to be in the range.
|
|
promotion_body="$(gh api --paginate --slurp \
|
|
"repos/$GITHUB_REPOSITORY/commits/$GITHUB_SHA/pulls?per_page=100" 2>/dev/null \
|
|
| jq -r --arg repo "$GITHUB_REPOSITORY" --arg sha "$GITHUB_SHA" '
|
|
[.[][] | select(.merged_at != null
|
|
and .base.repo.full_name == $repo and .base.ref == "main"
|
|
and .head.repo.full_name == $repo and .head.ref == "dev"
|
|
and .merge_commit_sha == $sha)]
|
|
| sort_by(.merged_at) | last | .body // ""' || true)"
|
|
notes="Download the right version for your device below."
|
|
if [ -n "$(printf '%s' "$promotion_body" | tr -d '[:space:]')" ]; then
|
|
notes="$promotion_body"
|
|
fi
|
|
if [ -n "$closed" ]; then
|
|
notes+=$'\n\n## Issues closed\n\n'"$closed"
|
|
fi
|
|
if [ -n "$contributors" ]; then
|
|
notes+=$'\n\n## Contributors\n\n'"$contributors"
|
|
fi
|
|
printf 'Release notes:\n%s\n' "$notes"
|
|
mkdir -p dist/release-notes
|
|
printf '%s\n' "$notes" > dist/release-notes/release-notes.md
|
|
|
|
- name: Upload release notes
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: gen1recomp-release-notes
|
|
path: dist/release-notes/release-notes.md
|
|
if-no-files-found: error
|
|
retention-days: 7
|
|
|
|
windows-sign:
|
|
name: build and sign Windows (separate run)
|
|
needs: [version, love-payload, native-tls-win, shaderfx-bridge]
|
|
if: github.repository == 'bryanthaboi/gen1recomp'
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 100
|
|
steps:
|
|
- name: Start the Windows signing workflow
|
|
id: dispatch
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
set -euo pipefail
|
|
nonce="${{ github.run_id }}-${{ github.run_attempt }}"
|
|
title="windows ${{ needs.version.outputs.version }} ($nonce)"
|
|
gh workflow run windows-sign.yml -R "$GITHUB_REPOSITORY" --ref "$GITHUB_REF_NAME" \
|
|
-f version="${{ needs.version.outputs.version }}" \
|
|
-f sha="$GITHUB_SHA" \
|
|
-f source_run="${{ github.run_id }}" \
|
|
-f nonce="$nonce"
|
|
id=""
|
|
for _ in $(seq 1 60); do
|
|
id="$(gh run list -R "$GITHUB_REPOSITORY" --workflow windows-sign.yml --event workflow_dispatch -L 30 \
|
|
--json databaseId,displayTitle --jq ".[] | select(.displayTitle == \"$title\") | .databaseId" | head -1)"
|
|
[ -n "$id" ] && break
|
|
sleep 5
|
|
done
|
|
[ -n "$id" ] || { echo "::error::the Windows signing run never showed up"; exit 1; }
|
|
echo "Windows signing run: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$id"
|
|
echo "run-id=$id" >> "$GITHUB_OUTPUT"
|
|
gh run watch "$id" -R "$GITHUB_REPOSITORY" --exit-status --interval 15
|
|
|
|
- name: Download the signed Windows build
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: gen1recomp-windows-signed
|
|
path: dist/win
|
|
run-id: ${{ steps.dispatch.outputs.run-id }}
|
|
github-token: ${{ github.token }}
|
|
|
|
- name: Check the signed Windows zip
|
|
run: |
|
|
set -euo pipefail
|
|
unzip -l dist/win/gen1recomp-win64.zip | grep -F gen1recomp.exe
|
|
unzip -l dist/win/gen1recomp-win64.zip | grep -F gen1tls.dll
|
|
unzip -l dist/win/gen1recomp-win64.zip | grep -F librashader_bridge.dll
|
|
|
|
- name: Upload gen1recomp-windows-signed
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: gen1recomp-windows-signed
|
|
path: dist/win/gen1recomp-win64.zip
|
|
if-no-files-found: error
|
|
retention-days: 7
|
|
|
|
release:
|
|
needs: [version, notes, love-payload, xbox-uwp, linux-arm64, linux-flatpak, desktop, windows-sign, android, ios, switch, rg34xxsp, linux-arm-sbc]
|
|
if: github.repository == 'bryanthaboi/gen1recomp'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v7
|
|
with:
|
|
fetch-depth: 0
|
|
fetch-tags: true
|
|
|
|
- name: Download gen1recomp-release-notes
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: gen1recomp-release-notes
|
|
path: dist/release-notes
|
|
|
|
- name: Download gen1recomp-release-love
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: gen1recomp-release-love
|
|
path: dist/payload
|
|
|
|
- name: Download gen1recomp-desktop-release
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: gen1recomp-desktop-release
|
|
path: dist
|
|
|
|
- name: Download gen1recomp-windows-signed
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: gen1recomp-windows-signed
|
|
path: dist/win
|
|
|
|
- name: Download gen1recomp-android-release
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: gen1recomp-android-release
|
|
path: dist/android/release
|
|
|
|
- name: Download gen1recomp-ios-release
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: gen1recomp-ios-release
|
|
path: dist/ios
|
|
|
|
- name: Download gen1recomp-switch-release
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: gen1recomp-switch-release
|
|
path: dist/switch
|
|
|
|
- name: Download gen1recomp-rg34xxsp-release
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: gen1recomp-rg34xxsp-release
|
|
path: dist/rg34xxsp
|
|
|
|
- name: Download gen1recomp-linux-arm-sbc-release
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: gen1recomp-linux-arm-sbc-release
|
|
path: dist/linux-arm-sbc
|
|
|
|
- name: Download gen1recomp-xbox-uwp-release
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: gen1recomp-xbox-uwp-release
|
|
path: dist/xbox-uwp
|
|
|
|
- name: Download gen1recomp-linux-arm64-release
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: gen1recomp-linux-arm64-release
|
|
path: dist/linux-arm64
|
|
|
|
- name: Download gen1recomp-linux-flatpak-release
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: gen1recomp-linux-flatpak-release
|
|
path: dist/flatpak
|
|
|
|
- name: Stage release assets
|
|
if: github.repository == 'bryanthaboi/gen1recomp'
|
|
id: assets
|
|
run: |
|
|
set -euo pipefail
|
|
v="${{ needs.version.outputs.version }}"
|
|
outdir="dist/release"
|
|
rm -rf "$outdir"
|
|
mkdir -p "$outdir"
|
|
cp "dist/mac/gen1recomp-macos.zip" "$outdir/gen1recomp-${v}-macos.zip"
|
|
cp "dist/win/gen1recomp-win64.zip" "$outdir/gen1recomp-${v}-windows.zip"
|
|
# x86_64 desktop Linux: raw AppImage (no zip wrapper).
|
|
x64_appimage="dist/linux/gen1recomp-linux-x86_64.AppImage"
|
|
[ -f "$x64_appimage" ] || { echo "::error::$x64_appimage not found (expected from scripts/build.sh linux)"; exit 1; }
|
|
cp "$x64_appimage" "$outdir/gen1recomp-${v}-linux-x86_64.AppImage"
|
|
chmod +x "$outdir/gen1recomp-${v}-linux-x86_64.AppImage"
|
|
|
|
# arm64 desktop Linux (Raspberry Pi, Armbian, arm64 VMs). Built on
|
|
# its own runner because LÖVE publishes no aarch64 binary and the
|
|
# AppImage has to be compiled natively; ships as a runnable
|
|
# AppImage so `chmod +x && ./it` just works.
|
|
arm64_appimage="dist/linux-arm64/gen1recomp-${v}-linux-arm64.AppImage"
|
|
[ -f "$arm64_appimage" ] || { echo "::error::$arm64_appimage not found (expected from the linux-arm64 job)"; exit 1; }
|
|
cp "$arm64_appimage" "$outdir/gen1recomp-${v}-linux-arm64.AppImage"
|
|
chmod +x "$outdir/gen1recomp-${v}-linux-arm64.AppImage"
|
|
|
|
flatpak_bundle="dist/flatpak/gen1recomp-${v}-linux.flatpak"
|
|
[ -f "$flatpak_bundle" ] || { echo "::error::$flatpak_bundle not found (expected from the linux-flatpak job)"; exit 1; }
|
|
cp "$flatpak_bundle" "$outdir/gen1recomp-${v}-linux.flatpak"
|
|
apk="$(find dist/android/release -name '*.apk' | head -1)"
|
|
[ -n "$apk" ] || { echo "::error::no Android APK found under dist/android/release"; exit 1; }
|
|
cp "$apk" "$outdir/gen1recomp-${v}-android.apk"
|
|
|
|
ipa="dist/ios/gen1recomp++.ipa"
|
|
[ -f "$ipa" ] || { echo "::error::$ipa not found (expected from scripts/build_ios.sh --device)"; exit 1; }
|
|
cp "$ipa" "$outdir/gen1recomp++-${v}-ios.ipa"
|
|
|
|
swzip="dist/switch/gen1recomp-${v}-switch.zip"
|
|
[ -f "$swzip" ] || { echo "::error::$swzip not found (expected from scripts/build_switch.sh --fused → pack_sd_zip.sh)"; exit 1; }
|
|
cp "$swzip" "$outdir/gen1recomp-${v}-switch.zip"
|
|
# Local fused .nro stays under dist/switch/ for PR CI / debug; release
|
|
# publishes the SD-ready zip only.
|
|
|
|
uwp="dist/xbox-uwp/gen1recomp-${v}-xbox-uwp.zip"
|
|
[ -f "$uwp" ] || { echo "::error::$uwp not found (expected from the Xbox UWP job)"; exit 1; }
|
|
cp "$uwp" "$outdir/gen1recomp-${v}-xbox-uwp.zip"
|
|
|
|
# Anbernic handheld port (suffix names the CFW it targets, so a
|
|
# future RG35XX/other-CFW pack can ship alongside it).
|
|
rg34="dist/rg34xxsp/gen1recomp-rg34xxsp-stockos64-mod.zip"
|
|
[ -f "$rg34" ] || { echo "::error::$rg34 not found (expected from ./build-rg34xxsp.sh)"; exit 1; }
|
|
cp "$rg34" "$outdir/gen1recomp-${v}-rg34xxsp-stockos64-mod.zip"
|
|
|
|
# Linux ARM SBC PortMaster handheld port.
|
|
sbc="dist/linux-arm-sbc/gen1recomp-sbc-portmaster.zip"
|
|
[ -f "$sbc" ] || { echo "::error::$sbc not found (expected from ./build-linux-arm-sbc.sh)"; exit 1; }
|
|
cp "$sbc" "$outdir/gen1recomp-${v}-sbc-portmaster.zip"
|
|
|
|
# Platform-independent update payload, built alongside the desktop
|
|
# apps above (same game.love that gets fused into each of them).
|
|
love_file="dist/payload/game.love"
|
|
[ -f "$love_file" ] || { echo "::error::$love_file not found (expected from the love-payload job)"; exit 1; }
|
|
cp "$love_file" "$outdir/gen1recomp-${v}.love"
|
|
|
|
ls -lh "$outdir"
|
|
|
|
# Checksums for every staged release asset (sums file itself is
|
|
# written after this and named outside the gen1recomp-* glob, so it
|
|
# never lists itself).
|
|
(cd "$outdir" && shasum -a 256 gen1recomp-* > sha256sums.txt)
|
|
cat "$outdir/sha256sums.txt"
|
|
|
|
- name: Publish GitHub Release
|
|
if: github.repository == 'bryanthaboi/gen1recomp'
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
set -euo pipefail
|
|
v="${{ needs.version.outputs.version }}"
|
|
tag="${{ needs.version.outputs.tag }}"
|
|
|
|
notes_file="dist/release-notes/release-notes.md"
|
|
[ -f "$notes_file" ] || { echo "::error::$notes_file not found (expected from the notes job)"; exit 1; }
|
|
|
|
release_files=(
|
|
"dist/release/gen1recomp-${v}-macos.zip"
|
|
"dist/release/gen1recomp-${v}-windows.zip"
|
|
"dist/release/gen1recomp-${v}-linux-x86_64.AppImage"
|
|
"dist/release/gen1recomp-${v}-linux-arm64.AppImage"
|
|
"dist/release/gen1recomp-${v}-linux.flatpak"
|
|
"dist/release/gen1recomp-${v}-android.apk"
|
|
"dist/release/gen1recomp++-${v}-ios.ipa"
|
|
"dist/release/gen1recomp-${v}-switch.zip"
|
|
"dist/release/gen1recomp-${v}-xbox-uwp.zip"
|
|
"dist/release/gen1recomp-${v}-rg34xxsp-stockos64-mod.zip"
|
|
"dist/release/gen1recomp-${v}-sbc-portmaster.zip"
|
|
"dist/release/gen1recomp-${v}.love"
|
|
"dist/release/sha256sums.txt"
|
|
)
|
|
|
|
if [ "$(gh release view "$tag" --json isDraft --jq .isDraft 2>/dev/null || true)" = "true" ]; then
|
|
gh release delete "$tag" --yes
|
|
fi
|
|
gh release create "$tag" \
|
|
--draft \
|
|
--target "$GITHUB_SHA" \
|
|
--title "$v" \
|
|
--notes-file "$notes_file"
|
|
printf '%s\n' "${release_files[@]}" | xargs -P 6 -I{} gh release upload "$tag" {} --clobber
|
|
gh release edit "$tag" --draft=false
|
|
|
|
echo "Published release $tag"
|
|
|
|
- name: Update iOS app repository
|
|
if: github.repository == 'bryanthaboi/gen1recomp'
|
|
run: |
|
|
set -euo pipefail
|
|
v="${{ needs.version.outputs.version }}"
|
|
ipa="dist/release/gen1recomp++-${v}-ios.ipa"
|
|
app_repo="mobile/ios/app-repo.json"
|
|
[ -f "$ipa" ] || { echo "::error::$ipa not found"; exit 1; }
|
|
[ -f "$app_repo" ] || { echo "::error::$app_repo not found"; exit 1; }
|
|
|
|
date="$(date -u +"%Y-%m-%d")"
|
|
size="$(wc -c < "$ipa" | tr -d '[:space:]')"
|
|
download_url="https://github.com/${GITHUB_REPOSITORY}/releases/download/v${v}/gen1recomp++-${v}-ios.ipa"
|
|
bundle_id="com.theboisclub.gen1recompplusplus"
|
|
localized_description="Gen1Recomp - A native Lua / LÖVE2D recreation of Gen 1 Poke"
|
|
release_notes="$(GH_TOKEN="${{ github.token }}" gh release view "v${v}" --json body --jq '.body // ""' 2>/dev/null || true)"
|
|
if [ -n "$release_notes" ]; then
|
|
localized_description="$release_notes"
|
|
fi
|
|
entry="$(jq -n \
|
|
--arg version "$v" \
|
|
--arg date "$date" \
|
|
--arg download_url "$download_url" \
|
|
--arg localized_description "$localized_description" \
|
|
--argjson size "$size" \
|
|
'{version: $version, date: $date, size: $size, downloadURL: $download_url, localizedDescription: $localized_description}')"
|
|
|
|
if jq -e --arg bundle_id "$bundle_id" --arg version "$v" \
|
|
'any(.apps[] | select(.bundleIdentifier == $bundle_id).versions[]?; .version == $version)' \
|
|
"$app_repo" >/dev/null; then
|
|
jq --arg bundle_id "$bundle_id" --arg version "$v" --argjson entry "$entry" \
|
|
'(.apps[] | select(.bundleIdentifier == $bundle_id).versions) |= map(if .version == $version then $entry else . end)' \
|
|
"$app_repo" > "$app_repo.tmp"
|
|
else
|
|
jq --arg bundle_id "$bundle_id" --argjson entry "$entry" \
|
|
'(.apps[] | select(.bundleIdentifier == $bundle_id).versions) |= [$entry] + .' \
|
|
"$app_repo" > "$app_repo.tmp"
|
|
fi
|
|
mv "$app_repo.tmp" "$app_repo"
|
|
|
|
# main is PR-only for everyone except deploy keys (the "main protection"
|
|
# ruleset's bypass actor), so this push must authenticate with the
|
|
# RELEASE_DEPLOY_KEY deploy key over SSH; the workflow's GITHUB_TOKEN
|
|
# would be rejected by the branch protection.
|
|
- name: Commit iOS app repository
|
|
if: github.repository == 'bryanthaboi/gen1recomp'
|
|
env:
|
|
DEPLOY_KEY: ${{ secrets.RELEASE_DEPLOY_KEY }}
|
|
run: |
|
|
set -euo pipefail
|
|
git add mobile/ios/app-repo.json
|
|
if git diff --cached --quiet; then
|
|
echo "app-repo.json unchanged; nothing to push"
|
|
exit 0
|
|
fi
|
|
key="$RUNNER_TEMP/release-deploy-key"
|
|
printf '%s\n' "$DEPLOY_KEY" > "$key"
|
|
chmod 600 "$key"
|
|
git config user.name "github-actions[bot]"
|
|
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
git commit -m "chore(ios): update app-repo.json [skip ci]"
|
|
git -c core.sshCommand="ssh -i $key -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new" \
|
|
push "git@github.com:${GITHUB_REPOSITORY}.git" "HEAD:${GITHUB_REF_NAME}"
|
|
rm -f "$key"
|