Skip to content

Build All (bundle)

Build All (bundle) #25

Workflow file for this run

name: Build All (bundle)
on:
workflow_dispatch:
permissions:
contents: read
env:
APP_NAME: MiruShin
PUBSPEC_APP_NAME: mirushin
LINUX_APP_ID: com.emp0ry.mirushin
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
jobs:
build-android:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Setup Java (17)
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 17
- name: Setup Flutter (stable)
uses: subosito/flutter-action@v2
with:
channel: stable
cache: true
- name: Flutter pub get
run: flutter pub get
- name: Build APK (release)
run: flutter build apk --release
- name: Read version from pubspec.yaml
id: ver
shell: bash
run: |
VERSION=$(grep '^version:' pubspec.yaml | awk '{print $2}' | cut -d'+' -f1)
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- name: Rename APK
id: apk
shell: bash
run: |
set -euo pipefail
SRC_APK=$(find build/app/outputs/flutter-apk -maxdepth 1 -type f -name '*.apk' | head -n 1)
if [ -z "$SRC_APK" ]; then
echo "ERROR: no APK found in build/app/outputs/flutter-apk"
exit 1
fi
VERSION="${{ steps.ver.outputs.version }}"
DEST_APK="${APP_NAME}-android-v${VERSION}.apk"
cp "$SRC_APK" "$DEST_APK"
echo "apk_path=$DEST_APK" >> "$GITHUB_OUTPUT"
- name: Upload Android artifact
uses: actions/upload-artifact@v6
with:
name: MiruShin-android
path: ${{ steps.apk.outputs.apk_path }}
build-linux:
runs-on: ubuntu-latest
# Pin the MDK SDK (the native libmdk.so fvp bundles) to a stable, tagged
# release. By default fvp downloads the SourceForge *nightly* at build
# time (fvp cmake/deps.cmake), so the bundled libmdk silently changes day
# to day even though the fvp package version is fixed. A June 1 nightly
# regressed and crashes on playback (SIGSEGV at 0x0 in a libmdk
# FrameReader/MediaIO::createForProtocol worker thread). Scoped to this job
# so Android/Windows/macOS keep their own mdk handling.
env:
FVP_DEPS_URL: https://github.com/wang-bin/mdk-sdk/releases/latest/download
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Setup Flutter (stable)
uses: subosito/flutter-action@v2
with:
channel: stable
cache: true
- name: Install Linux build deps
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
clang cmake ninja-build pkg-config libgtk-3-dev liblzma-dev \
libfuse2 libglib2.0-0 libsecret-1-dev libpulse0 python3-pil \
libmpv-dev libva2 libva-drm2 libva-x11-2 libvdpau1 libass9
# Extra runtime libs kept for build-host parity (libmdk resolves some
# media/codec/audio libs at runtime; linuxdeploy only bundles libs
# present on the host). The actual crash fix is FVP_DEPS_URL above.
- name: Flutter pub get
run: flutter pub get
- name: Force fresh pinned MDK SDK
run: |
# fvp's cmake/deps.cmake skips re-downloading libmdk if an mdk-sdk is
# already extracted in its package dir. Because ~/.pub-cache can be
# cached between runs, a previously-downloaded (broken nightly) libmdk
# could otherwise persist and silently ignore FVP_DEPS_URL. Delete any
# cached/extracted mdk-sdk so the pinned stable SDK is always fetched.
find "$HOME/.pub-cache" -type d -path "*fvp*/linux/mdk-sdk" -prune -exec rm -rf {} + 2>/dev/null || true
find "$HOME/.pub-cache" -type f -path "*fvp*/linux/mdk-sdk-*.tar.xz*" -delete 2>/dev/null || true
echo "Cleared cached mdk-sdk; build will fetch FVP_DEPS_URL=$FVP_DEPS_URL"
- name: Strip Linux MediaKit plugin
run: bash .github/scripts/strip-linux-media-kit.sh
- name: Build Linux (release)
run: flutter build linux --release --no-pub
- name: Read version from pubspec.yaml
id: ver
shell: bash
run: |
VERSION=$(grep '^version:' pubspec.yaml | awk '{print $2}' | cut -d'+' -f1)
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- name: Create tar.gz bundle
id: tgz
shell: bash
run: |
set -euo pipefail
VERSION="${{ steps.ver.outputs.version }}"
OUT_DIR="build/linux/x64/release/bundle"
if [ ! -d "$OUT_DIR" ]; then
echo "ERROR: bundle not found at $OUT_DIR"
ls -la build/linux/x64/release || true
exit 1
fi
TGZ="${APP_NAME}-linux-v${VERSION}.tar.gz"
tar -C "$OUT_DIR" -czf "$TGZ" .
echo "tgz_path=$TGZ" >> "$GITHUB_OUTPUT"
- name: Build AppImage
id: appimage
shell: bash
run: |
set -euo pipefail
VERSION="${{ steps.ver.outputs.version }}"
OUT_DIR="build/linux/x64/release/bundle"
curl -L -o linuxdeploy-x86_64.AppImage \
https://github.com/linuxdeploy/linuxdeploy/releases/latest/download/linuxdeploy-x86_64.AppImage
curl -L -o linuxdeploy-plugin-gtk.sh \
https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh
chmod +x linuxdeploy-x86_64.AppImage
chmod +x linuxdeploy-plugin-gtk.sh
rm -rf AppDir
mkdir -p AppDir/usr/bin
cp -r "$OUT_DIR/"* AppDir/usr/bin/
APP_BIN="AppDir/usr/bin/${PUBSPEC_APP_NAME}"
if [ ! -x "$APP_BIN" ]; then
APP_BIN="$(find AppDir/usr/bin -maxdepth 1 -type f -executable | head -n 1)"
fi
if [ -z "$APP_BIN" ]; then
echo "ERROR: cannot find main executable in AppDir/usr/bin"
ls -la AppDir/usr/bin || true
exit 1
fi
mkdir -p AppDir/usr/share/applications
cat > "AppDir/usr/share/applications/${LINUX_APP_ID}.desktop" <<EOAPP
[Desktop Entry]
Type=Application
Name=${APP_NAME}
Exec=$(basename "$APP_BIN")
Icon=${LINUX_APP_ID}
Categories=AudioVideo;
StartupWMClass=${APP_NAME}
EOAPP
mkdir -p AppDir/usr/share/icons/hicolor/256x256/apps
ICON_PATH="AppDir/usr/share/icons/hicolor/256x256/apps/${LINUX_APP_ID}.png"
if [ -f "assets/icons/logo.png" ]; then
ICON_SOURCE="assets/icons/logo.png"
elif [ -f "linux/runner/resources/logo.png" ]; then
ICON_SOURCE="linux/runner/resources/logo.png"
else
echo "ERROR: no app logo found at assets/icons/logo.png or linux/runner/resources/logo.png"
exit 1
fi
python3 -c "from PIL import Image; Image.open('$ICON_SOURCE').resize((256, 256), Image.LANCZOS).save('$ICON_PATH')"
if [ ! -f "$ICON_PATH" ]; then
echo "ERROR: icon was not created at $ICON_PATH"
exit 1
fi
export LINUXDEPLOY=./linuxdeploy-x86_64.AppImage
export LINUXDEPLOY_PLUGIN_GTK=./linuxdeploy-plugin-gtk.sh
# Phase 1: let linuxdeploy populate the AppDir (deps, AppRun, desktop,
# icon, GTK) but DO NOT package yet (no --output).
./linuxdeploy-x86_64.AppImage \
--appdir AppDir \
--executable "$APP_BIN" \
--desktop-file "AppDir/usr/share/applications/${LINUX_APP_ID}.desktop" \
--icon-file "$ICON_PATH" \
--plugin gtk
# Phase 2: libmdk dlopens its FFmpeg backend (libffmpeg.so.*) + codec
# plugins at RUNTIME — they are NOT in libmdk's DT_NEEDED graph, so
# linuxdeploy never bundles them and playback SIGSEGVs the instant it
# starts (MediaIO::create -> NULL FFmpeg I/O vtable). Physically copy
# the whole mdk runtime into usr/lib (where linuxdeploy put libmdk) so
# dlopen finds it via the AppRun's LD_LIBRARY_PATH.
MDK_LIB_DIR="$(dirname "$APP_BIN")/lib"
mkdir -p AppDir/usr/lib
copied=0
for so in "$MDK_LIB_DIR"/libffmpeg.so* "$MDK_LIB_DIR"/libmdk*.so* "$MDK_LIB_DIR"/libc++.so*; do
[ -e "$so" ] || continue
cp -aP "$so" AppDir/usr/lib/ && echo "Bundled $(basename "$so")" && copied=1
done
if [ "$copied" != 1 ]; then
echo "ERROR: no libmdk runtime libs found in $MDK_LIB_DIR"
ls -la "$MDK_LIB_DIR" || true
exit 1
fi
# Phase 3: package the AppDir verbatim. appimagetool copies the tree
# as-is, so the dlopen-only libs we just placed cannot be stripped.
curl -L -o appimagetool-x86_64.AppImage \
https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage
chmod +x appimagetool-x86_64.AppImage
FINAL="${APP_NAME}-linux-v${VERSION}.AppImage"
ARCH=x86_64 ./appimagetool-x86_64.AppImage --no-appstream AppDir "$FINAL"
chmod +x "$FINAL"
# Guard: fail the build (don't ship) if libffmpeg didn't make it in.
rm -rf squashfs-root
"./$FINAL" --appimage-extract >/dev/null 2>&1 || true
if ! ls squashfs-root/usr/lib/libffmpeg.so* >/dev/null 2>&1; then
echo "ERROR: libffmpeg.so missing from AppImage — playback would crash."
find squashfs-root -name 'libmdk*' -o -name 'libffmpeg*' || true
exit 1
fi
echo "Verified: $(ls squashfs-root/usr/lib/libffmpeg.so*) bundled in AppImage"
rm -rf squashfs-root
echo "appimage_path=$FINAL" >> "$GITHUB_OUTPUT"
- name: Upload Linux artifacts
uses: actions/upload-artifact@v6
with:
name: MiruShin-linux
path: |
${{ steps.appimage.outputs.appimage_path }}
${{ steps.tgz.outputs.tgz_path }}
build-windows:
runs-on: windows-2025-vs2026
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Setup Flutter (stable)
uses: subosito/flutter-action@v2
with:
channel: stable
cache: false
- name: Flutter pub get
run: flutter pub get
- name: Build Windows (release)
env:
FVP_DEPS_URL: https://github.com/wang-bin/mdk-sdk/releases/latest/download
run: flutter build windows --release
- name: Read version from pubspec.yaml
id: ver
shell: pwsh
run: |
$v = (Get-Content "pubspec.yaml" | Where-Object { $_ -match '^version:' } | ForEach-Object {
($_ -split ':')[1].Trim().Split(' ')[0]
} | Select-Object -First 1)
if (-not $v) { throw "Version not found in pubspec.yaml" }
$v = $v.Split('+')[0]
"version=$v" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
- name: Create ZIP from Release folder contents
id: zip
shell: pwsh
run: |
$sourceDir = "build/windows/x64/runner/Release"
if (-not (Test-Path $sourceDir)) { throw "Source folder not found: $sourceDir" }
$version = "${{ steps.ver.outputs.version }}"
$zipFile = "${{ env.APP_NAME }}-windows-v$version-portable.zip"
if (Test-Path $zipFile) { Remove-Item $zipFile -Force }
Compress-Archive -Path (Join-Path $sourceDir '*') -DestinationPath $zipFile -Force
"zip_path=$zipFile" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
- name: Build Inno Setup installer
id: installer
shell: pwsh
run: |
$iscc = $null
$candidates = @(
"C:\Program Files (x86)\Inno Setup 6\ISCC.exe",
"C:\Program Files (x86)\Inno Setup 5\ISCC.exe"
)
foreach ($c in $candidates) {
if (Test-Path $c) { $iscc = $c; break }
}
if (-not $iscc) {
choco install innosetup --no-progress -y
$iscc = "C:\Program Files (x86)\Inno Setup 6\ISCC.exe"
}
$version = "${{ steps.ver.outputs.version }}"
$sourceDir = (Resolve-Path "build/windows/x64/runner/Release").Path
& "$iscc" `
"/DMyAppVersion=$version" `
"/DMySourceDir=$sourceDir" `
"/O." `
"windows\installer\mirushin.iss"
if ($LASTEXITCODE -ne 0) { throw "Inno Setup compilation failed (exit $LASTEXITCODE)" }
$exeFile = "${{ env.APP_NAME }}-windows-v$version-setup.exe"
"installer_path=$exeFile" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
- name: Upload Windows artifacts
uses: actions/upload-artifact@v6
with:
name: MiruShin-windows
path: |
${{ steps.zip.outputs.zip_path }}
${{ steps.installer.outputs.installer_path }}
build-macos-ios:
runs-on: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Setup Flutter (stable)
uses: subosito/flutter-action@v2
with:
channel: stable
cache: true
- name: Flutter pub get
run: flutter pub get
- name: Pre-cache mdk SDK (bypass unreliable SourceForge nightly)
run: |
set -e
MDK_SF_URL="https://sourceforge.net/projects/mdk-sdk/files/nightly/mdk-sdk-apple.tar.xz"
MDK_GH_URL="https://github.com/wang-bin/mdk-sdk/releases/latest/download/mdk-sdk-apple.tar.xz"
CACHE_HASH=$(ruby -rdigest -e 'puts Digest::MD5.hexdigest({:http=>ARGV[0]}.to_s)' "$MDK_SF_URL")
CACHE_DIR="$HOME/Library/Caches/CocoaPods/Pods/External/mdk/$CACHE_HASH"
if [ ! -d "$CACHE_DIR" ]; then
echo "Downloading mdk SDK from GitHub releases..."
mkdir -p "$CACHE_DIR"
curl -fL "$MDK_GH_URL" -o /tmp/mdk-sdk-apple.tar.xz
tar xf /tmp/mdk-sdk-apple.tar.xz -C "$CACHE_DIR"
rm -f /tmp/mdk-sdk-apple.tar.xz
echo "Cached at $CACHE_DIR"
fi
- name: Prepare iOS no-codesign config
shell: bash
run: |
{
echo ""
echo "CODE_SIGN_ENTITLEMENTS="
echo "DEVELOPMENT_TEAM="
echo "PROVISIONING_PROFILE_SPECIFIER="
echo "CODE_SIGN_STYLE=Manual"
} >> ios/Flutter/Release.xcconfig
- name: Build iOS (release, no codesign)
run: flutter build ios --release --no-codesign
- name: Read version from pubspec.yaml
id: ver
shell: bash
run: |
VERSION=$(grep '^version:' pubspec.yaml | awk '{print $2}' | cut -d'+' -f1)
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- name: Create IPA
shell: bash
run: |
set -euo pipefail
VERSION="${{ steps.ver.outputs.version }}"
APP_PATH="build/ios/iphoneos/Runner.app"
if [ ! -d "$APP_PATH" ]; then
echo "ERROR: iOS app not found at $APP_PATH"
ls -la build/ios/iphoneos || true
exit 1
fi
rm -rf Payload
mkdir -p Payload
cp -R "$APP_PATH" Payload/
zip -r "${APP_NAME}-ios-v${VERSION}.ipa" Payload
- name: Build macOS (release)
run: flutter build macos --release
- name: Package DMG (drag & drop)
shell: bash
run: bash .github/scripts/package-macos-dmg.sh
- name: Upload iOS artifact
uses: actions/upload-artifact@v6
with:
name: MiruShin-ios
path: MiruShin-ios-v*.ipa
- name: Upload macOS artifact
uses: actions/upload-artifact@v6
with:
name: MiruShin-macos
path: MiruShin-macos-v*.dmg
package-all:
runs-on: ubuntu-latest
needs: [build-android, build-linux, build-windows, build-macos-ios]
steps:
- name: Download all artifacts
uses: actions/download-artifact@v8
with:
path: dist
merge-multiple: true
- name: Show bundled files
shell: bash
run: |
set -euo pipefail
find dist -maxdepth 1 -type f | sort
- name: Upload bundle artifact
uses: actions/upload-artifact@v6
with:
name: MiruShin-all
path: dist/*