Un même commit ne produit aucune modification sur la machine du développeur, mais fait apparaître des dizaines de lignes de différence dans Generated/ une fois exécuté sur un Mac cloud en CI. Plus problématique encore, le projet continue de compiler : d’anciennes constantes, des clés de ressources manquantes ou des mocks obsolètes peuvent ainsi se retrouver dans le livrable. Pour traiter ce type de problème, il ne suffit pas d’ajouter ponctuellement une commande de génération. La version du générateur, l’ensemble des entrées, le répertoire de sortie et les règles de validation doivent tous faire partie du contrat de build.
Identifier d’abord l’origine de la dérive
La sortie d’outils comme SwiftGen ou Sourcery dépend de quatre éléments : la version de l’exécutable, la configuration et les modèles, les fichiers d’entrée et l’environnement d’exécution. Verrouiller uniquement le fichier de configuration ne suffit pas. L’outil présent dans le PATH de la machine de développement peut être plus récent que celui de la CI, l’ordre de parcours des fichiers peut varier selon l’état des répertoires, et les paramètres régionaux peuvent modifier le tri ou le format des dates.
Commencez par relever le contexte minimal, une fois en local et une fois dans la CI :
set -euo pipefail
sw_vers
xcodebuild -version
swiftgen --version
sourcery --version
printf 'LANG=%s
' "${LANG:-unset}"
printf 'PWD=%s
' "$PWD"
git status --short
L’objectif n’est pas d’accumuler le plus d’informations possible, mais de déterminer ce que le processus de génération lit réellement. Si les modèles inscrivent l’heure courante, un chemin absolu ou un identifiant aléatoire, supprimez d’abord ces entrées non déterministes. Sinon, chaque exécution produira des différences explicables, mais dépourvues de sens.
« Pouvoir régénérer » ne signifie pas « pouvoir obtenir le même résultat ». Le contrôle vérifie que des entrées identiques produisent une sortie strictement identique, octet par octet.
Établir une base de génération reproductible
Le répertoire de génération doit appartenir entièrement aux outils et ne pas contenir de fichiers Swift maintenus manuellement. Supprimer l’ancien répertoire avant chaque exécution permet de détecter les types qui ont disparu des entrées, mais restent présents à cause d’une génération incrémentale. La configuration, les modèles et les répertoires d’entrée doivent tous être référencés par des chemins déterministes à partir de la racine du dépôt, sans dépendre du répertoire courant de l’appelant.
Il est recommandé de répartir les responsabilités comme suit :
| Élément | Méthode de verrouillage | Validation dans la CI |
|---|---|---|
| Générateur | Verrouiller et afficher la version | Conforme à la version déclarée dans le dépôt |
| Configuration et modèles | Les placer sous contrôle de version | Aucune modification temporaire dans l’espace de travail |
| Entrées | Définir explicitement les répertoires et extensions | Ensemble stable après tri |
| Sorties | Utiliser un répertoire dédié | Vider avant de régénérer |
| Environnement | Fixer les paramètres régionaux | Ne pas inscrire l’heure ni les chemins absolus |
La méthode d’installation des outils peut varier selon l’équipe, mais la version déclarée doit provenir d’une source unique. Évitez qu’une version figure dans le script, une autre dans la documentation de développement, tandis que le Mac cloud utilise une troisième version trouvée dans son PATH.
Contrôler les différences avant la compilation
Placez la génération avant la compilation pour obtenir un retour d’échec plus rapide et éviter qu’une erreur de compilation ne masque la cause réelle. Le script ci-dessous suppose que tous les fichiers générés se trouvent dans Generated/ et qu’ils sont suivis dans le dépôt :
set -euo pipefail
repo_root="$(git rev-parse --show-toplevel)"
cd "$repo_root"
export LANG=C
export LC_ALL=C
rm -rf Generated
mkdir -p Generated build
swiftgen config run --config swiftgen.yml
sourcery --config sourcery.yml
find Generated -type f -print |
LC_ALL=C sort |
while IFS= read -r file; do
shasum -a 256 "$file"
done > build/generated.sha256
git diff --exit-code -- Generated
git status --short --untracked-files=all Generated
git diff ne détecte que les changements apportés aux fichiers déjà suivis. Il faut donc également rechercher les fichiers non suivis. Dans le contrôle réel, toute sortie de git status doit provoquer un échec explicite, et le correctif des différences ainsi que generated.sha256 doivent être conservés comme artefacts du build. Le manifeste de hachage ne remplace pas la vérification des différences, mais il permet de répondre à la question : « Quel ensemble de fichiers cette exécution de CI a-t-elle réellement généré ? »
Distinguer trois catégories d’échec
Si seul le formatage change, vérifiez les fins de ligne des modèles, la version des outils et l’ordre d’exécution des formateurs. Si des fichiers sont ajoutés ou supprimés, contrôlez l’ensemble des entrées et la logique de nettoyage. Si les valeurs du contenu changent, examinez les ressources, les descriptions d’interface ou les paramètres des modèles. Une fois l’échec classé, corrigez sa source au lieu d’écraser les artefacts dans la CI avant de poursuivre la compilation.
Éviter les pièges de concurrence dans les phases de build Xcode
Lorsque plusieurs targets partagent le même répertoire de génération, placer la commande dans chaque Run Script Phase peut entraîner des écritures simultanées dans un même fichier. Cela ne provoque parfois qu’une exécution en double, mais peut aussi produire des fichiers tronqués ou temporairement absents. Une approche plus robuste consiste à faire de la génération une étape préalable indépendante de la CI, puis à lancer toutes les tâches xcodebuild une fois celle-ci terminée.
Si la génération doit impérativement rester dans une phase de build Xcode, un seul propriétaire doit être désigné, avec une liste complète des fichiers d’entrée et de sortie. Le script de génération ne doit pas modifier le fichier de projet ni vider le répertoire généré pendant qu’un autre processus le lit. Si les builds parallèles nécessitent des sorties indépendantes, créez un répertoire temporaire par identifiant de tâche, puis publiez le résultat validé à l’emplacement fixe au moyen d’un remplacement atomique unique.
Une autre erreur fréquente consiste à exécuter le formateur immédiatement après la génération alors que le hook local de pré-commit utilise l’ordre inverse. La génération, le formatage et le contrôle des différences doivent toujours s’exécuter dans le même ordre et depuis un même script d’entrée.
Liste de contrôle avant fusion et en cas d’incident
Lorsqu’un commit modifie un générateur ou ses entrées, la revue de code doit au minimum vérifier les points suivants :
- La déclaration de version du générateur a-t-elle été mise à jour en même temps ?
- Les modifications de la configuration, des modèles et des entrées figurent-elles dans le même commit ?
- Après la suppression d’une entrée, le fichier généré correspondant a-t-il réellement disparu ?
- Les artefacts contiennent-ils une heure, un nom d’utilisateur ou un chemin absolu ?
- Après deux exécutions locales consécutives, la seconde ne produit-elle aucune différence ?
- La CI vérifie-t-elle les fichiers suivis et non suivis avant la compilation ?
- Les tâches parallèles écrivent-elles dans des répertoires isolés les uns des autres ?
- En cas d’échec, la version, le correctif des différences et le manifeste de hachage sont-ils conservés ?
Lors du diagnostic, n’exécutez pas immédiatement une correction automatique qui écraserait l’état du problème. Conservez d’abord git status, les versions des outils et les différences, puis relancez le point d’entrée de génération depuis un checkout propre. Si le résultat reste incohérent depuis un checkout propre, le problème vient de l’environnement ou d’une entrée non verrouillée. Si seul un espace de travail réutilisé échoue, concentrez-vous sur les anciens fichiers, les caches et les écritures concurrentes. Le critère final est simple : pour un même commit, deux générations consécutives dans n’importe quelle tâche sur Mac cloud ne doivent laisser aucune différence observable après la seconde exécution.
Questions fréquentes
Faut-il versionner les fichiers Swift générés ?
Versionnez-les lorsqu'ils sont nécessaires à la revue ou au développement sans générateur local. Le CI doit alors les recréer et refuser toute différence non validée.
Pourquoi la génération diffère-t-elle entre le poste local et le CI ?
Les causes fréquentes sont la version de l'outil, l'ordre des entrées, la langue système, les fins de ligne et le répertoire courant. Rendez chaque paramètre explicite.
Exécutez votre prochaine tâche sur un nœud physique dédié
Choisissez le nœud et la période de facturation, puis commencez le déploiement avec la configuration fixe M4, 16 Go de RAM et 256 Go de SSD. La disponibilité réelle est indiquée en temps réel dans la console.