Technique

Flux de déploiement automatisé - Expo

Image principale du blog pour l’article publié le 10/9/2020

En quelques années, Expo a su s’imposer dans le monde du développement d’application mobiles. Facile à prendre en main et comportant beaucoup de packages compatibles, il est devenu une boîte à outil incontournable lorsque l’on veut développer une application mobile avec React, sans passer par un langage natif.

Depuis plusieurs années, Matière Noire utilise React Native avec Expo. Si le workflow de développement est très fluide, celui de déploiement l’est un peu moins. Au fil de nos développements, nous avons dû faire face à une problématique :

Comment builder notre app pour une préprod et une prod depuis notre environnement de dev ?

Autrement dit, comment gérer des environnements avec Expo ?

Il est possible de le faire manuellement. Cela nécessite de modifier le fichier app.json afin de gérer les numéros de version, puis d’ajuster les variables d’environnement et d’activer ou non le mode prod, puis de lancer le build et de publier. Des modifications fastidieuses et sources d’erreur.

Pour remédier à cela, nous avons mis en place un flux de déploiement basé sur les branches de nos dépôts et qui, combiné à des GitHub Actions et à semantic release, permet de déployer une app sur plusieurs environnements de manière automatique.


Les release channels

Expo bénéficie d’une fonctionnalité intéressante, les releases channels. Ils permettent de maintenir plusieurs versions d’une même app dans des canaux dédiés. Pour les utiliser, il suffit de les renseigner lors des moments clés du déploiement, comme lors du build ou du publish:

expo build:android --release-channel next
expo publish --release-channel master


Le code publié ne sera disponible que dans l’app buildé dans ce channel. Le release channel est visible sur la page de téléchargement d’un build Expo.

Gérer les environnements d’API

Avec le paquet Constants d’Expo, il est facile de récupérer le release channel en cours:

Constants.manifest.releaseChannel


A noter qu’il retournera false en mode dev.

Si l’on peut récupérer notre channel, on peut donc leur dédier un environnement. Par exemple, comme suit:

import Constants from "expo-constants";

const ENV = {
 dev: {
   apiUrl: "<https://dev.example.org/api>",
 },
 staging: {
   apiUrl: "<https://staging.example.org/api>",
 },
 prod: {
   apiUrl: "<https://prod.example.org/api>",
 }
};

const getEnvVars = (env = Constants.manifest.releaseChannel) => {
 // What is __DEV__ ?
 // This variable is set to true when react-native is running in Dev mode.
 // __DEV__ is true when run locally, but false when published.
 if (__DEV__) {
   return ENV.dev;
 } else if (env === 'next') {
   return ENV.staging;
 } else {
   return ENV.prod;
 }
};

export default getEnvVars;


Maintenant que nos environnements sont gérés, il ne reste plus qu’à gérer la partie déploiement.

Les branches git

Afin d’automatiser les builds, chaque release channel va correspondre à une branche. Nous garderons ainsi notre flux de travail habituel. Pour la partie staging, il s’agira de la branche next et pour la partie prod, ce sera la branche master. Ces noms de branches découlent de semantic release.

Grâce aux GitHub Actions, chaque pull request ou commit sur une branche pourra déclencher un build. Expo Semantic Release se chargera de mettre à jour les numéros de versions. Puis Expo lancera les builds sur l’environnement correspondant.

Pour cela, il convient de créer un fichier dans .github/workflows/main.yml dans le projet et y insérer:

name: Handle expo build

on:
  push:
    branches: [next, master]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2

      - name: Setup Node.js
        uses: actions/setup-node@v1
        with:
          node-version: 12

      #expo semantic release crashes if semantic-releases/error is not there
      - name: Install dependencies
        run: yarn

      - name: Expo Semantic Release
        uses: mgibeau/semantic-release-expo-github-action@v0.2-alpha
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - uses: expo/expo-github-action@v5
        with:
          expo-version: 3.x
          expo-username: ${{ secrets.EXPO_CLI_USERNAME }}
          expo-password: ${{ secrets.EXPO_CLI_PASSWORD }}

      - name: Build iOS app
        run: expo build:ios --release-channel=${GITHUB_REF#refs/*/} --no-wait
        env:
          EXPO_APPLE_ID: ${{secrets.EXPO_APPLE_ID}}
          EXPO_APPLE_ID_PASSWORD: ${{secrets.EXPO_APPLE_ID_PASSWORD}}

      - name: Build Android app
        run: expo build:android --release-channel=${GITHUB_REF#refs/*/} --no-wait

      - name: Publish app
        run: expo publish --release-channel=${GITHUB_REF#refs/*/}


Il faut bien penser à entrer les variables suivantes dans le repo Github (dans settings/secrets) :

EXPO_APPLE_ID
EXPO_APPLE_ID_PASSWORD
EXPO_APPLE_TEAM_ID
EXPO_APP_NAME
EXPO_CLI_PASSWORD
EXPO_CLI_USERNAME

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/05254436-173f-4ddf-be5f-71f1a5f5ec7d/Capture_decran_2020-09-08_a_23.46.01.png


Il faut également mettre à jour le package.json avec les dépendances nécessaires.


Bonus

Un des bénéfices d’Expo est d’externaliser vers leurs serveurs les builds de nos applications, souvent gourmands en ressources.

Pour être averti de la fin des builds, Expo peut appeler un webhook. Pour cela, on peut créer un petit script PHP qui recevra l’appel du webhook et enverra un email.

Pour créer le webhook:

expo webhooks:set --event build --url {{url}}?dest[]=salut@matierenoire.io


Pour recevoir le webhook, le serveur enverra un mail assez sommaire avec l’url du fichier à télécharger:

<?php

/**
 * Répond à un webhook d'Expo signalant la fin d'un build
 * Dans un projet Expo, enregistrer le webhook dela façon suivante :
 * expo webhooks:set —event build —url {{url}}?dest[]=salut@matierenoire.io
 */

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_GET['dest'])) {
  $payload = json_decode(file_get_contents('php://input'));

  $dest = $_GET['dest'];
  $error = $payload->status !== 'finished';
  $platform = $payload->platform;
  $url = $payload->artifactUrl;

  $headers[] = 'MIME-Version: 1.0';
  $headers[] = 'From: contact@matierenoire.io';
  $headers[] = 'Content-type: text/html; charset=utf-8';

  $message = '<html>
      <head>
       <title>Info Build Expo</title>
      </head>
      <body>';

  if ($error) {
    $subject = 'Une erreur a eu lieu lors du build Expo';
    $message .= "<p>Le build pour la plateforme $platform a rencontré une erreur.</p>";
  } else {
    $subject = "Build Expo pour $platform terminé";
    $message .= "<p>Le build pour la plateforme $platform est terminé.</p>\\r\\n";
    $message .= '<p>Il est téléchargeable à cette adresse: <a href="' . $url . '" target="_blank" rel="noreferrer noopener">' . $url . '</a></p>';
  }
  $message .= '</body>
  </html>';

  foreach ($dest as $d) {
    mail($d, $subject, $message, implode("\\r\\n", $headers));
  }
  exit;
} else {
  exit;
}


Il ne reste plus qu’à récupérer les builds sur le site d’Expo et le tour est joué.

L’ensemble du projet démo est accessible sur ce dépôt GitHub.

N’hésitez pas à nous contacter si vous souhaitez mettre en place ce type de déploiement, ou développer votre application mobile.

Auteur de l article Jean-Rémy Praud

Par Jean-Rémy Praud

Partager l'article