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/**' - '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/**' - '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/**' - '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' 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: 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 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' 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 APK audits run: | python3 -m unittest discover -s scripts -p 'test_check_backend_apk.py' - 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: bash scripts/build_android.sh --target lib/main.dart - name: Verify Rust release APK payloads run: | python3 scripts/check_backend_apk.py \ build/app/outputs/flutter-apk/app-arm64-v8a-release.apk \ --backend rust --abis arm64-v8a python3 scripts/check_backend_apk.py \ build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk \ --backend rust --abis armeabi-v7a # 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