Lorsque plusieurs builds React Native s’exécutent à la suite sur un même Mac dans le cloud, le problème le plus difficile à diagnostiquer n’est souvent pas une erreur de compilation, mais le constat suivant : « le code a changé, mais le livrable semble inchangé ». Une nouvelle exécution en local fonctionne normalement, tandis qu’une tâche d’intégration continue référence parfois un ancien module. Supprimer entièrement le dépôt résout temporairement le problème, qui réapparaît quelques jours plus tard. La cause profonde tient généralement à l’absence de séparation entre quatre couches d’état : l’espace de travail Git, la surveillance Watchman, le cache de transformation Metro et les DerivedData de Xcode.
Identifier d’abord la couche contaminée
Il ne faut pas supprimer tous les caches au premier comportement anormal. Un nettoyage intégral masque l’origine de la panne et ralentit les builds suivants. Commencez par une modification minimale permettant d’en délimiter la portée, par exemple en changeant une chaîne présente uniquement dans le paquet de développement. Vérifiez ensuite séparément le contenu des fichiers, le graphe de modules Metro et le livrable final de l’application.
Collecter les éléments de preuve de chaque tâche
Pour chaque tâche, consignez au minimum le hash du commit, le chemin absolu de l’espace de travail, les versions de Node et de Xcode, la commande de démarrage de Metro ainsi que le chemin des DerivedData. Conservez également la sortie des commandes suivantes :
set -euo pipefail
printf 'commit=%s\n' "$(git rev-parse HEAD)"
printf 'workspace=%s\n' "$PWD"
node --version
xcodebuild -version
watchman watch-list || true
find "$PWD" -maxdepth 2 -name metro.config.js -print
Si le commit et les fichiers source sont corrects, mais que le bundle JavaScript reste obsolète, examinez Metro en priorité. Si le code natif n’est pas actualisé dans l’archive, vérifiez les DerivedData, la configuration du build et l’espace de travail réellement ouvert. Lorsque Watchman signale une erreur ou surveille un répertoire parent, corrigez d’abord les limites de surveillance.
Le nettoyage est une action corrective, pas un diagnostic. Déterminez d’abord quelle couche est défaillante, puis supprimez uniquement l’état de cette couche.
Attribuer un espace de travail indépendant à chaque tâche
Des tâches parallèles ne doivent pas changer de branche dans un même répertoire de checkout. Il ne faut pas non plus placer plusieurs tâches sous un même répertoire parent surveillé par Watchman. Il est recommandé d’utiliser un chemin contenant le numéro du pipeline et celui de la tâche, par exemple /Users/ci/work/4821/ios-release. Une fois la tâche terminée, le répertoire complet peut être archivé ou supprimé indépendamment.
L’espace de travail doit être un répertoire réel, et non un lien symbolique dont la cible change continuellement. Certains scripts utilisent un chemin fixe pour désigner le « build en cours », alors que Watchman et la chaîne d’outils peuvent conserver l’ancien chemin après sa résolution. La commande semble alors s’exécuter dans le nouveau répertoire, tandis que la surveillance porte toujours sur l’ancien checkout.
JOB_ROOT="/Users/ci/work/${PIPELINE_ID}/${JOB_ID}"
WORKSPACE="${JOB_ROOT}/repo"
DERIVED_DATA="${JOB_ROOT}/DerivedData"
METRO_CACHE="${JOB_ROOT}/metro-cache"
mkdir -p "$WORKSPACE" "$DERIVED_DATA" "$METRO_CACHE"
export TMPDIR="${JOB_ROOT}/tmp/"
mkdir -p "$TMPDIR"
Utilisez uniquement des caractères ASCII stables dans les noms de répertoires, sans espaces ni liens symboliques dynamiques. Un répertoire racine par tâche facilite également l’audit de l’espace disque utilisé et des responsabilités de nettoyage.
Restreindre le périmètre de surveillance de Watchman
Après être entré dans l’espace de travail, exécutez watchman watch-project "$WORKSPACE" et vérifiez que la valeur watch renvoyée correspond bien à la racine attendue du projet. Si Watchman remonte jusqu’à un répertoire parent partagé, les limites du dépôt sont généralement mal définies, ou une configuration présente dans le répertoire parent perturbe leur détection.
Placez un fichier .watchmanconfig à la racine du dépôt afin d’exclure les répertoires volumineux qui ne participent pas au graphe de dépendances JavaScript :
{
"ignore_dirs": [
".git",
"DerivedData",
"build",
"artifacts"
]
}
Après avoir modifié ce fichier, recréez la surveillance du projet actuel. Sur une machine exécutant plusieurs tâches en parallèle, n’utilisez pas systématiquement watchman watch-del-all, car cette commande supprime également les surveillances des autres tâches. La méthode sûre consiste à faire de chaque espace de travail une racine de surveillance indépendante, puis à exécuter uniquement la commande suivante à la fin de la tâche :
watchman watch-del "$WORKSPACE" || true
Si cette commande indique que la cible n’est pas une racine surveillée, consultez d’abord watchman watch-list. Ne devinez pas le chemin avant de lancer une suppression globale.
Gérer explicitement les caches Metro et Xcode
Le cache Metro doit être associé à une tâche ou à une base de code donnée. Il ne doit pas dépendre d’anciens fichiers d’origine inconnue présents dans le répertoire temporaire du système. Le script de démarrage doit recevoir explicitement l’emplacement du cache. Le nom exact du paramètre pouvant varier selon la version de Metro, référez-vous à l’aide de la version verrouillée par le projet. Que l’emplacement soit défini par un argument de commande ou par la configuration du projet, l’objectif reste le même : stocker le cache dans ${METRO_CACHE} au lieu de laisser plusieurs tâches partager le répertoire par défaut.
L’option --reset-cache ne doit servir qu’à une récupération ponctuelle après confirmation d’une corruption du cache de transformation. S’il faut réinitialiser le cache à chaque tâche, l’isolation n’est pas encore correctement mise en place. Pour Xcode, fixez de la même manière les DerivedData dans le répertoire propre à la tâche :
xcodebuild \
-workspace App.xcworkspace \
-scheme App \
-configuration Release \
-derivedDataPath "$DERIVED_DATA" \
build
Pods, les paquets Swift et les dépendances JavaScript peuvent utiliser des caches de téléchargement validés, mais le « cache de téléchargement » et « l’état du build » doivent rester distincts. Le premier peut être réutilisé à partir d’une clé dérivée des fichiers de verrouillage, tandis que le second doit être réservé à une seule tâche. Après la restauration d’un cache, vérifiez au minimum le hash du fichier de verrouillage : une correspondance fondée uniquement sur le nom de la branche ne suffit pas.
Préférer un nettoyage par couches à une réinitialisation globale
En cas d’anomalie, commencez par la couche dont le nettoyage aura le moins d’impact. Arrêtez d’abord le processus Metro en cours, supprimez le cache Metro de la tâche, puis redémarrez-le. Si le problème persiste, retirez la surveillance Watchman de l’espace de travail actuel et enregistrez-la de nouveau. Ne supprimez les DerivedData de la tâche que si les résultats de compilation native sont incohérents. Un nouveau checkout du dépôt ne doit être envisagé qu’en dernier recours.
Avant le nettoyage, vérifiez qu’aucun processus n’utilise encore le répertoire :
pgrep -af "$WORKSPACE" || true
lsof +D "$JOB_ROOT" 2>/dev/null | head -n 50 || true
Le script exécuté à la fin de la tâche doit également vérifier que le chemin de l’espace de travail se trouve bien sous la racine attendue. Cette précaution évite de supprimer par erreur un répertoire utilisateur lorsqu’une variable est vide. Il est possible d’utiliser case pour comparer le préfixe du chemin à une liste blanche avant d’exécuter rm -rf -- "$JOB_ROOT".
Pour la validation, exécutez deux fois de suite le même commit. La seconde exécution peut réutiliser les caches de téléchargement, mais l’espace de travail, l’état de Metro et les DerivedData doivent conserver une isolation au niveau de la tâche. Si les deux livrables diffèrent, comparez d’abord les fichiers générés, les variables d’environnement et les journaux de build au lieu d’élargir immédiatement le nettoyage. Cette procédure ne se contente pas de résoudre un incident de cache ponctuel : elle conserve aussi suffisamment d’éléments pour expliquer le prochain écart.
Questions fréquentes
Faut-il lancer Metro avec reset-cache à chaque build ?
Non. Attribuez un répertoire de cache explicite à chaque tâche et ne réinitialisez le cache qu'après avoir confirmé une corruption du graphe de modules.
Plusieurs tâches peuvent-elles partager une racine Watchman ?
Ce n'est pas recommandé. Utilisez des espaces non imbriqués, enregistrez chaque racine séparément et supprimez uniquement la surveillance de la tâche terminée.
Choisissez un Mac dans le cloud toujours disponible pour votre prochaine compilation Xcode
Vérifiez la puce, la mémoire, le stockage, la période de facturation et la région du nœud, puis lancez la configuration. La disponibilité réelle est indiquée en temps réel par la console.