Pipeline CI : build, test et empaquetage de l'APK Android sur chaque push sur main #64

Closed
opened 2026-09-06 10:02:35 +02:00 by kyuno · 0 comments
Owner

Contexte

Suite à l'issue #39
(portage mobile Capacitor) : le projet natif Android existe déjà
(apps/web/android/, généré par npx cap add android, branche
feat/capacitor-android-native — PR pas encore mergée au moment où ce
ticket est écrit). Le seul suivi manuel resté documenté sur #39 est
"build APK + test appareil, toujours manuel dans Android Studio (la CI ne
build pas Android)". Le but ici : que ce build+test+empaquetage se fasse
automatiquement à chaque push sur main, produisant un APK téléchargeable.

Trouvé en creusant le pipeline existant, hors scope de ce ticket (voir
plus bas)
: .github/workflows/release.yml (déclenché par un tag
vX.Y.Z) référence des actions GitHub sans URL complète
(actions/checkout@v4 au lieu de https://github.com/actions/checkout@v4,
contrairement à ci.yml qui a bien été mis à jour pour l'hébergement
Forgejo auto-hébergé) et utilise softprops/action-gh-release, une action
de release spécifique à GitHub — très probablement cassé depuis la
migration vers Forgejo. Signalé mais pas corrigé ici (décision prise avec
l'utilisateur : ce ticket reste scopé à l'artefact sur main).

Décisions validées avec l'utilisateur

Question Décision
Signature de l'APK Release signée (vrai keystore), pas un simple build debug
Périmètre Juste l'artefact sur push main — ne touche pas à release.yml

Conception — pourquoi le keystore ne peut pas être généré par la CI

Android refuse d'installer la mise à jour d'une appli si elle est signée
par une clé différente de la version déjà installée — le keystore doit donc
être créé une seule fois, à la main, hors CI, puis rester stable pour
toujours :

keytool -genkeypair -v -keystore batchcooking-release.jks \
  -alias batchcooking -keyalg RSA -keysize 2048 -validity 10000

