Introduction
Publiez-vous encore vos applications Android en cliquant manuellement sur Generate Signed Bundle dans Android Studio, puis en ouvrant la Play Console pour glisser-déposer l'AAB ?
J'ai procédé ainsi pendant un moment, mais à mesure que la fréquence de publication augmentait, j'ai rencontré les problèmes suivants :
- Les builds cassent à cause des différences d'environnement local (incompatibilités de version JDK, problèmes de cache Gradle)
- La gestion du Keystore devient silo (n'existe que sur le Mac d'un développeur spécifique)
- Le processus de publication dépend de la documentation, ce qui entraîne des erreurs humaines
Pour résoudre tous ces problèmes d'un coup, j'ai automatisé l'ensemble du flux – build, signature et upload vers Google Play Console – avec GitHub Actions. Voici la procédure détaillée.
Vue d'ensemble
Le pipeline que nous allons construire se présente comme suit :
flowchart TD
A["Push git tag v1.0.0"]
B["GitHub Actions d\u00e9clench\u00e9"]
C["Configuration JDK et restauration du cache Gradle"]
D["Restauration du Keystore depuis les Secrets"]
E["Build et signature de l'AAB avec ./gradlew bundleRelease"]
F["D\u00e9ploiement vers le test interne via r0adkll/upload-google-play"]
G["Google re\u00e7oit, re-signe avec la cl\u00e9 de signature d'app et distribue aux utilisateurs"]
A --> B --> C --> D --> E --> F --> GLes termes « AAB » et « clé d'upload » sont expliqués dans la section suivante.
3 concepts clés à comprendre en amont
Avant d'entrer dans les étapes concrètes, clarifions les termes clés utilisés dans cet article. Si ces notions restent floues, la partie Play App Signing peut rapidement prêter à confusion.
APK vs AAB
| APK (Android Package Kit) | AAB (Android App Bundle) | |
|---|---|---|
| Contenu | Package universel combinant tous les ABI, densités d'écran et ressources linguistiques en un seul fichier | Artefact de build avant l'optimisation pour les appareils spécifiques |
| Taille du fichier | Tend à être volumineuse car tout est inclus | Google Play génère des Split APKs pour la distribution, réduisant la taille de téléchargement des utilisateurs d'environ 15 % en moyenne |
| Usage principal | Distribution directe / distribution interne / stores tiers | Distribution sur le Google Play Store (obligatoire pour les nouvelles applications depuis août 2021) |
| Tâche Gradle | ./gradlew assembleRelease |
./gradlew bundleRelease |
En résumé : « APK = produit fini qui s'installe sur l'appareil de l'utilisateur » et « AAB = blueprint soumis à Google Play ». Google Play reçoit l'AAB et assemble l'APK adapté à l'appareil de chaque utilisateur.
Cet article visant la distribution sur le Play Store, nous utiliserons l'AAB (bundleRelease). Pour la distribution interne ou d'autres cas nécessitant un APK, utilisez assembleRelease.
Clé d'upload vs clé de signature d'application
Play App Signing utilise deux types de clés de signature :
| Type de clé | Qui gère ? | Usage |
|---|---|---|
| Clé d'upload (Upload Key) | Développeur | Signe l'AAB lors de l'upload vers la Play Console |
| Clé de signature d'application (App Signing Key) | Signe l'APK final distribué aux utilisateurs depuis le Play Store |
Autrement dit, la Play Console vérifie si l'AAB est signé avec la bonne clé d'upload, c'est pourquoi les développeurs doivent signer avec leur propre clé d'upload = Keystore. Ce que Google fait pour vous, c'est uniquement la re-signature avec la clé de signature d'application lors de la distribution.
flowchart TD
Dev["D\u00e9veloppeur"] -->|"Signe avec la cl\u00e9 d'upload"| AAB["AAB"]
AAB --> Play["Play Console"]
Play --> Verify["Google v\u00e9rifie la cl\u00e9 d'upload"]
Verify --> Resign["Google re-signe avec la cl\u00e9 de signature d'app"]
Resign --> User["Utilisateur"]Même avec Play App Signing, le fait que « vous deviez toujours créer votre propre Keystore » n'a pas changé. Ce qui a changé, c'est le filet de sécurité : « vous pouvez demander une réinitialisation si vous perdez votre clé d'upload ».
Signature de débogage vs signature release
Quand vous avez appuyé sur Run pour la première fois dans Android Studio sans aucune configuration, une APK a été construite et s'est exécutée sur votre appareil parce que AGP (Android Gradle Plugin) génère automatiquement ~/.android/debug.keystore et signe automatiquement les builds de débogage.
| Signature de débogage | Signature release | |
|---|---|---|
| Keystore | ~/.android/debug.keystore (généré automatiquement par AGP) |
Créé et géré par vous (release.keystore dans cet article) |
| Mot de passe | Fixe : android |
Défini par vous |
| Alias | Fixe : androiddebugkey |
Défini par vous |
| Expiration | ~30 ans | Défini par vous (~27 ans dans cet article) |
| Usage | Tests de développement / ./gradlew assembleDebug |
Distribution Play Store / ./gradlew bundleRelease |
| Gestion Git | Non nécessaire (OK si présent par machine) | Conserver en sécurité (réinitialisable via Play App Signing en cas de perte) |
Deux propriétés importantes :
- La Play Console refuse catégoriquement les AAB signées avec la signature de débogage
→ La CI a besoin d'unrelease.keystoreséparé - La signature de débogage et la signature release sont complètement indépendantes
→ Ajouter la configuration de signature release pour la CI n'a aucun effet sur les appels locaux./gradlew assembleDebug
Le signingConfigs.create("release") conditionnel dans les sections 5 et 6 existe précisément pour exploiter ces propriétés – créant un état où les développeurs sans la clé peuvent quand même exécuter des builds de débogage normalement.
Prérequis
- Application Android (utilisant
build.gradleoubuild.gradle.kts) - Dépôt GitHub
- Compte développeur Google Play Console
- La première publication de l'application doit déjà avoir été effectuée manuellement dans la Play Console (la première création via API n'est pas possible)
- Play App Signing doit être activé pour l'application cible
Pour les applications créées après août 2021, Play App Signing est automatiquement activé, donc aucune étape spéciale n'est nécessaire. Pour les applications plus anciennes créées avant cela avec la méthode legacy, vous devrez d'abord migrer vers Play App Signing (traité dans la section 7-4).
1. Créer la clé d'upload (Keystore)
Générez le Keystore localement pour signer l'AAB. Dans une configuration Play App Signing, cette clé fonctionne comme la « clé d'upload » (= votre identité lors de la soumission de l'AAB à la Play Console). La livraison finale aux utilisateurs est signée par la « clé de signature d'application » de Google, donc même si vous perdez cette clé, vous pouvez demander une réinitialisation depuis la Play Console (détails en 7-3).
Cela dit, perdre cette clé en exploitation quotidienne bloquera les publications jusqu'à la fin de la réinitialisation, alors conservez-la en sécurité.
# macOS / Linux
keytool -genkey -v \
-keystore release.keystore \
-alias my-release-key \
-keyalg RSA \
-keysize 2048 \
-validity 10000
Sous Windows (PowerShell / Invite de commandes), la continuation de ligne avec backslash ne fonctionne pas, exécutez donc sur une seule ligne ou utilisez le backtick ` pour les sauts de ligne dans PowerShell.
keytool -genkey -v `
-keystore release.keystore `
-alias my-release-key `
-keyalg RSA `
-keysize 2048 `
-validity 10000
Saisissez le mot de passe, le nom, l'organisation, etc. de manière interactive.
2. Encoder le Keystore en Base64
GitHub Secrets ne peut pas stocker de binaire directement, convertissez-le donc en chaîne Base64. Le workflow supprime les sauts de ligne avec tr -d '\n\r' lors du décodage, mais une sortie sans sauts de ligne est plus sûre.
macOS
base64 -i release.keystore -o release.keystore.base64
cat release.keystore.base64 | pbcopy
Linux
base64 -w 0 release.keystore > release.keystore.base64
cat release.keystore.base64 | xclip -selection clipboard
Windows (PowerShell)
[Convert]::ToBase64String([IO.File]::ReadAllBytes("release.keystore")) `
| Set-Clipboard
Pour écrire dans un fichier :
[Convert]::ToBase64String([IO.File]::ReadAllBytes("release.keystore")) `
| Out-File -Encoding ascii -NoNewline release.keystore.base64
Windows (Invite de commandes)
certutil fonctionne mais ajoute des en-têtes, pieds de page et sauts de ligne qui doivent être supprimés.
certutil -encode release.keystore release.keystore.base64
Supprimez les lignes -----BEGIN CERTIFICATE----- et -----END CERTIFICATE----- ainsi que tous les sauts de ligne du fichier de sortie. L'utilisation de PowerShell est recommandée.
Copiez la chaîne dans le presse-papiers et collez-la dans GitHub Secrets à l'étape suivante.
3. Créer un compte de service pour Google Play Console
Les uploads automatisés vers la Play Console nécessitent un compte de service Google Cloud.
3-1. Activer l'API Google Play Android Developer
D'abord, activez l'API du côté GCP. Ignorer cette étape entraînera une erreur 403 plus tard, donc faites-le en premier.
- Accédez à Google Play Android Developer API
- Cliquez sur Activer
3-2. Créer un compte de service dans la Google Cloud Console
- Accédez à Google Cloud Console
- Créez un projet (ou sélectionnez-en un existant)
- Dans IAM et administration → Comptes de service, créez-en un nouveau. N'attribuez pas de rôles IAM (les permissions sont gérées du côté Play Console)
- Après la création, sélectionnez Ajouter une clé → Créer une nouvelle clé → JSON et téléchargez le fichier JSON
3-3. Accorder les permissions dans la Play Console
- Ouvrez la Play Console
- Ouvrez Utilisateurs et permissions, cliquez sur Inviter de nouveaux utilisateurs
- Saisissez l'adresse e-mail du compte de service créé à l'étape 3-2
- Dans l'onglet Permissions d'application, ajoutez l'application cible et accordez les permissions suivantes :
- Publier vers les tracks de test — requis pour la distribution vers le track de test interne
- Afficher les informations de l'application et télécharger des rapports en masse — permission de lecture
4. Enregistrer les valeurs dans GitHub Secrets
Depuis Settings → Secrets and variables → Actions du dépôt, enregistrez ce qui suit :
| Nom du Secret | Valeur |
|---|---|
KEYSTORE_BASE64 |
Chaîne Base64 de l'étape 2 |
KEYSTORE_PASSWORD |
Mot de passe du Keystore |
KEY_ALIAS |
Ex. : my-release-key |
KEY_PASSWORD |
Mot de passe de la clé |
SERVICE_ACCOUNT_JSON |
Collez directement le contenu du fichier JSON téléchargé à l'étape 3-2 |
5. Configurer build.gradle pour lire les informations de signature depuis les variables d'environnement
Configurez-le pour que la CI lise depuis les variables d'environnement et que le développement local lise depuis keystore.properties, et même les environnements sans Keystore peuvent toujours exécuter des builds de débogage normalement.
Éditez app/build.gradle.kts comme suit :
import java.util.Properties
import java.io.FileInputStream
// --- Résolution des informations de signature ---
// Priorité : variables d'environnement (CI) > keystore.properties (local) > aucune (build debug uniquement)
val keystorePropertiesFile = rootProject.file("keystore.properties")
val keystoreProperties = Properties().apply {
if (keystorePropertiesFile.exists()) {
load(FileInputStream(keystorePropertiesFile))
}
}
fun resolveSigning(key: String, envKey: String): String? =
System.getenv(envKey) ?: keystoreProperties.getProperty(key)
val releaseStoreFile = resolveSigning("storeFile", "KEYSTORE_FILE")
val releaseStorePassword = resolveSigning("storePassword", "KEYSTORE_PASSWORD")
val releaseKeyAlias = resolveSigning("keyAlias", "KEY_ALIAS")
val releaseKeyPassword = resolveSigning("keyPassword", "KEY_PASSWORD")
val hasReleaseSigning = listOf(
releaseStoreFile, releaseStorePassword, releaseKeyAlias, releaseKeyPassword
).all { !it.isNullOrBlank() }
android {
signingConfigs {
if (hasReleaseSigning) {
create("release") {
storeFile = file(releaseStoreFile!!)
storePassword = releaseStorePassword
keyAlias = releaseKeyAlias
keyPassword = releaseKeyPassword
}
}
}
buildTypes {
getByName("debug") {
// Ajouter applicationIdSuffix pour que les builds release et debug
// puissent coexister sur le même appareil
applicationIdSuffix = ".debug"
versionNameSuffix = "-debug"
// signingConfig utilise le debug.keystore auto-généré par AGP
}
getByName("release") {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
// N'assigner release signingConfig que si les informations de signature sont disponibles
// (= les développeurs sans la clé peuvent quand même exécuter assembleDebug)
if (hasReleaseSigning) {
signingConfig = signingConfigs.getByName("release")
}
}
}
}
Points clés de ce code :
signingConfigs.create("release")est gardé, donc la configuration release n'est pas créée dans les environnements sans informations de signature- Le type de build
debugutilise ledebug.keystoreauto-généré par AGP et n'est en rien affecté par les paramètres de signature release - Les variables d'environnement ont la priorité la plus haute, donc aucun fichier
keystore.propertiesn'est nécessaire sur la CI
6. Configuration supplémentaire pour ne pas casser le développement local
« J'ai modifié build.gradle pour la CI et maintenant les builds de débogage locaux sont cassés » est un accident courant dans les configurations Android × CI. Pour éviter cela, mettez en place ce qui suit :
6-1. Créer keystore.properties (local uniquement)
local.properties est destiné aux chemins SDK, et mélanger les informations de signature là-dedans rend les choses confuses. Créer un fichier keystore.properties séparé pour les informations de signature est l'approche recommandée dans la documentation officielle Android.
Créez keystore.properties à la racine du projet (un niveau au-dessus de app/) :
storeFile=/Users/yourname/keys/release.keystore
storePassword=your-store-password
keyAlias=my-release-key
keyPassword=your-key-password
Ce fichier ne doit jamais aller dans Git, alors ajoutez-le au .gitignore (voir ci-dessous).
6-2. Ce qu'il faut ajouter au .gitignore
Les entrées liées à la signature sont souvent oubliées car le .gitignore généré par Android Studio ne les inclut pas. Ajoutez-les explicitement :
# Signature - ne jamais committer
*.keystore
*.jks
keystore.properties
release.keystore.base64
# JSON du compte de service
*-service-account*.json
play-publisher.json
# Fichiers d'authentification temporaires générés par google-github-actions/auth (avec WIF)
gha-creds-*.json
# Créé par Android Studio mais à vérifier quand même
local.properties
local.properties est généralement ajouté par Android Studio, mais vérifiez s'il est absent dans les anciens projets.
6-3. Coexistence Release et Debug avec applicationIdSuffix
L'extrait build.gradle.kts ci-dessus inclut :
debug {
applicationIdSuffix = ".debug"
}
Cela permet d'installer simultanément la version release du Play Store et une version debug construite localement sur le même appareil. Sans cela, installer un build de débogage écraserait la version Play Store sur votre appareil de test.
Pas directement lié à la CI, mais une fois que vous commencez à automatiser les publications, vous exécuterez souvent la version Play Store sur votre appareil de test, donc je recommande fortement de l'ajouter.
6-4. Utiliser ~/.gradle/gradle.properties
Une autre option est d'écrire les informations de signature dans les propriétés Gradle du répertoire home :
# macOS/Linux: ~/.gradle/gradle.properties
# Windows: %USERPROFILE%\.gradle\gradle.properties
MYAPP_KEYSTORE_FILE=/Users/yourname/keys/release.keystore
MYAPP_KEYSTORE_PASSWORD=xxxx
MYAPP_KEY_ALIAS=my-release-key
MYAPP_KEY_PASSWORD=xxxx
Dans build.gradle.kts, lisez-le avec findProperty("MYAPP_KEYSTORE_FILE") as String?.
L'avantage est qu'aucune information secrète n'est stockée dans le répertoire du projet. L'inconvénient est que communiquer les étapes de configuration aux nouveaux développeurs demande un peu plus d'effort. Ma recommandation : ~/.gradle/gradle.properties pour les projets solo, keystore.properties protégé par .gitignore pour les projets en équipe.
6-5. Tester des builds release non signés dans Android Studio
Bien que ./gradlew assembleDebug fonctionne toujours, vous pourriez vouloir exécuter ./gradlew assembleRelease sans signature (par exemple pour vérifier les paramètres ProGuard des builds release).
Dans ce cas, quand hasReleaseSigning est false, le type de build release n'aura pas de signingConfig, donc exécuter assembleRelease produit un APK non signé (ne peut pas être installé sur un appareil, mais la taille et mapping.txt peuvent être vérifiés).
7. Notes opérationnelles sur Play App Signing
Avec les étapes 1–6 complétées, la configuration Play App Signing est déjà terminée. Le release.keystore créé à l'étape 1 fonctionne comme clé d'upload dans le contexte Play App Signing.
Cette section contient des notes supplémentaires utiles en exploitation.
7-1. Que se passe-t-il au premier upload
Pour une nouvelle application, lors du premier upload de l'AAB vers la Play Console :
- Le certificat utilisé dans cette AAB est automatiquement enregistré comme « certificat de clé d'upload »
- Google génère automatiquement une nouvelle « clé de signature d'application »
- Play App Signing est activé
Donc aucune configuration supplémentaire de la Play Console n'est nécessaire – en suivant les étapes de cet article telles quelles, la configuration Play App Signing est établie.
flowchart TD
CI["CI"] -->|"Signe avec release.keystore"| AAB["AAB"]
AAB --> Play["Play Console"]
Play --> Verify["Google v\u00e9rifie le certificat de cl\u00e9 d'upload"]
Verify --> Resign["Google re-signe avec la cl\u00e9 de signature d'app"]
Resign --> Device["Appareil de l'utilisateur"]7-2. Aller plus loin : Créer une clé d'upload dédiée
Même pour les nouvelles applications, vous pouvez choisir de « enregistrer la clé d'upload séparément » dans la Play Console.
- Lors du premier upload, sélectionnez « Utiliser la clé de signature d'application générée par Google » dans la Play Console
- Sur le même écran, uploadez le certificat de clé d'upload séparément
Dans ce cas, release.keystore contient une « clé entièrement dédiée à l'upload ».
| Modèle standard (7-1) | Approche clés séparées (7-2) | |
|---|---|---|
Contenu de release.keystore |
Clé d'upload = clé ayant signé la première AAB | Clé d'upload pure |
| En cas de compromission | Récupérable via une demande de réinitialisation | Récupérable via une demande de réinitialisation |
| Niveau de sécurité | Suffisant | Plus strict |
Les étapes CI sont identiques pour les deux modèles. La seule différence est la façon dont vous créez release.keystore.
7-3. Que faire si vous perdez la clé d'upload
C'est le plus grand avantage de Play App Signing. Vous pouvez demander une réinitialisation depuis la Play Console.
-
Créez un nouveau Keystore localement
-
Exportez le certificat au format PEM
keytool -export -rfc \ -keystore release.keystore \ -alias my-release-key \ -file upload_certificate.pem -
Dans Play Console → Configuration → Intégrité de l'application → Signature d'application → Demander la réinitialisation de la clé d'upload, uploadez le certificat
-
Le support Google vérifie (généralement 1 à 2 jours ouvrables)
-
Après approbation, les AAB signées avec la nouvelle clé seront acceptées
Avec l'ancienne méthode, « perdre votre clé signifiait que vous ne pouviez plus jamais mettre à jour le même nom de package » – une impasse permanente. Cette option de récupération à elle seule vaut l'adoption de Play App Signing.
7-4. Migrer une application existante (legacy)
Pour les applications créées avant août 2021 qui n'ont pas encore migré vers Play App Signing, vous devez remettre la clé de signature actuelle à Google :
-
Play Console → application cible → Configuration → Intégrité de l'application → Signature d'application
-
Téléchargez l'outil PEPK (Play Encrypt Private Key) fourni par Google
-
Utilisez PEPK pour exporter le Keystore actuel sous forme chiffrée
java -jar pepk.jar \ --keystore=existing-release.keystore \ --alias=my-release-key \ --output=encrypted-key.zip \ --include-cert \ --rsa-aes-encryption \ --encryption-key-path=public-key.pem -
Uploadez le fichier de sortie (
encrypted-key.zip) vers la Play Console -
Après la migration, créez et enregistrez une nouvelle clé d'upload si nécessaire (continuer à utiliser la clé existante comme clé d'upload est également possible)
public-key.pem est la clé publique affichée sur l'écran de migration dans la Play Console, sauvegardée en fichier.
Après la migration, les étapes 1–6 de cet article s'appliquent directement.
8. Écrire le workflow GitHub Actions
Voici maintenant l'essentiel. Créez .github/workflows/release.yml :
name: Android Release
on:
push:
tags:
- 'v*'
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
- name: Cache Gradle packages
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Grant execute permission for gradlew
run: chmod +x ./gradlew
- name: Decode Keystore
run: |
echo "${{ secrets.KEYSTORE_BASE64 }}" | tr -d '\n\r' | base64 --decode > ${{ github.workspace }}/release.keystore
- name: Build Release AAB
env:
KEYSTORE_FILE: ${{ github.workspace }}/release.keystore
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
run: ./gradlew bundleRelease --no-daemon
- name: Upload AAB as artifact
uses: actions/upload-artifact@v4
with:
name: release-aab
path: app/build/outputs/bundle/release/app-release.aab
- name: Deploy to Play Store (Internal Track)
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
packageName: com.example.myapp
releaseFiles: app/build/outputs/bundle/release/app-release.aab
tracks: internal
status: completed
whatsNewDirectory: distribution/whatsnew
Points clés
- Le déclencheur de tag (
v*) empêche le déploiement accidentel à chaque push sur main - Le cache Gradle réduit le temps de build (de ~6 minutes à ~2 minutes lors des exécutions suivantes)
- L'upload d'artefact sert de sauvegarde – si l'upload vers la Play Console échoue, l'AAB peut être téléchargé manuellement depuis l'interface Actions
tracks: internalpeut être changé enalpha,beta,production, etc.whatsNewDirectoryest le répertoire pour les notes de version par langue, ex.distribution/whatsnew/whatsnew-ja-JP
8-2. Configuration plus sécurisée : Utiliser Workload Identity Federation
Au lieu de stocker des clés JSON à longue durée de vie dans GitHub Secrets, Workload Identity Federation (WIF) permet à GitHub Actions d'obtenir des credentials temporaires pour accéder à la Play Console. Cela élimine le risque de fuite de clé JSON et est recommandé pour la production.
Prérequis (côté GCP)
La configuration WIF est effectuée une seule fois. Suivez les étapes dans google-github-actions/auth. Avec cette approche, aucune clé JSON n'est générée pour le compte de service, donc l'étape 4 de la section 3-2 (création/téléchargement de clé) et l'enregistrement de SERVICE_ACCOUNT_JSON dans la section 4 ne sont pas nécessaires.
Configurez avec les commandes gcloud suivantes. Remplacez chaque placeholder par vos valeurs réelles :
| Placeholder | Description |
|---|---|
${PROJECT_ID} |
ID du projet GCP |
${GITHUB_ORG} |
Nom d'organisation GitHub ou nom d'utilisateur |
${REPO} |
Nom du dépôt au format org/repo |
${SERVICE_ACCOUNT} |
Nom du compte de service de l'étape 3-2 (la partie avant @ dans l'e-mail) |
${WORKLOAD_IDENTITY_POOL_ID} |
ID complet du Pool obtenu à l'étape 2 |
# 1. Créer Workload Identity Pool
gcloud iam workload-identity-pools create "github" \
--project="${PROJECT_ID}" \
--location="global" \
--display-name="GitHub Actions Pool"
# 2. Obtenir l'ID complet du Pool
gcloud iam workload-identity-pools describe "github" \
--project="${PROJECT_ID}" \
--location="global" \
--format="value(name)"
# Exemple : projects/123456789/locations/global/workloadIdentityPools/github
# 3. Créer Workload Identity Provider
# --attribute-condition limite aux tokens de cette organisation uniquement
gcloud iam workload-identity-pools providers create-oidc "my-repo" \
--project="${PROJECT_ID}" \
--location="global" \
--workload-identity-pool="github" \
--issuer-uri="https://token.actions.githubusercontent.com" \
--attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository,attribute.repository_owner=assertion.repository_owner" \
--attribute-condition="assertion.repository_owner == '${GITHUB_ORG}'"
# 4. Accorder roles/iam.workloadIdentityUser au compte de service
# principalSet's attribute.repository limite à ce dépôt spécifique
# ${WORKLOAD_IDENTITY_POOL_ID} = ID complet obtenu à l'étape 2
gcloud iam service-accounts add-iam-policy-binding \
"${SERVICE_ACCOUNT}@${PROJECT_ID}.iam.gserviceaccount.com" \
--project="${PROJECT_ID}" \
--role="roles/iam.workloadIdentityUser" \
--member="principalSet://iam.googleapis.com/${WORKLOAD_IDENTITY_POOL_ID}/attribute.repository/${REPO}"
Après la configuration, notez l'ID complet du Provider (projects/.../providers/...). Il est utilisé dans le champ workload_identity_provider du YAML GitHub Actions.
YAML du workflow (version WIF)
Ajoutez la permission id-token: write au dépôt et une étape google-github-actions/auth :
name: Android Release
on:
push:
tags:
- 'v*'
permissions:
contents: read
id-token: write # Requis pour l'acquisition du token WIF
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
- name: Cache Gradle packages
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Grant execute permission for gradlew
run: chmod +x ./gradlew
- name: Decode Keystore
run: |
echo "${{ secrets.KEYSTORE_BASE64 }}" | tr -d '\n\r' | base64 --decode > ${{ github.workspace }}/release.keystore
- name: Build Release AAB
env:
KEYSTORE_FILE: ${{ github.workspace }}/release.keystore
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
run: ./gradlew bundleRelease --no-daemon
- name: Upload AAB as artifact
uses: actions/upload-artifact@v4
with:
name: release-aab
path: app/build/outputs/bundle/release/app-release.aab
- name: Authenticate to Google Cloud (WIF)
uses: google-github-actions/auth@v3
id: auth
with:
workload_identity_provider: projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID
service_account: SERVICE_ACCOUNT@PROJECT_ID.iam.gserviceaccount.com
- name: Deploy to Play Store (Internal Track)
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJson: ${{ steps.auth.outputs.credentials_file_path }}
packageName: com.example.myapp
releaseFiles: app/build/outputs/bundle/release/app-release.aab
tracks: internal
status: completed
whatsNewDirectory: distribution/whatsnew
Remplacez workload_identity_provider et service_account par les valeurs confirmées dans la console GCP après la configuration WIF. La seule différence avec la version à clé JSON est de passer le chemin du fichier de credentials à serviceAccountJson au lieu de serviceAccountJsonPlainText – tout le reste est identique.
9. Créer le répertoire des notes de version
Créez distribution/whatsnew/ à la racine du projet et placez-y les fichiers :
distribution/whatsnew/
├── whatsnew-en-US
└── whatsnew-ja-JP
Chaque fichier contient des notes de version dans la langue respective (jusqu'à 500 caractères).
10. Lançons-nous
Une fois arrivé ici, il suffit de pousser un tag :
git tag v1.0.0
git push origin v1.0.0
Ouvrez l'onglet Actions dans GitHub et le workflow commencera à s'exécuter. En cas de succès, une nouvelle version apparaîtra dans Tests internes dans la Play Console.
11. Pièges courants et solutions
Quelques pièges dans lesquels je suis tombé lors de l'exploitation réelle :
Oublier d'incrémenter versionCode
La Play Console n'autorise pas les valeurs versionCode dupliquées. Mettre en place une numérotation automatique avec le numéro d'exécution GitHub Actions (GITHUB_RUN_NUMBER) évite les accidents. Utilisez le nom du tag sans v pour versionName.
android {
defaultConfig {
// Sur CI, utiliser GITHUB_RUN_NUMBER comme versionCode (fallback sur 1 en local)
versionCode = (System.getenv("GITHUB_RUN_NUMBER")?.toInt() ?: 1) + 1000
// Sur CI, utiliser le tag git (v1.0.0 → 1.0.0) comme versionName
versionName = System.getenv("GITHUB_REF_NAME")?.removePrefix("v") ?: "1.0.0-local"
}
}
Délai de propagation des permissions du compte de service
Exécuter Actions immédiatement après avoir accordé des permissions peut entraîner 403 The caller does not have permission. Les changements de permissions prennent du temps à se propager, alors soyez patient lors de la première exécution.
Sauts de ligne mélangés dans le décodage Base64
La commande base64 se comporte différemment selon les OS :
- macOS : Insère automatiquement des sauts de ligne tous les 76 caractères → utilisez
-i/-opour une sortie propre - Linux : Insère des sauts de ligne par défaut → utilisez
-w 0pour aucun saut de ligne - Windows (
certutil) : En-têtes, pieds de page et sauts de ligne tous inclus → post-traitement requis ;[Convert]::ToBase64Stringde PowerShell est plus sûr
Vérifiez toujours qu'il n'y a pas de sauts de ligne avant de coller dans GitHub Secrets. Ajouter tr -d '\n\r' lors du décodage dans la CI est une pratique standard.
- name: Decode Keystore
run: |
echo "${{ secrets.KEYSTORE_BASE64 }}" | tr -d '\n\r' | base64 --decode > release.keystore
Conclusion
Avec GitHub Actions, le processus de publication devient :
« Créer simplement un tag et pousser »
Cela permet d'augmenter la fréquence de publication avec une charge opérationnelle quasi nulle.
Pour les équipes avec plusieurs développeurs, résoudre uniquement le problème « seul ce Mac peut faire des publications » vaut déjà l'adoption. Si vous rencontrez les mêmes difficultés dans le développement d'applications Android, essayez-le.
Références
Android / Signature
- Signer votre application | Android Developers
- À propos des Android App Bundles | Android Developers
- APK versus AAB | Android Developers
- Configurer votre build | Android Developers
- Conseils et recettes Gradle (exclure les infos de signature de Git) | Android Developers
Google Play Console
- Utiliser Play App Signing | Aide Play Console
- Google Play Developer API
- Démarrer avec la Google Play Developer API | Android Developers
GitHub Actions
- actions/checkout
- actions/setup-java
- actions/cache
- actions/upload-artifact
- r0adkll/upload-google-play
- Secrets chiffrés | GitHub Docs
