Files
SpotiFLAC-Mobile/.github/workflows/ci.yml
T
zarzet b8c50b1527 perf(android): reduce release APK size
Keep Dart debug data outside libapp.so and archive matching symbols and R8 mappings in CI. Pack native relocations, preserve JNI exports, and recompress PNG assets losslessly.

ARM64 APK shrinks from 80,306,680 to 76,080,247 bytes (5.26%). Verified with 1,877 Flutter tests, 19 native device tests, Discord SDK integration, and release startup on a 16 KiB Android emulator.
2026-10-06 15:22:42 +07:00

334 lines
12 KiB
YAML

name: CI
on:
pull_request:
push:
branches:
- main
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
changes:
name: Detect changed paths
runs-on: ubuntu-latest
permissions:
pull-requests: read
outputs:
dart: ${{ steps.filter.outputs.dart }}
android: ${{ steps.filter.outputs.android }}
ios: ${{ steps.filter.outputs.ios }}
rust: ${{ steps.filter.outputs.rust }}
steps:
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Filter paths
uses: dorny/paths-filter@0e4a8c6effa4802afeda77dc8d303f8176d7dfad # v3
id: filter
with:
filters: |
dart:
- 'third_party/audio_service/**'
- 'third_party/spotiflac_discord/**'
- 'lib/**'
- 'test/**'
- 'android/app/src/test/resources/**'
- 'assets/**'
- 'pubspec.yaml'
- 'pubspec.lock'
- 'analysis_options.yaml'
- 'l10n.yaml'
- '.fvmrc'
- '.github/workflows/ci.yml'
rust:
- 'rust_backend/**'
- 'scripts/*rust*.sh'
- 'android/gradlew'
- 'android/gradle/wrapper/**'
- '.github/workflows/ci.yml'
ios:
- 'third_party/audio_service/**'
- 'third_party/spotiflac_discord/**'
- 'scripts/setup_discord_sdk.sh'
- 'scripts/prepare_discord_sdk.py'
- 'scripts/test_prepare_discord_sdk.py'
- '.github/actions/discord-sdk/**'
- 'third_party/discord/*.zip.gpg'
- 'ios/**'
- 'scripts/build_ios.sh'
- 'scripts/check_backend_ios.py'
- 'scripts/test_check_backend_ios.py'
- 'lib/**'
- 'assets/**'
- 'pubspec.yaml'
- 'pubspec.lock'
- 'l10n.yaml'
- '.fvmrc'
- '.github/workflows/ci.yml'
android:
- 'third_party/audio_service/**'
- 'third_party/spotiflac_discord/**'
- 'scripts/setup_discord_sdk.sh'
- 'scripts/prepare_discord_sdk.py'
- 'scripts/test_prepare_discord_sdk.py'
- '.github/actions/discord-sdk/**'
- 'third_party/discord/*.zip.gpg'
- 'android/**'
- 'rust_backend/**'
- 'scripts/build_android.sh'
- 'scripts/build_rust_backend.sh'
- 'scripts/check_backend_apk.py'
- 'scripts/test_check_backend_apk.py'
- 'lib/**'
- 'assets/**'
- 'pubspec.yaml'
- 'pubspec.lock'
- 'l10n.yaml'
- '.fvmrc'
- '.github/workflows/ci.yml'
flutter:
name: Flutter analyze & test
runs-on: ubuntu-latest
needs: changes
if: needs.changes.outputs.dart == 'true'
steps:
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Setup Flutter
uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2
with:
channel: "stable"
flutter-version-file: .fvmrc
cache: true
- name: Cache pub dependencies
uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5
with:
path: ~/.pub-cache
key: pub-${{ runner.os }}-${{ hashFiles('pubspec.lock') }}
restore-keys: pub-${{ runner.os }}-
- name: Get Flutter dependencies
run: flutter pub get
- name: Analyze
run: flutter analyze
- name: Install media test tools
run: |
sudo apt-get update
sudo apt-get install -y ffmpeg
ffmpeg -version
ffprobe -version
- name: Run tests
run: flutter test
rust:
name: Rust checks & Android libraries
runs-on: ubuntu-latest
needs: changes
if: needs.changes.outputs.rust == 'true'
steps:
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Setup Java for Android SDK tools
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5
with:
distribution: "temurin"
java-version: "25"
- name: Activate pinned Rust toolchain
working-directory: rust_backend
run: rustup show
- name: Install C binding generator dependencies
run: |
sudo apt-get update
sudo apt-get install -y libclang-dev
- name: Rust formatting, Clippy and unit tests
run: bash scripts/check_rust_backend.sh
- name: Install Android NDK
run: '"$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" "ndk;29.0.14206865"'
- name: Build Android ARM32 and ARM64 libraries
run: ANDROID_NDK_HOME="$ANDROID_HOME/ndk/29.0.14206865" bash scripts/build_rust_backend.sh android
rust-ios:
name: Rust iOS libraries & release application
runs-on: macos-latest
needs: changes
if: needs.changes.outputs.rust == 'true' || needs.changes.outputs.ios == 'true'
env:
SPOTIFLAC_DISCORD_SDK: "0"
steps:
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Activate pinned Rust toolchain
working-directory: rust_backend
run: rustup show
- name: Build iOS device and universal simulator XCFramework
run: bash scripts/build_rust_backend.sh ios
- name: Setup Flutter
uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2
with:
channel: "stable"
flutter-version-file: .fvmrc
cache: true
- name: Prepare pinned Discord SDK
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
uses: ./.github/actions/discord-sdk
with:
passphrase: ${{ secrets.DISCORD_MOBILE_SDK_PASSPHRASE }}
- name: Configure Rust application pods
run: |
flutter pub get
cd ios
pod install
- name: Build and audit Rust iOS release application
run: |
python3 -m unittest discover -s scripts -p 'test_check_backend_ios.py'
flutter build ios --release --no-codesign --target lib/main.dart
if [[ "$SPOTIFLAC_DISCORD_SDK" == "1" ]]; then
test -f build/ios/iphoneos/Runner.app/Frameworks/discord_partner_sdk.framework/discord_partner_sdk
test -f build/ios/iphoneos/Runner.app/Discord-License-Notices.txt
fi
python3 scripts/check_backend_ios.py build/ios/iphoneos/Runner.app \
--backend rust --archs arm64 --platform ios --release
android:
name: Android Rust builds & native tests
runs-on: ubuntu-latest
needs: changes
if: needs.changes.outputs.android == 'true'
env:
SPOTIFLAC_DISCORD_SDK: "0"
steps:
- name: Checkout repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Setup Java
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5
with:
distribution: "temurin"
java-version: "25"
- name: Setup Flutter
uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2
with:
channel: "stable"
flutter-version-file: .fvmrc
cache: true
- name: Setup Gradle
uses: gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5
with:
gradle-version: "9.8.0"
# Cache public dependencies, not transforms of the decrypted SDK.
gradle-home-cache-includes: |
caches/modules-2
notifications
- name: Install Android SDK & NDK
run: |
yes | "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --licenses || true
"$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" \
"ndk;29.0.14206865" \
"platforms;android-37.0" \
"build-tools;37.0.0" \
"cmake;3.22.1"
echo "ANDROID_NDK_HOME=$ANDROID_HOME/ndk/29.0.14206865" >> "$GITHUB_ENV"
- name: Test SDK preparation, release selection and APK audits
run: |
python3 -m unittest discover -s scripts -p 'test_prepare_discord_sdk.py'
python3 -m unittest discover -s scripts -p 'test_build_android.py'
python3 -m unittest discover -s scripts -p 'test_check_backend_apk.py'
# Fork PRs cannot access repository secrets and exercise the Lite build.
- name: Prepare pinned Discord SDK
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
uses: ./.github/actions/discord-sdk
with:
passphrase: ${{ secrets.DISCORD_MOBILE_SDK_PASSPHRASE }}
- name: Get Flutter dependencies
run: flutter pub get
- name: Configure Flutter SDK for Gradle
run: echo "flutter.sdk=$FLUTTER_ROOT" > android/local.properties
- name: Install Rust application build and media test prerequisites
run: |
cd rust_backend
rustup show
sudo apt-get update
sudo apt-get install -y libclang-dev ffmpeg
ffmpeg -version
echo "SPOTIFLAC_TEST_FFMPEG=$(command -v ffmpeg)" >> "$GITHUB_ENV"
- name: Package Rust application and run native unit tests
run: gradle -p android :app:assembleDebug :app:testDebugUnitTest
- name: Build Rust release application
run: |
if [[ "$SPOTIFLAC_DISCORD_SDK" == "1" ]]; then
bash scripts/build_android.sh --production --target lib/main.dart
else
bash scripts/build_android.sh --lite --target lib/main.dart
fi
- name: Verify Rust release APK payloads
run: |
audit_flags=()
if [[ "$SPOTIFLAC_DISCORD_SDK" == "1" ]]; then
audit_flags+=(--discord-sdk)
fi
python3 scripts/check_backend_apk.py \
build/app/outputs/flutter-apk/app-arm64-v8a-release.apk \
--backend rust --abis arm64-v8a "${audit_flags[@]}"
python3 scripts/check_backend_apk.py \
build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk \
--backend rust --abis armeabi-v7a "${audit_flags[@]}"
# PR builds use Gradle's test key; production signing stays in Release.
for abi in arm64-v8a armeabi-v7a; do
"$ANDROID_HOME/build-tools/37.0.0/apksigner" verify --verbose \
"build/app/outputs/flutter-apk/app-${abi}-release.apk"
done
- name: Record Android symbol provenance
run: |
test -s build/symbols/android/app.android-arm64.symbols
test -s build/symbols/android/app.android-arm.symbols
test -s build/app/outputs/mapping/release/mapping.txt
git rev-parse HEAD > build/symbols/android/commit.txt
sha256sum build/app/outputs/flutter-apk/*release.apk > build/symbols/android/apk-sha256.txt
- name: Retain Android debug symbols
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: android-symbols-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
if-no-files-found: error
retention-days: 90
path: |
build/symbols/android/
build/app/outputs/mapping/release/mapping.txt