Mettre en place un contrôle de migration Swift 6 sur un Mac cloud

Mettre en place un contrôle de migration Swift 6 sur un Mac cloud

Lorsqu’un projet iOS maintenu depuis plusieurs années passe à Swift 6, le principal risque n’est pas d’avancer trop lentement, mais d’activer le nouveau mode de langage en une seule fois et de se retrouver avec des centaines de diagnostics de concurrence mêlés aux changements fonctionnels. Une approche plus sûre consiste à créer d’abord une tâche de contrôle indépendante sur un Mac cloud afin de figer la chaîne d’outils, d’isoler le répertoire de build, d’enregistrer les avertissements existants, puis de renforcer progressivement les règles module par module.

Diviser d’abord la migration en deux étapes

La première étape conserve le mode de langage Swift 5, tout en définissant SWIFT_STRICT_CONCURRENCY sur complete. Cette configuration fait apparaître le passage de types non Sendable entre domaines d’isolation, les appels soumis à l’isolation du thread principal et les états globaux mutables non sécurisés, sans introduire immédiatement tous les changements sémantiques de Swift 6.

La seconde étape consiste à faire passer à Swift 6 uniquement les modules déjà corrigés. Il est recommandé de commencer par les packages fondamentaux ayant peu de dépendances, puis de traiter les couches réseau, données et interface. Une fois les modules feuilles stabilisés, le nombre de diagnostics dans les couches supérieures diminue généralement de lui-même.

Étape Mode de langage Paramètres de contrôle Condition de fusion
Détection des problèmes Swift 5 complete Le nombre d’avertissements de concurrence ne doit pas augmenter
Correction du module Swift 5 complete Aucun avertissement dans le module ciblé
Basculement définitif Swift 6 Règles strictes par défaut Tous les builds et tests réussissent

L’objectif du contrôle de migration de la concurrence n’est pas d’éliminer tous les avertissements dès le premier jour, mais d’empêcher l’apparition de nouveaux problèmes sur la branche principale à partir de ce jour.

Créer une tâche de contrôle reproductible

La tâche exécutée sur le Mac cloud doit consigner explicitement les versions de Xcode et du compilateur Swift, ainsi que le commit utilisé. Ne réutilisez pas le DerivedData issu des builds manuels des développeurs : d’anciens caches de modules pourraient masquer les dépendances réelles. Utilisez un répertoire distinct pour chaque commit, puis nettoyez-le à la fin de la tâche conformément à la politique de conservation.

#!/bin/zsh
set -o pipefail
export LC_ALL=C
root="$PWD/.ci"
derived="$root/DerivedData"
result="$root/ConcurrencyCheck.xcresult"
log="$root/concurrency.log"

rm -rf "$derived" "$result"
mkdir -p "$root"

xcodebuild -version
xcrun swiftc --version
git rev-parse HEAD

xcodebuild \
  -project Example.xcodeproj \
  -scheme Example \
  -configuration Debug \
  -destination 'generic/platform=iOS Simulator' \
  -derivedDataPath "$derived" \
  -resultBundlePath "$result" \
  SWIFT_VERSION=5.0 \
  SWIFT_STRICT_CONCURRENCY=complete \
  build 2>&1 | tee "$log"

build_status=${pipestatus[1]}
exit "$build_status"

Le project, le scheme et la plateforme cible doivent provenir de la configuration du dépôt, sans être dupliqués dans plusieurs scripts. Si le projet utilise un workspace, remplacez -project par -workspace et vérifiez que le scheme partagé a bien été ajouté au dépôt.

Intégrer un budget d’avertissements à la branche principale existante

Dans un projet ancien, il est généralement impossible d’atteindre immédiatement zéro avertissement. Après la première exécution, enregistrez le nombre de diagnostics de concurrence et utilisez-le comme budget initial. Tout commit ultérieur dépassant ce budget doit échouer. Après chaque série de corrections, réduisez le budget dans la même demande de fusion.

budget=${CONCURRENCY_WARNING_BUDGET:-0}
count=$(grep -Ec \
  'warning:.*(Sendable|actor-isolated|concurrency-safe)' \
  .ci/concurrency.log || true)

printf 'concurrency warnings: %s, budget: %s
' "$count" "$budget"
test "$count" -le "$budget"

