Build All (bundle) #25
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 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/* |