From 87b0998759b479cbe0290675b135c3f0fece3883 Mon Sep 17 00:00:00 2001 From: InstaZDLL Date: Tue, 18 Aug 2026 21:56:03 +0200 Subject: [PATCH 1/3] ci: construire et tester chaque pull request MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le 18/08, `main` a cessé de compiler. Deux PR vertes chacune de leur côté — l'une branchée quand le catalogue déclarait encore media3 1.5.1, l'autre le bump Dependabot vers 1.11.0 — ont fusionné en un arbre où `createTestOnlyControllerInfo` avait changé de signature. Rien ne l'a signalé : les 255 tests ne tournaient que sur une machine de développement. Un résultat de fusion que personne ne construit est un résultat que personne n'a vérifié. Le workflow lance `testDebugUnitTest` puis `assembleDebug` sur chaque PR et sur chaque poussée vers `main`. Les deux séparément : le second n'ajoute pas la compilation, déjà faite par les tests, mais l'empaquetage — fusion du manifest, ressources, dexing. Deux choix qui méritent un mot. Temurin 21 plutôt que le JDK local, le projet n'épinglant aucun toolchain : Gradle compile avec le runtime qu'on lui donne, et une LTS toujours présente sur les runners fait un plancher plus stable. Et un cache dédié pour Robolectric, qui va chercher un `android-all-instrumented` par SDK émulé à l'exécution des tests, par Maven et non par Gradle — environ 200 Mo que le cache Gradle ne voit pas passer. Le lint Android reste dehors : il n'a jamais tourné sur ce dépôt, et l'ouvrir dans la même PR mêlerait un filet de sécurité à une dette inconnue. --- .github/workflows/build-and-test.yml | 78 ++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 .github/workflows/build-and-test.yml diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml new file mode 100644 index 0000000..f3a3791 --- /dev/null +++ b/.github/workflows/build-and-test.yml @@ -0,0 +1,78 @@ +name: Build & test + +# Why this exists: on 2026-08-18 `main` stopped compiling. Two pull requests +# were green on their own — one branched while the catalogue still declared +# media3 1.5.1, the other a Dependabot bump to 1.11.0 — and their merge broke +# `createTestOnlyControllerInfo`, whose signature had gained a parameter. +# Nothing reported it: the test suite only ever ran on a developer machine. +# A merge result nobody builds is a merge result nobody has checked. + +on: + pull_request: + push: + branches: [main] + +permissions: + contents: read + +# A new push to the same ref supersedes the previous run: only the latest state +# matters, and a freed runner benefits the other pull requests. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + name: Unit tests and debug build + runs-on: ubuntu-latest + timeout-minutes: 30 + + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + # Temurin 21 rather than the JDK used locally: the project pins no + # toolchain, so Gradle compiles with whatever runtime it gets, and an LTS + # the runners always carry is the steadier floor. `compileOptions` targets + # Java 11 either way. + - name: Set up JDK + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 + with: + distribution: temurin + java-version: '21' + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6.3.0 + + # Robolectric fetches an `android-all-instrumented` jar per emulated SDK + # at *test* runtime, through Maven and not through Gradle — so the Gradle + # cache above never sees it. Around 200 MB, re-downloaded on every run + # without this. + - name: Cache Robolectric runtimes + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ~/.m2/repository/org/robolectric + # The jar is chosen by the Robolectric version and by the SDK the + # tests emulate — one lives in the version catalogue, the other in the + # module's `targetSdk`. + key: robolectric-${{ runner.os }}-${{ hashFiles('gradle/libs.versions.toml', 'app/build.gradle.kts') }} + restore-keys: robolectric-${{ runner.os }}- + + - name: Unit tests + run: ./gradlew testDebugUnitTest + + # Separate from the tests, which already compile the main sources: what + # this adds is packaging — manifest merge, resources, dexing. + - name: Debug build + run: ./gradlew assembleDebug + + # A failed job says a test fell; only the report says which one, and why. + - name: Test reports + if: failure() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: test-reports + path: | + app/build/reports/tests/ + app/build/test-results/ + retention-days: 7 From 8a7feee9addc9830d8f783e6bc327aad4c0bb117 Mon Sep 17 00:00:00 2001 From: InstaZDLL Date: Tue, 18 Aug 2026 22:05:28 +0200 Subject: [PATCH 2/3] =?UTF-8?q?ci:=20taire=20l'artefact=20quand=20la=20com?= =?UTF-8?q?pilation=20a=20=C3=A9chou=C3=A9=20avant=20tout=20rapport?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rejouer la panne du 18/08 sur cette PR a fait tomber le job à la compilation, donc avant qu'un seul rapport de test existe. L'étape d'artefact ajoutait alors une annotation « No files were found », plus voyante que l'erreur qui comptait. Une compilation qui échoue est une façon parfaitement ordinaire de rougir ici. --- .github/workflows/build-and-test.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index f3a3791..9eb7bf0 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -67,6 +67,11 @@ jobs: run: ./gradlew assembleDebug # A failed job says a test fell; only the report says which one, and why. + # + # `ignore` rather than the default `warn`: a compilation failure produces + # no report at all, and that is a perfectly ordinary way for this job to + # go red. Warning about the missing directory would put a second, louder + # annotation next to the error that actually matters. - name: Test reports if: failure() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 @@ -75,4 +80,5 @@ jobs: path: | app/build/reports/tests/ app/build/test-results/ + if-no-files-found: ignore retention-days: 7 From b8373dc0ff1ae67e40eeb6563459c9b2cd89c5d9 Mon Sep 17 00:00:00 2001 From: InstaZDLL Date: Tue, 18 Aug 2026 22:15:37 +0200 Subject: [PATCH 3/3] ci: ne pas laisser le jeton dans la configuration git du runner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `actions/checkout` écrit par défaut le `GITHUB_TOKEN` dans `.git/config` sous forme d'en-tête d'authentification, lisible par toutes les étapes suivantes — la construction Gradle et ce qu'elle résout compris. Aucune étape ne parle à git après le checkout, et le build ne lit rien du dépôt git : le jeton n'a pas de raison de lui survivre. La portée reste `contents: read`, ce qui bornait déjà les dégâts, mais un identifiant qui traîne sur le disque n'a pas besoin d'être puissant pour être de trop. --- .github/workflows/build-and-test.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 9eb7bf0..6b5bb1d 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -30,6 +30,12 @@ jobs: steps: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + # By default the token is written into `.git/config` as an auth + # header, where every later step can read it — the Gradle build and + # anything it resolves included. Nothing here talks to git after the + # checkout, so the credential has no reason to outlive it. + persist-credentials: false # Temurin 21 rather than the JDK used locally: the project pins no # toolchain, so Gradle compiles with whatever runtime it gets, and an LTS