name: Pull Request CI on: pull_request: branches: [ main ] paths-ignore: - '**/*.md' - 'docs/**' - '.gitignore' concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: # 1. CHANGE DETECTION: Prevents unnecessary builds check-changes: if: github.repository == 'meshtastic/Meshtastic-Android' && !( github.head_ref == 'scheduled-updates' || github.head_ref == 'l10n_main' ) runs-on: ubuntu-latest outputs: android: ${{ steps.filter.outputs.android }} steps: - uses: actions/checkout@v6 - uses: dorny/paths-filter@v4 id: filter with: token: '' filters: | android: # CI/workflow implementation - '.github/workflows/**' - '.github/actions/**' # Product modules validated by reusable-check - 'app/**' - 'baselineprofile/**' - 'desktop/**' - 'core/**' - 'feature/**' - 'mesh_service_example/**' # Shared build infrastructure - 'build-logic/**' - 'config/**' - 'gradle/**' # Root build entrypoints/config that can alter task graph or outputs - 'build.gradle.kts' - 'config.properties' - 'compose_compiler_config.conf' - 'gradle.properties' - 'gradlew' - 'gradlew.bat' - 'settings.gradle.kts' - 'test.gradle.kts' # 1b. FILTER DRIFT CHECK: Ensures check-changes stays aligned with module roots verify-check-changes-filter: if: github.repository == 'meshtastic/Meshtastic-Android' && !( github.head_ref == 'scheduled-updates' || github.head_ref == 'l10n_main' ) runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Verify module roots are represented in check-changes filter run: | python3 - <<'PY' import re from pathlib import Path settings = Path('settings.gradle.kts').read_text() workflow = Path('.github/workflows/pull-request.yml').read_text() module_roots = { module.split(':')[0] for module in re.findall(r'":([^"]+)"', settings) } allowed_extra_roots = {'baselineprofile'} expected_roots = module_roots | allowed_extra_roots filter_paths = { path.split('/')[0] for path in re.findall(r"-\s*'([^']+/\*\*)'", workflow) } actual_module_roots = filter_paths & expected_roots missing = sorted(expected_roots - actual_module_roots) unexpected = sorted(actual_module_roots - expected_roots) if missing or unexpected: print('check-changes filter drift detected:') if missing: print(' Missing roots:', ', '.join(missing)) if unexpected: print(' Unexpected roots:', ', '.join(unexpected)) raise SystemExit(1) print('check-changes filter is aligned with settings.gradle module roots.') PY # 2. VALIDATION & BUILD: Delegate to reusable-check.yml # We disable instrumented tests for PRs to keep feedback fast (< 10 mins). validate-and-build: needs: [check-changes, verify-check-changes-filter] if: needs.check-changes.outputs.android == 'true' uses: ./.github/workflows/reusable-check.yml with: run_lint: true run_unit_tests: true run_instrumented_tests: false api_levels: '[35]' upload_artifacts: true secrets: inherit # 3. WORKFLOW STATUS: Ensures required checks are satisfied check-workflow-status: name: Check Workflow Status runs-on: ubuntu-latest needs: [check-changes, verify-check-changes-filter, validate-and-build] if: always() steps: - name: Check Workflow Status run: | if [[ "${{ needs.verify-check-changes-filter.result }}" == "failure" || "${{ needs.verify-check-changes-filter.result }}" == "cancelled" ]]; then echo "::error::check-changes filter verification failed" exit 1 fi # If changes were detected but build failed, fail the status check if [[ "${{ needs.check-changes.outputs.android }}" == "true" && ("${{ needs.validate-and-build.result }}" == "failure" || "${{ needs.validate-and-build.result }}" == "cancelled") ]]; then echo "::error::Android Check failed" exit 1 fi # If no changes were detected, this still succeeds to satisfy required status check echo "Workflow status satisfied."