diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 60536f5..bf2b0f6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,9 +1,13 @@ name: Build +# Pull requests are covered by status_check.yml, which builds the real FFmpeg AAR and +# runs the instrumented suite across API 33-37. This workflow keeps the post-merge and +# release duties, and deliberately does not duplicate PR validation: its unit job falls +# back to a stub AAR, which is a weaker check than the one status_check.yml performs. on: push: branches: [main] - pull_request: + tags: ['v*'] jobs: test: diff --git a/.github/workflows/status_check.yml b/.github/workflows/status_check.yml new file mode 100644 index 0000000..e400e26 --- /dev/null +++ b/.github/workflows/status_check.yml @@ -0,0 +1,184 @@ +name: Status check + +on: + pull_request: + branches: [main] + +# A newer push to the same PR makes the in-flight run obsolete. Emulator matrices are +# expensive, so cancel rather than let them pile up. +concurrency: + group: status-check-${{ github.ref }} + cancel-in-progress: true + +env: + # Must match the coordinate app/build.gradle.kts loads from app/libs/. + FFMPEG_AAR: ffmpeg-kit-next-8.1.1.aar + +jobs: + # --------------------------------------------------------------------------- + # FFmpeg is not committed: the AAR is ~35 MB of native code, and F-Droid strips + # checked-in binaries. Every other job needs it, because the app module compiles + # against it and the instrumented tests exercise it for real. + # + # Building it is a full cross-compile of FFmpeg, x264, x265 and SVT-AV1, so it is + # cached on the contents of tools/ffmpeg. That directory pins the upstream tag and + # the configure flags, which is exactly what determines the output. + # --------------------------------------------------------------------------- + ffmpeg: + name: FFmpeg AAR + runs-on: ubuntu-latest + timeout-minutes: 120 + steps: + - uses: actions/checkout@v4 + + - name: Restore cached AAR + id: cache + uses: actions/cache@v4 + with: + path: tools/ffmpeg/out + key: ffmpeg-aar-${{ hashFiles('tools/ffmpeg/Containerfile', 'tools/ffmpeg/build-ffmpeg.sh') }} + + # The Nix store plus the build tree runs to several gigabytes, which does not fit + # alongside the runner's preinstalled toolchains. + - name: Free disk space + if: steps.cache.outputs.cache-hit != 'true' + run: | + sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /usr/local/share/boost + sudo docker image prune -af || true + df -h / + + - name: Build the AAR + if: steps.cache.outputs.cache-hit != 'true' + working-directory: tools/ffmpeg + run: | + mkdir -p out + docker build -t ffmpeg-kit-builder:ci -f Containerfile . + docker run --rm -v "$PWD/out":/work/out ffmpeg-kit-builder:ci full + + - name: Check the AAR is present and plausibly complete + run: | + AAR=$(find tools/ffmpeg/out -name 'ffmpeg-kit-next*.aar' | head -1) + test -n "$AAR" || { echo "::error::no AAR produced"; exit 1; } + # A truncated or stub AAR would still satisfy `test -f`, so check it carries + # native libraries for both ABIs before letting the matrix depend on it. + for abi in arm64-v8a x86_64; do + n=$(unzip -l "$AAR" | grep -c "jni/$abi/.*\.so$" || true) + echo " $abi: $n shared libraries" + test "$n" -gt 0 || { echo "::error::AAR has no $abi libraries"; exit 1; } + done + cp "$AAR" "${{ env.FFMPEG_AAR }}" + + - uses: actions/upload-artifact@v4 + with: + name: ffmpeg-aar + path: ${{ env.FFMPEG_AAR }} + retention-days: 1 + + # --------------------------------------------------------------------------- + # JVM tests: the routing matrix, the FFmpeg argument builder, the concat planner + # and the retry rule. These need no device and are the fastest signal on a PR. + # --------------------------------------------------------------------------- + unit: + name: Unit tests + runs-on: ubuntu-latest + needs: ffmpeg + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + + - uses: actions/download-artifact@v4 + with: + name: ffmpeg-aar + path: app/libs/ + + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '17' # AGP 9 will not run on anything older + + - uses: gradle/actions/setup-gradle@v4 + + - run: ./gradlew :app:testDebugUnitTest + + - uses: actions/upload-artifact@v4 + if: always() + with: + name: unit-test-report + path: app/build/reports/tests/ + + # --------------------------------------------------------------------------- + # Instrumented tests across the whole supported range. minSdk is 33 and targetSdk + # is 37, and the foreground-service type differs across that range -- none below + # 34, dataSync at 34, mediaProcessing from 35 -- so a single API level would leave + # two thirds of that branch unexercised. + # --------------------------------------------------------------------------- + instrumented: + name: E2E API ${{ matrix.api-level }} + runs-on: ubuntu-latest + needs: ffmpeg + timeout-minutes: 60 + strategy: + # Report every API level rather than stopping at the first red one: knowing + # whether a failure is universal or specific to one level is most of the + # diagnosis. + fail-fast: false + matrix: + include: + - api-level: 33 + system-image-api-level: 33 + - api-level: 34 + system-image-api-level: 34 + - api-level: 35 + system-image-api-level: 35 + - api-level: 36 + system-image-api-level: 36 + # API 37 is published as android-37.0, not android-37, so the image level + # has to be given separately or the download resolves to nothing. + - api-level: 37 + system-image-api-level: "37.0" + steps: + - uses: actions/checkout@v4 + + - uses: actions/download-artifact@v4 + with: + name: ffmpeg-aar + path: app/libs/ + + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '17' + + - uses: gradle/actions/setup-gradle@v4 + + # Without this the emulator falls back to software rendering and takes minutes + # longer to boot, when it boots at all. + - name: Enable KVM + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' \ + | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + - name: Instrumented tests + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{ matrix.api-level }} + system-image-api-level: ${{ matrix.system-image-api-level }} + target: google_apis + arch: x86_64 + profile: pixel_6 + # -gpu swiftshader_indirect is the usual CI choice. It is correct here only + # because runners have no GPU to pass through; on a workstation the same + # setting routes through SwiftShader's JIT, which is a known crash source. + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: true + script: ./gradlew :app:connectedDebugAndroidTest + + - uses: actions/upload-artifact@v4 + if: always() + with: + name: e2e-report-api${{ matrix.api-level }} + path: | + app/build/reports/androidTests/ + app/build/outputs/androidTest-results/