DevOps / CI·CD 24 avril 2026

2026-04-24 Swift 6 concurrence stricte, Sendable et @MainActor dans xcodebuild CI sur Mac cloud loué

Équipe Ingénierie MacXCode 24 avril 2026 Lecture ~19 min

Les responsables de build iOS et macOS qui ont déjà adopté des hôtes Apple Silicon en HK / JP / KR / SG / US font face à un second effort : le modèle de langage Swift 6 avec vérification stricte de la concurrence transforme le « ça marche sur ma machine » en écart mesurable entre un portable déjà chaud et un job xcodebuild froid et parallélisé sur un Mac SSH loué. Ce runbook du 2026-04-24 n’est pas un tutoriel de langage—il cartographie les réglages de build (SWIFT_STRICT_CONCURRENCY, version de langage Swift), l’isolation de DerivedData et du magasin d’index, et une migration par étapes pour le bruit Sendable / nonisolated / MainActor qui explose dans les topologies parallèles et runner auto-hébergé. Croisez avec tmp DerivedData + isolation xcresult et archive à distance pour que le même nœud à Singapour ne martèle pas un ModuleCache partagé que votre fragment US croyait privé.

Pourquoi la concurrence stricte Swift 6 se comporte différemment en CI sans interface graphique

La vérification stricte révèle des accès concurrents aux données que le simulateur ne déclenche pas parce que le thread principal de l’UI masque la durée de vie d’un var capturé. En CI, des jobs swift-frontend parallèles et les optimisations module entier réordonnent les diagnostics ; le même package qui a compilé une nuit sur un portable 64 Go peut échouer sur une voie -warnings-as-errors lorsque l’incrémental est désactivé. Traitez la concurrence stricte comme vous traitez déjà la fin du bitcode archivé ou la parité CLT vs Xcode complet : une étape de pipeline séparée avec un DEVELOPER_DIR épinglé et une politique écrite sur les cibles qui peuvent rester sur SWIFT_STRICT_CONCURRENCY=targeted jusqu’aux refactors.

Prévoyez aussi le budget rebuild d’index : un premier build Swift 6 sur un DerivedData vierge peut passer davantage de minutes en ExtractAppIntentsMetadata et étapes voisines si l’app embarque des App Intents ou de grosses extensions WidgetKit. Ce saut d’horloge n’est pas un échec de concurrence, c’est un effet de froid de cache à annoncer aux produits comme le premier chauffage du registre SwiftPM. Enregistrez le p95 avant/après sur le tableau de bord afin que la « semaine Swift 6 » ne se lise pas comme une régression de capacité sur le pool cloud.

Réglages de build : version de langage et modes de concurrence stricte

Au niveau projet ou xcconfig, alignez trois leviers : (1) SWIFT_VERSION sur la chaîne Swift 6 que vous livrez ; (2) SWIFT_STRICT_CONCURRENCY à complete sur votre voie bloquante pour merge, mais peut-être targeted pour d’anciennes libs statiques fournisseur jusqu’à annotation ; (3) l’isolation par défaut par cible en Swift 6, où les modules UIKit et SwiftUI veulent souvent une couverture explicite @MainActor. Dans le YAML CI, ne passez des overrides que lorsque le schéma ne peut pas encore les porter, p. ex. OTHER_SWIFT_FLAGS=-warn-concurrency dans une voie consultative, pas mélangé silencieusement dans des archives release qui doivent rester reproductibles bit à bit. Documentez la différence entre un plugin de package et une cible app : un journal de build plugin peut masquer des erreurs tant que vous n’activez pas -strict-concurrency=complete pour le binaire qui compte.

Invocation xcodebuild : Archive vs Build vs Analyze

Épinglez un seul DEVELOPER_DIR=/Applications/Xcode.app sur l’hôte, puis utilisez -configuration et -destination explicites pour éviter la dérive de schéma. Pour une barrière strict compilation seule avant les tests UI, préférez xcodebuild -scheme App build -destination 'platform=iOS Simulator,name=iPhone 16' avec CODE_SIGNING_ALLOWED=NO lorsque vous n’avez besoin que des signaux compilateur—puis un second job pour les tests simulateur avec la signature réactivée, en miroir de la séparation du guide XCTest + xcresult. Une barrière stricte minimale :