Le fichier + ses mots de passe deviennent des secrets du dépôt Forgejo —
même précédent que PORTAINER_WEBHOOK_URL déjà utilisé dans release.yml :

  • ANDROID_KEYSTORE_BASE64 (le .jks encodé en base64)
  • ANDROID_KEYSTORE_PASSWORD
  • ANDROID_KEY_ALIAS
  • ANDROID_KEY_PASSWORD
  • MOBILE_API_URL (l'URL publique de l'API — même valeur que
    apps/web/.env.capacitor.example's VITE_API_URL, mais comme secret
    plutôt qu'en dur, cohérent avec "aucun secret avec une valeur par défaut")

Conception — configuration Gradle de signature

apps/web/android/app/build.gradle n'a aujourd'hui aucune
signingConfig
(vérifié : buildTypes.release, lignes 19-24, ne
référence rien) — assembleRelease produirait donc un APK non signé en
l'état. Ajouter, lu depuis l'environnement et jamais codé en dur :

android {
    signingConfigs {
        release {
            storeFile file(System.getenv("ANDROID_KEYSTORE_PATH") ?: "release.keystore")
            storePassword System.getenv("ANDROID_KEYSTORE_PASSWORD")
            keyAlias System.getenv("ANDROID_KEY_ALIAS")
            keyPassword System.getenv("ANDROID_KEY_PASSWORD")
        }
    }
    buildTypes {
        release {
            signingConfig signingConfigs.release
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

Conception — le job CI

Dans ci.yml directement (pas un nouveau fichier de workflow séparé) :
un 6e job build-android, avec needs: [lint, test, intent-service-test, build, e2e] et if: github.ref == 'refs/heads/main'. C'est ce qui répond
réellement à "testée" — GitHub/Forgejo Actions n'a pas de mécanisme natif
pour qu'un job d'un autre fichier de workflow attende un run ailleurs,
donc rester dans le même fichier est ce qui permet un vrai needs: bloquant
plutôt qu'un simple déclenchement en parallèle qui ignorerait un échec des
tests.

build-android:
  runs-on: ubuntu-latest
  needs: [lint, test, intent-service-test, build, e2e]
  if: github.ref == 'refs/heads/main'
  steps:
    - uses: https://github.com/actions/checkout@v4
    - uses: pnpm/action-setup@v4
    - uses: https://github.com/actions/setup-node@v4
      with: { node-version: 22, cache: pnpm }
    - uses: https://github.com/actions/setup-java@v4
      with: { distribution: temurin, java-version: "21" } # à confirmer contre l'AGP réel du projet généré
    - uses: https://github.com/android-actions/setup-android@v3
    - run: pnpm install --frozen-lockfile

    - name: Build le bundle web pour Capacitor
      env:
        VITE_API_URL: ${{ secrets.MOBILE_API_URL }}
      run: pnpm --filter web build:capacitor
    - run: pnpm --filter web exec cap sync android

    - name: Décode le keystore de release
      env:
        ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
      run: echo "$ANDROID_KEYSTORE_BASE64" | base64 -d > apps/web/android/app/release.keystore

    - name: Build l'APK signé
      working-directory: apps/web/android
      env:
        ANDROID_KEYSTORE_PATH: release.keystore
        ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
        ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
        ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
      run: ./gradlew assembleRelease

    - uses: https://github.com/actions/upload-artifact@v4
      with:
        name: batchcooking-release.apk
        path: apps/web/android/app/build/outputs/apk/release/app-release.apk

"Testée" dans ce contexte précis : aucune suite de tests instrumentés
Android n'existe (zéro code natif au-delà de la coquille générée par
Capacitor) — il n'y a rien de spécifiquement "mobile" à tester au sens
Espresso/UI natif. "Testée" est donc satisfait par le blocage sur les 5 jobs
existants (Mocha, Cypress, lint, build, pytest du service NLP) avant de
packager quoi que ce soit — pas une suite native à inventer pour cette
itération.

Risque connu à documenter, pas à résoudre par avance

Aucun build Android n'a jamais tourné sur ce runner Forgejo auto-hébergé
(act) — contrairement à lint/test/build/e2e, rien ne prouve
aujourd'hui qu'installer le SDK Android + builder via Gradle tienne dans un
temps/espace disque raisonnable sur cette machine. À vérifier par un
premier run réel plutôt que par une supposition ici ; si le SDK Android
doit être retéléchargé à chaque run, actions/cache sur
~/.android/sdk/~/.gradle (même esprit que le cache Cypress déjà présent
dans e2e) sera probablement nécessaire pour un temps de build raisonnable
sur les runs suivants.

Fichiers à toucher

  • .github/workflows/ci.yml — nouveau job build-android.
  • apps/web/android/app/build.gradle — bloc signingConfigs.
  • apps/web/android/.gitignore — s'assurer que release.keystore (le
    fichier décodé en CI) et tout *.jks/*.keystore local sont bien
    ignorés, jamais commités.
  • README — section "Application mobile (Android)" existante (ajoutée par
    #39) : documenter les 5 secrets requis et comment générer/renouveler le
    keystore.

Hors scope

  • Correction de release.yml pour Forgejo (voir la note en Contexte) —
    signalé, pas traité ici sur demande explicite de l'utilisateur.
  • iOS — toujours hors scope de #39 lui-même.
  • Publication automatique sur le Play Store — juste un artefact
    téléchargeable pour cette itération, pas une chaîne de distribution.

🤖 Filed by Claude Code

## Contexte Suite à [l'issue #39](https://git.dev.kyuno.fr/kyuno/batchCooking/issues/39) (portage mobile Capacitor) : le projet natif Android existe déjà (`apps/web/android/`, généré par `npx cap add android`, branche `feat/capacitor-android-native` — PR pas encore mergée au moment où ce ticket est écrit). Le seul suivi manuel resté documenté sur #39 est "build APK + test appareil, toujours manuel dans Android Studio (la CI ne build pas Android)". Le but ici : que ce build+test+empaquetage se fasse automatiquement à chaque push sur `main`, produisant un APK téléchargeable. **Trouvé en creusant le pipeline existant, hors scope de ce ticket (voir plus bas)** : `.github/workflows/release.yml` (déclenché par un tag `vX.Y.Z`) référence des actions GitHub sans URL complète (`actions/checkout@v4` au lieu de `https://github.com/actions/checkout@v4`, contrairement à `ci.yml` qui a bien été mis à jour pour l'hébergement Forgejo auto-hébergé) et utilise `softprops/action-gh-release`, une action de release spécifique à GitHub — très probablement cassé depuis la migration vers Forgejo. Signalé mais pas corrigé ici (décision prise avec l'utilisateur : ce ticket reste scopé à l'artefact sur `main`). ## Décisions validées avec l'utilisateur | Question | Décision | |---|---| | Signature de l'APK | **Release signée** (vrai keystore), pas un simple build debug | | Périmètre | **Juste l'artefact sur push main** — ne touche pas à `release.yml` | ## Conception — pourquoi le keystore ne peut pas être généré par la CI Android refuse d'installer la mise à jour d'une appli si elle est signée par une clé différente de la version déjà installée — le keystore doit donc être créé **une seule fois, à la main, hors CI**, puis rester stable pour toujours : ```bash keytool -genkeypair -v -keystore batchcooking-release.jks \ -alias batchcooking -keyalg RSA -keysize 2048 -validity 10000 ``` Le fichier + ses mots de passe deviennent des secrets du dépôt Forgejo — même précédent que `PORTAINER_WEBHOOK_URL` déjà utilisé dans `release.yml` : - `ANDROID_KEYSTORE_BASE64` (le `.jks` encodé en base64) - `ANDROID_KEYSTORE_PASSWORD` - `ANDROID_KEY_ALIAS` - `ANDROID_KEY_PASSWORD` - `MOBILE_API_URL` (l'URL publique de l'API — même valeur que `apps/web/.env.capacitor.example`'s `VITE_API_URL`, mais comme secret plutôt qu'en dur, cohérent avec "aucun secret avec une valeur par défaut") ## Conception — configuration Gradle de signature `apps/web/android/app/build.gradle` **n'a aujourd'hui aucune `signingConfig`** (vérifié : `buildTypes.release`, lignes 19-24, ne référence rien) — `assembleRelease` produirait donc un APK non signé en l'état. Ajouter, lu depuis l'environnement et jamais codé en dur : ```gradle android { signingConfigs { release { storeFile file(System.getenv("ANDROID_KEYSTORE_PATH") ?: "release.keystore") storePassword System.getenv("ANDROID_KEYSTORE_PASSWORD") keyAlias System.getenv("ANDROID_KEY_ALIAS") keyPassword System.getenv("ANDROID_KEY_PASSWORD") } } buildTypes { release { signingConfig signingConfigs.release minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } ``` ## Conception — le job CI **Dans `ci.yml` directement** (pas un nouveau fichier de workflow séparé) : un 6e job `build-android`, avec `needs: [lint, test, intent-service-test, build, e2e]` et `if: github.ref == 'refs/heads/main'`. C'est ce qui répond réellement à "testée" — GitHub/Forgejo Actions n'a pas de mécanisme natif pour qu'un job d'un *autre* fichier de workflow attende un run ailleurs, donc rester dans le même fichier est ce qui permet un vrai `needs:` bloquant plutôt qu'un simple déclenchement en parallèle qui ignorerait un échec des tests. ```yaml build-android: runs-on: ubuntu-latest needs: [lint, test, intent-service-test, build, e2e] if: github.ref == 'refs/heads/main' steps: - uses: https://github.com/actions/checkout@v4 - uses: pnpm/action-setup@v4 - uses: https://github.com/actions/setup-node@v4 with: { node-version: 22, cache: pnpm } - uses: https://github.com/actions/setup-java@v4 with: { distribution: temurin, java-version: "21" } # à confirmer contre l'AGP réel du projet généré - uses: https://github.com/android-actions/setup-android@v3 - run: pnpm install --frozen-lockfile - name: Build le bundle web pour Capacitor env: VITE_API_URL: ${{ secrets.MOBILE_API_URL }} run: pnpm --filter web build:capacitor - run: pnpm --filter web exec cap sync android - name: Décode le keystore de release env: ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} run: echo "$ANDROID_KEYSTORE_BASE64" | base64 -d > apps/web/android/app/release.keystore - name: Build l'APK signé working-directory: apps/web/android env: ANDROID_KEYSTORE_PATH: release.keystore ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} run: ./gradlew assembleRelease - uses: https://github.com/actions/upload-artifact@v4 with: name: batchcooking-release.apk path: apps/web/android/app/build/outputs/apk/release/app-release.apk ``` **"Testée" dans ce contexte précis** : aucune suite de tests instrumentés Android n'existe (zéro code natif au-delà de la coquille générée par Capacitor) — il n'y a rien de spécifiquement "mobile" à tester au sens Espresso/UI natif. "Testée" est donc satisfait par le blocage sur les 5 jobs existants (Mocha, Cypress, lint, build, pytest du service NLP) avant de packager quoi que ce soit — pas une suite native à inventer pour cette itération. ## Risque connu à documenter, pas à résoudre par avance Aucun build Android n'a jamais tourné sur ce runner Forgejo auto-hébergé (`act`) — contrairement à `lint`/`test`/`build`/`e2e`, rien ne prouve aujourd'hui qu'installer le SDK Android + builder via Gradle tienne dans un temps/espace disque raisonnable sur cette machine. À vérifier par un premier run réel plutôt que par une supposition ici ; si le SDK Android doit être retéléchargé à chaque run, `actions/cache` sur `~/.android/sdk`/`~/.gradle` (même esprit que le cache Cypress déjà présent dans `e2e`) sera probablement nécessaire pour un temps de build raisonnable sur les runs suivants. ## Fichiers à toucher - `.github/workflows/ci.yml` — nouveau job `build-android`. - `apps/web/android/app/build.gradle` — bloc `signingConfigs`. - `apps/web/android/.gitignore` — s'assurer que `release.keystore` (le fichier décodé en CI) et tout `*.jks`/`*.keystore` local sont bien ignorés, jamais commités. - README — section "Application mobile (Android)" existante (ajoutée par #39) : documenter les 5 secrets requis et comment générer/renouveler le keystore. ## Hors scope - Correction de `release.yml` pour Forgejo (voir la note en Contexte) — signalé, pas traité ici sur demande explicite de l'utilisateur. - iOS — toujours hors scope de #39 lui-même. - Publication automatique sur le Play Store — juste un artefact téléchargeable pour cette itération, pas une chaîne de distribution. --- 🤖 Filed by [Claude Code](https://claude.com/claude-code)
kyuno added the
enhancement
planned
labels 2026-09-06 10:02:35 +02:00
kyuno closed this issue 2026-09-06 14:44:37 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: kyuno/batchCooking#64
No description provided.