name: CI # One gate per pull request. Branch pushes no longer trigger CI on their own, # so a change is built once on its PR (covering feature -> release/* and # release/* -> main) instead of once per push and again on the merge to main. # The merge itself is handled by release.yaml, which only does heavy work when # the merge actually cuts a release. on: pull_request: # Cancel superseded runs for the same PR. concurrency: group: ci-${{ github.ref }} cancel-in-progress: true jobs: # Single job named `ci` so the required "CI" status check is always reported, # even for docs-only PRs: those just skip the Android build and the job still # succeeds (fast green check) instead of being filtered out and leaving the # required check pending forever. ci: runs-on: docker env: ANDROID_HOME: /opt/android-sdk ANDROID_SDK_ROOT: /opt/android-sdk steps: - name: Checkout uses: actions/checkout@v4 with: # Full history so the base..HEAD diff below has a merge-base. fetch-depth: 0 submodules: recursive # Cheap, always-on guard: the release build must stay reproducible for the # official F-Droid repo (no AGP VCS-info embedding). Runs regardless of # change scope so a regression can't slip through on a "docs-only" PR. - name: Reproducible-release invariant run: bash scripts/check_reproducible_release.sh # Decide whether anything that affects the app build changed. Docs, # F-Droid metadata and the licence don't, so those PRs skip the SDK + # Gradle work below but still report a green `ci`. - name: Classify change scope id: scope run: | set -e BASE="${{ github.base_ref }}" # Full (not --depth=1) base fetch so the merge-base is present even when # the PR branch forked several commits back; a shallow tip has no merge # base with a divergent branch and `git diff base...HEAD` aborts. git fetch --no-tags origin "$BASE" MB=$(git merge-base "origin/$BASE" HEAD 2>/dev/null || true) if [ -z "$MB" ]; then # No common ancestor available — don't risk skipping the build. echo "No merge base with origin/$BASE — running the full build to be safe." echo "code=true" >> "$GITHUB_OUTPUT" exit 0 fi CHANGED=$(git diff --name-only "$MB" HEAD) echo "Changed files:"; echo "$CHANGED" if echo "$CHANGED" | grep -vE '(\.md$|^docs/|^fdroid-metadata/|^fastlane/|^LICENSE$)' | grep -q .; then echo "code=true" >> "$GITHUB_OUTPUT" else echo "code=false" >> "$GITHUB_OUTPUT" echo "Docs/metadata-only change — skipping the Android build." fi - name: Setup Java if: steps.scope.outputs.code == 'true' uses: actions/setup-java@v4 with: distribution: 'zulu' java-version: '17' - name: Setup Android SDK if: steps.scope.outputs.code == 'true' uses: android-actions/setup-android@v3 with: # Default ("tools platform-tools") drags in the Android Emulator # (~300 MB) which the build never uses. packages: '' - name: Setup Android SDK cache if: steps.scope.outputs.code == 'true' uses: actions/cache@v4 with: path: /opt/android-sdk key: ${{ runner.os }}-android-sdk-37-36.0.0 - name: Install Android SDK packages if: steps.scope.outputs.code == 'true' run: | yes | sdkmanager --licenses >/dev/null || true sdkmanager \ "platform-tools" \ "platforms;android-37.0" \ "build-tools;36.0.0" - name: Setup Gradle cache if: steps.scope.outputs.code == 'true' uses: actions/cache@v4 with: path: | ~/.gradle/caches ~/.gradle/wrapper key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', 'gradle/libs.versions.toml') }} restore-keys: | ${{ runner.os }}-gradle- - name: Grant execute permission for gradlew if: steps.scope.outputs.code == 'true' run: chmod +x ./gradlew # No --no-daemon: the daemon lives only as long as this job container # and lets the following steps skip JVM startup + reconfiguration. - name: Lint (debug variant only) if: steps.scope.outputs.code == 'true' run: ./gradlew lintDebug - name: Unit tests if: steps.scope.outputs.code == 'true' run: ./gradlew testDebugUnitTest - name: Assemble debug APK if: steps.scope.outputs.code == 'true' run: ./gradlew assembleDebug - name: Trivy filesystem scan if: steps.scope.outputs.code == 'true' run: | set -e SUDO="" if command -v sudo >/dev/null 2>&1; then SUDO="sudo" fi if command -v apt-get >/dev/null 2>&1; then $SUDO apt-get update $SUDO apt-get install -y wget apt-transport-https gnupg lsb-release wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | gpg --dearmor | $SUDO tee /usr/share/keyrings/trivy.gpg > /dev/null echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] https://aquasecurity.github.io/trivy-repo/deb generic main" | $SUDO tee /etc/apt/sources.list.d/trivy.list $SUDO apt-get update $SUDO apt-get install -y trivy fi trivy filesystem --severity HIGH,CRITICAL --exit-code 0 . continue-on-error: true