DEVELOPER_DIR=/Applications/Xcode.app xcodebuild build -workspace App.xcworkspace -scheme App -configuration Debug -destination 'platform=iOS Simulator,name=iPhone 16' -derivedDataPath "$CI_DERIVED/Swift6Gate" OTHER_SWIFT_FLAGS='$(inherited) -warn-concurrency'

Garde-fous CI : gardez une relance seulement pour les flakiness d’infra, pas pour les erreurs de concurrence—traitez un second échec identique sur Sendable comme un défaut de code réel, pas un fantôme Xcode.

Voies parallèles, DerivedData et magasin d’index

Lorsque vous ventilez (voir ventilation M4), donnez à chaque job enfant un -derivedDataPath privé et envisagez de désactiver l’incrémental pour l’étape stricte afin que les diagnostics soient déterministes sur les builders HK / JP / KR / SG / US. Un DerivedData partagé sur des montages de type NFS est le moyen le plus rapide d’obtenir de fausses erreurs « cannot assign through subscript: base is not a concurrent value » qui disparaissent au retry—exactement le motif d’instabilité dont l’article isolation avertit déjà pour xcresult. Si vous devez partager un volume pour le coût, placez au moins Index/DataStore sur un préfixe par job via $(CI_JOB_ID).

Matrice de migration : avertissements, erreurs et propriétaires

Signal Action Propriétaire
Capture de fermeture Sendable Refactor vers types valeur, marquer Sendable ou basculer vers un acteur Équipe feature
Dérive d’isolation MainActor Canaliser les mises à jour UI via une petite façade Lead client
Binaire tiers sans Sendable Envelopper à la frontière de module, épingler le ticket fournisseur ou forker Plateforme build

Quand ce n’est pas une erreur de concurrence

Certains échecs se déguisent en problèmes Swift 6 : les ratés provisionnement et trousseau pendant CodeSign peuvent s’entrelacer dans les journaux avec des lignes swift-frontend ; des trous dSYM après une archive cassent la symbolication, pas le compilateur. Gardez l’hygiène dSYM dans la même revue hebdomadaire que les voies strictes, sinon l’astreinte cible le mauvais diff.

Signature, droits et « strict » dans le même build

Activer davantage de contrôles compilateur dans le même train de release qu’une rotation de provisionnement est risqué. Enchaînez les changements : (1) prouver que la signature est verte sur un canari jetable, (2) activer l’étape stricte, (3) seulement alors fusionner un feature flag pour imposer SWIFT_STRICT_CONCURRENCY=complete dans votre schéma par défaut. Le même pool SSH Mac mini M4 peut héberger les deux, mais les jobs ne doivent pas s’entrelacer dans un même dossier workspace—le parallélisme n’est un atout que si les docs archive et sim sont déjà suivies.

Commencez par SwiftPM + cache registre et Ruby + CocoaPods déterministes lorsqu’un échec de concurrence est en réalité un décalage de graphe de dépendances. Si vos équipes mélangent Xcode Cloud et hôtes dédiés, alignez les drapeaux de langage sur les deux, sinon la voie stricte dédiée bloquera des fusions qu’Xcode Cloud n’a jamais vues.

FAQ : Swift 6 sur builders partagés

Question Réponse pratique
Dois-je activer d’abord pour les packages SPM ? Souvent oui—cartographier les frontières, puis les cibles app ; utilisez les traits de package si vous séparez le code test-only.
La VNC est-elle obligatoire ? En général non—la VNC est secours pour le débogage visuel, pas pour les journaux de build stricts.
Et le disque pour l’index ? Choisissez des hôtes 1–2 To lorsque vous désactivez les aperçus SwiftUI longtemps mais conservez de grands arbres Index ; voyez les tarifs si les voies strictes triplent le churn d’artefacts.

Pourquoi le Mac mini M4 nu convient aux compilations très concurrentes

La vérification stricte sollicite CPU et E/S : le compilateur réécrit des graphes, l’index gonfle et le magasin d’index profite du même NVMe que vous budgetisez déjà pour les archives sur un hôte MacXCode loué. Une pile socket sans voisin bruyant réduit aussi la latence de queue lorsqu’une voie parle à un cache ou un registre distant pendant la résolution des packages. Lorsque votre pipeline strict est stable, scalez un second Mac mini M4 dans la même région avant de replier les voies vers un DerivedData partagé—votre branche 2026 mérite un signal déterministe, pas des builds loterie.

Exécuter Swift 6 proprement sur M4

NVMe · Voies isolées · Régions mondiales