Bonnes pratiques d’ingénierie

Isoler Ruby et fastlane dans une CI Mac distante

Isoler Ruby et fastlane dans une CI Mac distante

Sur un même Mac distant, fastlane fonctionne lorsqu’un développeur le lance depuis le terminal, tandis que la tâche de CI signale un gem manquant. Une fois le runner corrigé, le terminal de connexion peut encore résoudre une autre installation de Ruby. En général, ces incidents ne viennent pas de fastlane lui-même, mais d’une dérive des chemins provoquée conjointement par le Ruby système, le gestionnaire de versions, la configuration de Bundler et l’environnement d’exécution. La solution n’est pas de réinstaller sans cesse les gems, mais d’intégrer toute la chaîne d’outils Ruby au projet.

Identifier d’abord l’environnement Ruby réellement exécuté

Ne commencez pas par lancer une commande d’installation. Relevez séparément les résultats suivants dans le terminal de connexion et dans la tâche de CI :

command -v ruby
ruby -v
command -v bundle
bundle -v
gem env home
bundle config list
printf '%s
' "$PATH"

Vérifiez en priorité si ruby et bundle proviennent du même préfixe. Si Ruby se trouve dans le répertoire d’un gestionnaire de versions alors que Bundler vient d’un répertoire système, les installations suivantes risquent d’écrire dans le mauvais chemin de gems. Vérifiez également si la CI définit GEM_HOME, GEM_PATH ou BUNDLE_PATH : d’anciennes variables peuvent remplacer la configuration du projet.

Une commande qui fonctionne dans un terminal interactif ne bénéficie pas nécessairement du même environnement dans une tâche automatisée. La CI doit démarrer à partir de fichiers de version et d’une configuration de projet explicites, plutôt que de compter sur un script d’initialisation du shell qui modifierait PATH par hasard.

Un seul artefact de diagnostic suffit. N’inscrivez pas dans les journaux les jetons, le contenu des certificats ni l’intégralité des variables d’environnement. Les chemins et les versions permettent de diagnostiquer la plupart des problèmes de résolution.

Verrouiller Ruby, Bundler et fastlane

La racine du dépôt doit contenir les fichiers versionnés .ruby-version, Gemfile et Gemfile.lock. La version de Ruby doit être complète et installable, et non approximative comme 3.3. Une fois la version choisie par l’équipe, exécutez :

printf '%s
' '3.3.6' > .ruby-version
bundle init
bundle add fastlane --version '~> 2.0'
bundle lock

Gemfile définit la plage de versions autorisée, tandis que Gemfile.lock enregistre les versions exactes obtenues lors de la résolution. La CI ne doit pas exécuter bundle update avant chaque build, sous peine d’annuler le rôle contraignant du fichier de verrouillage. Après l’installation, exécutez toutes les commandes par l’intermédiaire de Bundler :

bundle exec fastlane --version
bundle exec fastlane ios ci

N’utilisez pas l’installation globale de fastlane comme raccourci. Elle peut appartenir à une autre installation de Ruby ou changer après une mise à jour du système ou une intervention manuelle de débogage. Si le projet exige une version précise de Bundler, récupérez-la dans la section BUNDLED WITH de Gemfile.lock, installez-la, puis lancez bundle install.

Limiter l’installation des dépendances au répertoire du projet

La configuration propre au projet doit être enregistrée dans .bundle/config au sein du dépôt. Ne modifiez pas la configuration globale de Bundler pour l’ensemble de la machine :

bundle config set --local path vendor/bundle
bundle config set --local clean true
bundle install --jobs 4 --retry 2

En règle générale, vendor/bundle n’est pas ajouté à Git, mais confié au cache de la CI. Cette approche évite de polluer les gems système et permet à plusieurs projets d’utiliser des versions différentes. Après l’installation, les vérifications suivantes permettent de valider toute la chaîne de résolution :

test -f Gemfile.lock
test "$(ruby -e 'print RUBY_VERSION')" = "$(cat .ruby-version)"
bundle check
bundle exec ruby -e 'require "fastlane"; puts Fastlane::VERSION'

Si la dernière étape échoue, ne supprimez pas immédiatement tous les caches. Examinez d’abord bundle config list et bundle env afin de vérifier que la tâche utilise le Gemfile du dépôt actuel, et non celui d’un répertoire parent ou un autre fichier désigné par BUNDLE_GEMFILE.

Simuler un environnement non interactif propre

Avant d’intégrer définitivement le runner, effectuez une validation dans un environnement minimal :

RUBY_BIN="$(dirname "$(command -v ruby)")"
env -i \
  HOME="$HOME" \
  PATH="$RUBY_BIN:/usr/bin:/bin:/usr/sbin:/sbin" \
  BUNDLE_GEMFILE="$PWD/Gemfile" \
  bundle exec fastlane --version

