Aller au contenu principal

Déployer automatiquement une app Flutter

remarque

Ce cookbook couvre la mise en place complète des pipelines CI/CD pour une application Flutter. Pour le contexte global (architecture, diagrammes, FAQ), voir la page principale.

Objectif : configurer des pipelines Bitbucket manuels pour déployer une app Flutter sur Google Play Store (Android) et TestFlight (iOS via Xcode Cloud), en un seul clic.

Flutter : 4h

Pré-requis

  1. Dépôt Bitbucket avec Pipelines activés
  2. App Flutter configurée avec des flavors (preprod / prod) via flutter_flavorizr
  3. Compte Google Play Console avec l'app créée
  4. Compte Apple Developer Program avec accès à App Store Connect et Xcode Cloud
  5. Fastlane (installé dans le pipeline via gem install)
  6. Ruby (présent dans l'image Docker CI)

Image Docker CI

image: ghcr.io/cirruslabs/flutter:3.38.5

Contient Flutter, Dart, Android SDK, Ruby et les outils de base.


Vue d'ensemble technique

ÉlémentRôle
bitbucket-pipelines.ymlDéfinition de tous les pipelines CI/CD
MakefileCommandes de build et deploy partagées entre local et CI
scripts/asc.rbClient API App Store Connect (génère JWT, déclenche Xcode Cloud)
ios/ci_scripts/ci_post_clone.shInstalle Flutter et CocoaPods dans Xcode Cloud

Partie 1 — Configuration initiale des secrets

Avant le premier déploiement, tous les secrets doivent être encodés en base64 et ajoutés dans Bitbucket.

1.1 Android — Compte de service Google Play

Créer le compte de service

  1. Aller sur Google Cloud Console
  2. Sélectionner (ou créer) le projet lié à l'app → API et services → Bibliothèque → activer Google Play Android Developer API

Google Play Android Developer API activée

  1. IAM et administration → Comptes de service → Créer un compte de service
    • Nom : release-account (ou similaire)
    • Rôle : aucun (les permissions sont données côté Play Console)

Créer un compte de service

  1. Cliquer sur le compte créé → onglet ClésAjouter une clé → JSON

Ajouter une clé JSON

  1. Télécharger le fichier .json → le renommer google-play-key.json
  2. Placer le fichier dans private_keys/google-play-key.json (ignoré par git)

Donner accès dans la Play Console

  1. Aller sur Google Play Console -> Paramètres → Utilisateurs et autorisations
  2. Onglet Autorisations de l'application → cocher l'app
  3. Droits nécessaires : Gestionnaire des versions (Release manager)
  4. Inviter l'utilisateur et confirmer

Inviter l'utilisateur dans la Play Console

Encoder les secrets de signature

Répéter l'opération pour chaque environnement :

# ── Preprod ──
echo "=== ANDROID_KEYSTORE_B64_PREPROD ==="
base64 -i android/upload-key-preprod.keystore | tr -d '\n'
echo

echo "=== ANDROID_KEY_PROPERTIES_B64_PREPROD ==="
base64 -i android/key-preprod.properties | tr -d '\n'
echo

echo "=== GOOGLE_PLAY_JSON_KEY_B64_PREPROD ==="
base64 -i private_keys/google-play-key-preprod.json | tr -d '\n'
echo

1.2 iOS — Clé API App Store Connect

Créer la clé API

  1. Aller sur App Store Connect → Clés d'API
  2. Générer une clé API
    • Nom : CI Xcode Cloud preprod (ou similaire)
    • Accès : Admin (nécessaire pour déclencher des builds Xcode Cloud)
  3. Noter l'Issuer ID (affiché en haut de la page, commun à toutes les clés du compte)
  4. Noter l'ID de la clé
  5. Télécharger la clé (AuthKey_XXXXXXXXXX.p8) — elle ne peut être téléchargée qu'une seule fois

Page clés API App Store Connect avec Issuer ID

Clés séparées par environnement

Il est recommandé de créer une clé distincte pour preprod et pour prod. Répéter la démarche ci-dessus pour chaque environnement.

Encoder la clé .p8

# Preprod
echo "=== ASC_PRIVATE_KEY_B64_PREPROD ==="
base64 -i private_keys/AuthKey_<PREPROD_KEY_ID>.p8 | tr -d '\n'
echo

1.3 Ajouter toutes les variables dans Bitbucket

Dépôt → Paramètres → Variables du dépôt :

Variables Bitbucket configurées

Android

VariableSourceSécurisée
GOOGLE_PLAY_JSON_KEY_B64_PREPRODCompte de service Google Cloud preprod (JSON encodé base64)
ANDROID_KEYSTORE_B64_PREPRODKeystore de signature preprod encodé base64
ANDROID_KEY_PROPERTIES_B64_PREPRODFichier key.properties preprod encodé base64

iOS

VariableSourceSécurisée
ASC_KEY_ID_PREPRODID de la clé API App Store Connect (preprod)
ASC_ISSUER_ID_PREPRODIssuer UUID App Store Connect (preprod)
ASC_PRIVATE_KEY_B64_PREPRODClé .p8 preprod encodée base64
ASC_WORKFLOW_ID_PREPRODID du workflow Xcode Cloud preprod -> ajouté + tard

Variables optionnelles

VariableUsage
ANDROID_TRACKTrack Play Store : internal (défaut), alpha, beta, production
attention

Les variables marquées sécurisées ne seront plus visibles après enregistrement dans Bitbucket. Note-les dans un gestionnaire de mots de passe si nécessaire.


Partie 2 — Android : Google Play Store via Fastlane

2.1 Principe

Le pipeline Linux build l'AAB (Android App Bundle) signé puis l'upload directement sur le Play Store via fastlane supply.

2.2 Signature

La signature utilise upload-key.keystore configuré dans android/key.properties :

android/key.properties
storePassword=***
keyPassword=***
keyAlias=upload-key
storeFile=upload-key.keystore

Ces fichiers ne sont pas commités (.gitignore). En CI, ils sont recréés depuis les variables base64.

2.3 Flavors et Package IDs

FlavorPackage IDPlay Store
preprodfr.doing.starterTrack internal
prodfr.doing.starterTrack internal (ou production)

2.4 Étape pipeline : décodage des secrets

bitbucket-pipelines.yml (extrait Android preprod)
- step: &deploy-android-preprod
name: '🤖 Android preprod → Play Store (internal)'
image: ghcr.io/cirruslabs/flutter:3.38.5
size: 2x # 8 Go RAM (Gradle + Flutter dépassent 4 Go)
caches:
- gradle
- android-sdk
- pub-cache
script:
- gem install fastlane --no-document -q
- mkdir -p private_keys android
- printf '%s' "$GOOGLE_PLAY_JSON_KEY_B64_PREPROD" | base64 -d > private_keys/google-play-key.json
- printf '%s' "$ANDROID_KEYSTORE_B64_PREPROD" | base64 -d > android/upload-key.keystore
- printf '%s' "$ANDROID_KEY_PROPERTIES_B64_PREPROD" | base64 -d > android/key.properties
- make deploy-android-preprod FLUTTER=flutter DART=dart GOOGLE_PLAY_JSON_KEY=$(pwd)/private_keys/google-play-key.json
artifacts:
- build/app/outputs/bundle/preprodRelease/**
Mémoire du conteneur

Les steps Android doivent utiliser size: 2x (8 Go RAM). Gradle + Flutter dépassent la limite par défaut de 4 Go. Vérifie aussi que android/gradle.properties contient -Xmx ≤ 3g.

2.5 Commande Makefile

Makefile (extrait)
deploy-android-preprod: _check-fastlane-supply
# 1. Récupère le dernier build number depuis Play Store et incrémente
@LATEST=$$(fastlane run google_play_track_version_codes \
json_key:"$(GOOGLE_PLAY_JSON_KEY)" \
package_name:"fr.doing.starter" \
track:"$(ANDROID_TRACK)" 2>/dev/null \
| grep -Eo '[0-9]+' | sort -n | tail -1 || echo "0"); \
NEW=$$(($$LATEST + 1)); \
# 2. Build avec le nouveau build number
$(FLUTTER) build appbundle --build-number=$$NEW \
--flavor preprod --dart-define=FLAVOR=preprod \
-t "lib/main.dart" --release; \
# 3. Upload vers Play Store
fastlane supply \
--aab build/app/outputs/bundle/preprodRelease/app-preprod-release.aab \
--json_key "$(GOOGLE_PLAY_JSON_KEY)" \
--package_name fr.doing.starter \
--track $(ANDROID_TRACK) \
--release_status $(ANDROID_RELEASE_STATUS) \
--skip_upload_apk true \
--skip_upload_metadata true \
--skip_upload_images true \
--skip_upload_screenshots true

_check-fastlane-supply:
@command -v fastlane >/dev/null 2>&1 \
|| (echo "$(RED)fastlane introuvable. Installer avec : gem install fastlane$(NC)" && exit 1)
@[ -f "$(GOOGLE_PLAY_JSON_KEY)" ] \
|| (echo "$(RED)Clé introuvable : $(GOOGLE_PLAY_JSON_KEY)\nPoser google-play-key.json à la racine ou définir GOOGLE_PLAY_JSON_KEY=chemin/vers/key.json$(NC)" && exit 1)

2.6 Build number automatique

Le build number n'est pas géré manuellement. Le Makefile interroge Fastlane pour récupérer le dernier build number présent sur le Play Store et l'incrémente automatiquement à chaque déploiement :

Dernier build number Play Store  →  +1  →  build Flutter  →  upload

Il n'est donc pas nécessaire de modifier pubspec.yaml avant chaque déploiement Android.

2.7 Premier déploiement Android

Pour la première publication sur le Play Store :

  1. Compléter la fiche de l'app dans la Play Console (screenshots, description, etc.)
  2. Lancer le pipeline avec ANDROID_RELEASE_STATUS=draft pour le premier upload
  3. Une fois la fiche validée, les uploads suivants utilisent completed (valeur par défaut)

Partie 3 — iOS : TestFlight via Xcode Cloud

3.1 Pourquoi Xcode Cloud ?

Bitbucket Pipelines n'offre pas de runners macOS. Or, un Mac est indispensable pour builder une app iOS (Xcode, codesigning, etc.). Xcode Cloud est inclus gratuitement avec l'Apple Developer Program (25h/mois).

Le pipeline Bitbucket ne build rien pour iOS — il se contente de déclencher un build Xcode Cloud via l'API App Store Connect.

3.2 Configurer le workflow Xcode Cloud

Connecter le dépôt Bitbucket

  1. Ouvrir le projet dans Xcode → Integrate → Xcode Cloud → Create Workflow (ou via le menu Source Control)

Xcode — Source Control → Create Workflow Attention, bien créer le workflow depuis Runner.xcworkspace (et non Runner.xcodeproj) pour que les pods soient inclus dans le graphe de dépendances. Changer cela sur appstoreconnect dans "Projet ou espace de travail" si nécessaire. 2. Xcode demande de connecter un SCM → choisir Bitbucket 3. S'authentifier et autoriser l'accès au dépôt

Créer le workflow

Dans la configuration du workflow, sur "edit workflow", utiliser les paramètres suivants (il est parfois nécessaire de supprimer la valeur par défaut, puis d'appuyer sur +) :

ParamètreValeur
Nompreprod archive (ou prod archive)
Schemepreprod (ou prod)
ActionsArchive → iOS / App store
Start ConditionsManual only (déclenché par le pipeline Bitbucket)

Configuration du workflow -> App Store Connect

Dans App Store Connect → Xcode Cloud → Manage Workflows → cliquer sur le workflow → copier l'ID affiché dans l'URL (format XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX).

Ajouter cet ID comme variable ASC_WORKFLOW_ID_PREPROD dans Bitbucket.

Vous pourrez ensuite modifier votre workflow pour ajouter les paramètres suivants depuis App Store Connect (Edit Workflow) :

ParamètreValeur
Build Number⚠️ "auto-increment"
Post-ActionsTestFlight Distribution → sélectionner les groupes de testeurs

3.3 Script asc.rb — Client API App Store Connect

Script Ruby pur (pas de gem externe) qui :

  • Génère des tokens JWT ES256 signés avec la clé .p8
  • Communique avec l'API REST App Store Connect v1

Commandes disponibles

# Lister les workflows Xcode Cloud d'une app
ruby scripts/asc.rb list-workflows fr.doing.starter

# Lister les branches connues de Xcode Cloud pour un workflow
ruby scripts/asc.rb list-refs <workflow_id>

# Résoudre un nom de branche en ref ID
ruby scripts/asc.rb resolve-ref <workflow_id> develop

# Déclencher un build en résolvant automatiquement la branche
ruby scripts/asc.rb trigger-branch <workflow_id> develop

Recherche de la clé .p8

Le script cherche AuthKey_<KEY_ID>.p8 dans cet ordre :

  1. ./private_keys/
  2. ~/.appstoreconnect/private_keys/
  3. ~/private_keys/

Variables par environnement

Dans le pipeline, les variables ASC_KEY_ID et ASC_ISSUER_ID sont exportées directement depuis les variables Bitbucket spécifiques à chaque env :

# Preprod
export ASC_KEY_ID="$ASC_KEY_ID_PREPROD" ASC_ISSUER_ID="$ASC_ISSUER_ID_PREPROD"

En local (via Makefile), le mécanisme utilise ASC_ENV avec un fallback :

ASC_ENV=PREPROD → ASC_KEY_ID_PREPROD (si défini) → sinon ASC_KEY_ID
ASC_ENV=PROD → ASC_KEY_ID_PROD (si défini) → sinon ASC_KEY_ID

3.4 Étape pipeline : déclenchement Xcode Cloud

bitbucket-pipelines.yml (extrait iOS preprod)
- step: &deploy-ios-preprod
name: '🍎 iOS preprod → Xcode Cloud'
image: ghcr.io/cirruslabs/flutter:3.38.5
script:
- mkdir -p private_keys
- printf '%s' "$ASC_PRIVATE_KEY_B64_PREPROD" | base64 -d > "private_keys/AuthKey_${ASC_KEY_ID_PREPROD}.p8"
- export ASC_KEY_ID="$ASC_KEY_ID_PREPROD" ASC_ISSUER_ID="$ASC_ISSUER_ID_PREPROD"
- ruby scripts/asc.rb trigger-branch "$ASC_WORKFLOW_ID_PREPROD" "$BITBUCKET_BRANCH"

3.5 Résolution dynamique de branche

Le pipeline passe $BITBUCKET_BRANCH à asc.rb trigger-branch. Le script :

  1. Récupère le workflow → son repository ID
  2. Liste les gitReferences du repository
  3. Cherche la branche par nom exact
  4. Si trouvée → déclenche le build sur cette branche
  5. Si non trouvée (branche pas encore synchronisée) → déclenche sur la branche par défaut du workflow (avec un warning)

3.6 Scripts CI Xcode Cloud

Les scripts doivent être dans ios/ci_scripts/ (relativement au .xcworkspace).

ci_post_clone.sh — Après le clone

Ce script prépare l'environnement Flutter dans Xcode Cloud :

ios/ci_scripts/ci_post_clone.sh
#!/bin/sh
set -e

FLUTTER_VERSION="3.38.5"
FLUTTER_DIR="$HOME/flutter"
REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"

# ─── 1. Installer Flutter (détection arm64 / x86_64) ─────────────────────────

echo "==> Installation de Flutter $FLUTTER_VERSION"

ARCH=$(uname -m)
if [ "$ARCH" = "arm64" ]; then
FLUTTER_URL="https://storage.googleapis.com/flutter_infra_release/releases/stable/macos/flutter_macos_arm64_${FLUTTER_VERSION}-stable.zip"
else
FLUTTER_URL="https://storage.googleapis.com/flutter_infra_release/releases/stable/macos/flutter_macos_${FLUTTER_VERSION}-stable.zip"
fi

curl -fsSL -o /tmp/flutter.zip "$FLUTTER_URL"
unzip -q /tmp/flutter.zip -d "$HOME"
rm /tmp/flutter.zip

export PATH="$FLUTTER_DIR/bin:$PATH"

echo "==> $(flutter --version | head -1)"

# ─── 2. Désactiver la télémétrie ─────────────────────────────────────────────

flutter config --no-analytics --suppress-analytics 2>/dev/null || true

# ─── 3. Générer Generated.xcconfig ───────────────────────────────────────────

cd "$REPO_ROOT"
echo "==> flutter pub get (répertoire : $REPO_ROOT)"
flutter pub get

# flutter pub get ne génère pas toujours Generated.xcconfig en CI.
# On le crée manuellement si absent — suffisant pour pod install.
mkdir -p ios/Flutter
if [ ! -f "ios/Flutter/Generated.xcconfig" ]; then
echo "==> Generated.xcconfig absent — création manuelle"
cat > ios/Flutter/Generated.xcconfig << XCCONFIG
// This is a generated file; do not edit or check into version control.
FLUTTER_ROOT=${FLUTTER_DIR}
FLUTTER_APPLICATION_PATH=${REPO_ROOT}
COCOAPODS_PARALLEL_CODE_SIGN=true
FLUTTER_TARGET=lib/main.dart
FLUTTER_BUILD_DIR=build
FLUTTER_BUILD_MODE=release
DART_DEFINES=
DART_OBFUSCATION=false
TRACK_WIDGET_CREATION=false
TREE_SHAKE_ICONS=false
PACKAGE_CONFIG=.dart_tool/package_config.json
XCCONFIG
fi

echo "==> Generated.xcconfig :"
cat ios/Flutter/Generated.xcconfig

# ─── 4. Installer les dépendances CocoaPods ──────────────────────────────────

echo "==> pod install"
cd "$REPO_ROOT/ios"
pod install
Points critiques du script
  • Ne pas utiliser sudo — Xcode Cloud n'a pas de terminal interactif, sudo échoue silencieusement.
  • Ne pas ajouter --repo-update à pod install — le Source CDN CocoaPods expire en SSL sur ces machines.
  • Ne pas activer flutterfire_cli sans Firebase — l'activation télécharge 200+ packages et provoque un timeout de 15 min.
  • Détecter l'architecture avec uname -m — les machines Xcode Cloud peuvent être arm64 ou x86_64, une URL fixe échoue sur l'une des deux.

ci_pre_xcodebuild.sh — Avant la compilation

Ce script synchronise le build number Xcode avec celui de pubspec.yaml :

ios/ci_scripts/ci_pre_xcodebuild.sh
#!/bin/sh
set -e

REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"

# Extraire le build number depuis la ligne "version: 1.0.0+1" de pubspec.yaml
BUILD_NUMBER=$(grep '^version:' "$REPO_ROOT/pubspec.yaml" | sed 's/.*+//' | tr -d '[:space:]')

if [ -z "$BUILD_NUMBER" ]; then
echo "warning: build number introuvable dans pubspec.yaml — conservation du numéro actuel"
exit 0
fi

echo "==> Build number pubspec.yaml : $BUILD_NUMBER"

cd "$REPO_ROOT/ios"
agvtool new-version -all "$BUILD_NUMBER"

echo "==> CURRENT_PROJECT_VERSION mis à jour : $BUILD_NUMBER"
Workflow Xcode Cloud — utiliser le workspace

Xcode Cloud doit utiliser -workspace Runner.xcworkspace (et non -project Runner.xcodeproj) pour que CocoaPods soit inclus dans le graphe de dépendances. Si le workflow a été créé avant que pod install ne soit exécuté, il faut ouvrir le workflow dans Xcode → Product → Xcode Cloud → Manage Workflows → éditer → sauvegarder pour forcer la re-détection du workspace.


Partie 4 — Le fichier bitbucket-pipelines.yml complet

Voici la structure complète du fichier de pipelines :

bitbucket-pipelines.yml
definitions:
caches:
pub-cache: ~/.pub-cache
android-sdk: /opt/android-sdk

steps:
# ─── Mobile: Android ─────────────────────────────────────────────────────
- step: &deploy-android-preprod
name: '🤖 Android preprod → Play Store (internal)'
image: ghcr.io/cirruslabs/flutter:3.38.5
size: 2x
caches:
- gradle
- android-sdk
- pub-cache
script:
- gem install fastlane --no-document -q
- mkdir -p private_keys android
- printf '%s' "$GOOGLE_PLAY_JSON_KEY_B64_PREPROD" | base64 -d > private_keys/google-play-key.json
- printf '%s' "$ANDROID_KEYSTORE_B64_PREPROD" | base64 -d > android/upload-key.keystore
- printf '%s' "$ANDROID_KEY_PROPERTIES_B64_PREPROD" | base64 -d > android/key.properties
- make deploy-android-preprod FLUTTER=flutter DART=dart GOOGLE_PLAY_JSON_KEY=$(pwd)/private_keys/google-play-key.json
artifacts:
- build/app/outputs/bundle/preprodRelease/**

# ─── Mobile: iOS (Xcode Cloud) ──────────────────────────────────────────
- step: &deploy-ios-preprod
name: '🍎 iOS preprod → Xcode Cloud'
image: ghcr.io/cirruslabs/flutter:3.38.5
script:
- mkdir -p private_keys
- printf '%s' "$ASC_PRIVATE_KEY_B64_PREPROD" | base64 -d > "private_keys/AuthKey_${ASC_KEY_ID_PREPROD}.p8"
- export ASC_KEY_ID="$ASC_KEY_ID_PREPROD" ASC_ISSUER_ID="$ASC_ISSUER_ID_PREPROD"
- ruby scripts/asc.rb trigger-branch "$ASC_WORKFLOW_ID_PREPROD" "$BITBUCKET_BRANCH"

pipelines:
# Tests automatiques sur chaque PR
pull-requests:
'**':
- step:
name: Tests
image: ghcr.io/cirruslabs/flutter:3.38.5
script:
- flutter pub get
- flutter analyze
- flutter test

custom:
deploy-android-preprod:
- step: *deploy-android-preprod
deploy-ios-preprod:
- step: *deploy-ios-preprod

deploy-all-preprod:
- step: *deploy-android-preprod
- step: *deploy-ios-preprod

Dépannage

Android

ProblèmeCauseSolution
"The Android App Bundle was signed with the wrong key"Variables ANDROID_KEYSTORE_B64_* / ANDROID_KEY_PROPERTIES_B64_* ne correspondent pas au keystore Play StoreRé-encoder les bons fichiers avec base64 -i
"Only releases with status draft may be created on draft app"L'app n'a jamais été publiée sur le Play Store — elle est encore au statut draft et n'accepte que release_status=draftVoir § 2.7 Premier déploiement Android : ajouter ANDROID_RELEASE_STATUS=draft au lancement ou via la variable Makefile
"Container exceeded memory limit"Build Gradle dépasse la RAMVérifier size: 2x dans le pipeline + -Xmx ≤ 3g dans gradle.properties
"Gradle build daemon disappeared unexpectedly"Même causeIdem ci-dessus

iOS

ProblèmeCauseSolution
"Post-Clone script not found"Scripts au mauvais emplacementPlacer dans ios/ci_scripts/ (pas ci_scripts/ à la racine)
"Generated.xcconfig: No such file or directory"flutter pub get non exécutéVérifier ci_post_clone.sh dans les logs Xcode Cloud
"curl: (56) URL returned error: 404"URL Flutter obsolèteVérifier le format dans ci_post_clone.sh (.zip et non .tar.xz)
"Bad CPU type in executable"Flutter arm64 téléchargé sur machine x86_64 (ou inversement)Utiliser la détection uname -m dans ci_post_clone.sh pour choisir l'archive correcte
"sudo: a password is required"sudo utilisé dans un script Xcode Cloud (pas de terminal interactif)Supprimer tout appel sudo — ne pas utiliser sudo ln -sf pour Flutter
Post-clone timeout (≈15 min, pas d'erreur visible)flutter pub global activate flutterfire_cli télécharge 200+ packages silencieusementSupprimer cette ligne si le projet n'utilise pas Firebase
"Couldn't determine repo type — Operation timed out SSL"pod install --repo-update tente une mise à jour du CDN CocoaPods qui expire en SSLUtiliser pod install sans --repo-update
"Module 'connectivity_plus' not found" (exit code 65)Xcode Cloud archive avec -project Runner.xcodeproj au lieu de -workspace → pods exclus du grapheOuvrir Xcode → Product → Xcode Cloud → Manage Workflows, éditer le workflow et sauvegarder pour forcer la re-détection de Runner.xcworkspace
Build uniquement dans "Internes"Pas de post-action distributionAjouter TestFlight Distribution dans le workflow Xcode Cloud
"'' could not be added to xcode cloud"500 côté AppleRelancer la commande — erreur intermittente sans solution connue

Général

ProblèmeCauseSolution
"Variable XXX manquante"Variable non configuréeVérifier Dépôt → Paramètres → Variables dans Bitbucket

Tests de validation

PipelineCommande BitbucketRésultat attendu
Android preprodCustom: deploy-android-preprodAAB uploadé sur Play Store track internal
iOS preprodCustom: deploy-ios-preprodBuild Xcode Cloud déclenché → TestFlight
Tests PRAutomatique sur PRflutter analyze + flutter test passent

Checklist

PointOK
Variables Bitbucket Android configurées (GOOGLE_PLAY_JSON_KEY_B64_*, ANDROID_KEYSTORE_B64_*, ANDROID_KEY_PROPERTIES_B64_*)
Variables Bitbucket iOS configurées (ASC_KEY_ID_*, ASC_ISSUER_ID_*, ASC_PRIVATE_KEY_B64_*, ASC_WORKFLOW_ID_*)
Compte de service Google Play créé et autorisé (preprod + prod)
Clé API App Store Connect créée et encodée (preprod + prod)
Workflow Xcode Cloud créé avec Build Number → Auto-increment
Workflow ID récupéré et ajouté dans Bitbucket
Script ios/ci_scripts/ci_post_clone.sh présent et exécutable
Script ios/ci_scripts/ci_pre_xcodebuild.sh présent et exécutable
Workflow Xcode Cloud sauvegardé depuis Xcode après pod install (pour que l'archive utilise -workspace et non -project)
Premier deploy Android fait avec ANDROID_RELEASE_STATUS=draft
Pipeline Android testé (≈10 min)
Pipeline iOS testé (≈13 min total)

FAQ spécifique Flutter

Pourquoi des custom pipelines manuels et pas des triggers automatiques ?

Pour garder le contrôle sur le moment du déploiement. Les tests automatiques tournent sur chaque PR, mais le déploiement sur les stores est déclenché volontairement après validation.

Pourquoi Xcode Cloud pour iOS au lieu d'un runner macOS ?

Bitbucket Pipelines n'offre pas de runners macOS. Xcode Cloud est inclus gratuitement dans l'Apple Developer Program (25h/mois) et gère le build, la signature et l'upload vers TestFlight.

Pourquoi size: 2x pour Android seulement ?

Gradle + Flutter consomment beaucoup de RAM (> 4 Go). Le pipeline iOS ne build rien (simple appel API REST vers Xcode Cloud). Seul Android nécessite les 8 Go.

Comment passer d'un track internal à production pour Android ?

Ajouter la variable ANDROID_TRACK=production au lancement du pipeline, ou utiliser make deploy-android-prod ANDROID_TRACK=production.

Comment ajouter un nouveau groupe de testeurs TestFlight ?

Dans App Store Connect → Xcode Cloud → Workflow → modifier les Post-Actions → TestFlight Distribution → ajouter le groupe souhaité.