La recherche textuelle convient comme contrôle transitoire, mais elle nécessite de fixer LC_ALL=C et de vérifier à nouveau la formulation des diagnostics à chaque mise à niveau de Xcode. Le bundle de résultats du build doit être conservé comme pièce jointe en cas d’échec, afin de ne pas dépendre d’un extrait de journal de terminal tronqué. Une fois le budget ramené à zéro, vous pouvez interdire directement tout avertissement de concurrence.

Éviter les avertissements issus du code généré

Si les clients d’API ou les modèles sont générés par un outil, figez la version du générateur dans la configuration du dépôt. Le code externe généré qui ne peut pas être modifié immédiatement peut être placé dans un module cible distinct, mais n’excluez pas l’intégralité du module métier des contrôles stricts. Sinon, tout nouveau code écrit manuellement contournera lui aussi le contrôle.

Corriger par catégorie de problème plutôt que tout désactiver

Face à un diagnostic Sendable, commencez par déterminer si les données doivent réellement être transmises entre plusieurs tâches. Pour une configuration en lecture seule, privilégiez un instantané sous forme de type valeur. Un type référence contenant un cache mutable peut être placé dans un actor. Quant aux objets synchronisés qui doivent être partagés, encapsulez explicitement le verrouillage et les limites d’accès.

L’état de l’interface doit être isolé avec @MainActor au niveau du type ou de la méthode concernés. Pour faire passer rapidement le contrôle, n’annotez pas toute la couche de données ni tous les protocoles avec @MainActor : les traitements en arrière-plan seraient implicitement ramenés sur le thread principal, ce qui créerait encore plus d’appels entre domaines d’isolation.

@unchecked Sendable ne convient qu’aux types dont la sécurité est déjà garantie par un verrou interne, une conception immuable ou une file d’exécution série. La revue de code doit au minimum répondre à trois questions : quels champs sont protégés, toutes les lectures et écritures passent-elles par la même frontière, et comment les futurs champs resteront-ils protégés ? Si ces réponses ne sont pas disponibles, cette déclaration ne doit pas être utilisée.

Lors du pontage d’un callback vers async, vérifiez également que la continuation ne reprend qu’une seule fois. Les tests doivent couvrir à la fois l’absence de callback et les callbacks répétés ; faire simplement disparaître le diagnostic du compilateur ne suffit pas.

Basculer module par module et conserver les preuves de validation

Avant de faire basculer un module, ramenez d’abord ses propres avertissements à zéro en mode Swift 5 avec le niveau complete, puis activez Swift 6. La validation doit au minimum inclure un build Debug, un build Release, les tests unitaires et les principaux tests d’intégration. Exécuter uniquement un build Debug sur simulateur ne permet pas de détecter les problèmes liés aux paramètres d’optimisation ou aux branches de compilation conditionnelle.

Pour chaque migration, consignez le nom du module, son responsable, le commit de basculement, les dérogations restantes et le chemin du bundle de résultats. Lors d’une mise à niveau de Xcode, rétablissez d’abord la référence sur une branche distincte au lieu de réunir dans un même changement la mise à niveau de la chaîne d’outils, les corrections de concurrence et les fonctionnalités métier.

Conservez la tâche de contrôle strict même après la fin de la migration. Swift 6 renforce les contraintes par défaut, mais les mises à jour de dépendances, les frontières Objective-C et le code généré peuvent toujours réintroduire des failles d’isolation. L’état final stable ne consiste pas à avoir atteint zéro avertissement une fois, mais à pouvoir prouver à chaque fusion qu’aucune régression n’a été introduite.

Questions fréquentes

Faut-il activer Swift 6 sur tout le projet immédiatement ?

Non. Activez d’abord la vérification complete en mode Swift 5, corrigez les modules feuilles, puis migrez les cibles une par une.

Peut-on utiliser @unchecked Sendable comme correction rapide ?

Seulement si la sûreté est déjà garantie par l’immutabilité, un verrou ou une isolation sérieuse, avec une justification écrite et une revue ciblée.

Comment introduire ce contrôle dans un projet ancien ?

Prenez le nombre actuel d’avertissements comme budget, refusez toute hausse, puis réduisez ce budget après chaque lot de corrections.

Mac mini physique dédié

Choisir la durée de location d’un Mac dans le cloud selon la durée de la tâche

Deux configurations M4 sont disponibles à la location à la journée, à la semaine, au mois ou au trimestre. Les nœuds et leur disponibilité réelle sont indiqués en temps réel dans la console.

Choisir une configuration et commander