Cette étape révèle en amont les chemins, alias et variables d’environnement définis uniquement dans la configuration shell personnelle. Si la commande échoue, corrigez l’initialisation explicite du runner au lieu de demander à la CI de charger toute la configuration personnelle.

Concevoir des clés de cache sans collisions

Pour les gems comportant des extensions natives, une clé de cache fondée uniquement sur Gemfile.lock ne suffit pas. Une différence de version de Ruby, d’architecture CPU ou de version majeure et mineure de macOS peut empêcher le chargement des fichiers compilés. La clé de cache doit au minimum inclure les champs suivants :

Champ Méthode d’obtention Rôle
Version de Ruby ruby -e 'print RUBY_VERSION' Isoler les différences d’ABI
Architecture CPU uname -m Distinguer les différents codes natifs
Version de macOS sw_vers -productVersion Éviter les différences de bibliothèques système
Empreinte du fichier de verrouillage shasum -a 256 Gemfile.lock Invalider le cache lorsque les dépendances changent

Vous pouvez générer une empreinte stable comme suit :

RUBY_VERSION_KEY="$(ruby -e 'print RUBY_VERSION')"
ARCH_KEY="$(uname -m)"
OS_KEY="$(sw_vers -productVersion | cut -d. -f1,2)"
LOCK_KEY="$(shasum -a 256 Gemfile.lock | cut -d' ' -f1)"
printf '%s-%s-%s-%s
' "$RUBY_VERSION_KEY" "$ARCH_KEY" "$OS_KEY" "$LOCK_KEY"

Après la restauration du cache, exécutez tout de même bundle check. Si la vérification échoue, lancez alors bundle install pour compléter son contenu. La réussite de la décompression du cache ne prouve pas que les dépendances sont utilisables.

Transformer les échecs en contrôles faciles à diagnostiquer

La tâche finale doit s’exécuter en trois phases : validation de l’environnement, validation des dépendances, puis lane métier. La première vérifie les versions et les chemins, la deuxième exécute bundle check, et la troisième seulement appelle fastlane. Tout échec doit interrompre immédiatement l’exécution afin d’éviter qu’il ne se manifeste plus tard sous la forme d’un plugin introuvable ou d’une erreur incompréhensible de chargement de constante.

Il est recommandé d’ajouter les points suivants aux contrôles avant fusion :

  • La version indiquée dans .ruby-version correspond exactement au Ruby réellement utilisé par le runner.
  • Gemfile.lock est versionné et ne change pas entre le début et la fin de la tâche.
  • fastlane est appelé exclusivement avec bundle exec.
  • .bundle/config utilise un répertoire de dépendances propre au projet.
  • La clé de cache inclut Ruby, l’architecture, la version du système et l’empreinte du fichier de verrouillage.
  • Les journaux ne conservent que les versions, les chemins et les résultats des vérifications, sans exposer de valeurs d’environnement sensibles.
  • Le runner et le terminal de connexion utilisent la même logique d’initialisation explicite.

Une fois ces contraintes appliquées, la chaîne d’outils Ruby cesse d’être « un ensemble de commandes installées sur la machine » pour devenir une entrée de build vérifiable. Lors d’une future mise à niveau de Ruby ou de fastlane, les fichiers de version et de verrouillage pourront être actualisés dans un commit distinct, puis validés avec les mêmes contrôles. Il ne sera plus nécessaire d’attendre un échec pour tenter de deviner quel environnement la tâche a réellement utilisé.

Questions fréquentes

Pourquoi fastlane peut-il changer malgré un Gemfile.lock versionné ?

La cause habituelle est l’appel direct au binaire global ou la régénération du verrou avant l’exécution. Lancez toujours bundle exec fastlane et traitez Gemfile.lock comme une entrée immuable.

Peut-on partager vendor/bundle entre plusieurs Mac distants ?

Seulement avec une clé stricte incluant la version de Ruby, l’architecture CPU, la version majeure et mineure de macOS, ainsi que l’empreinte de Gemfile.lock.

Que vérifier si la CI fonctionne mais pas la session terminal ?

Comparez command -v ruby, ruby -v, bundle config list et les variables d’environnement. Les deux sessions chargent souvent des PATH ou des initialisations Ruby différents.

Nœud physique dédié

Exécutez vos workflows Mac mini en continu dans le cloud

Choisissez RunAMac M4, la durée de location et le nœud de déploiement pour exécuter vos tâches de développement, de compilation ou d’expérimentation sur une machine physique dédiée.

Choisir une formule de location