Lorsqu’un projet initialement destiné uniquement à l’iPhone et à l’iPad active Mac Catalyst, l’erreur la plus fréquente consiste à penser que « s’il compile pour iOS, il compilera aussi sur Mac ». Dans les tâches automatisées exécutées sur un Mac cloud, les problèmes concernent généralement la déclaration des plateformes prises en charge par les dépendances, l’affectation des ressources, la compilation conditionnelle et le partage des caches. La méthode fiable ne consiste pas simplement à ajouter une destination à la commande existante, mais à traiter les deux plateformes comme des produits de build qui partagent le même code source tout en faisant l’objet de validations distinctes.
Vérifier que le projet prend réellement en charge les deux cibles
Commencez par activer Mac Catalyst dans les réglages de la cible Xcode, puis vérifiez que les modifications correspondantes du fichier de projet ont bien été commitées. Ne vous fiez pas uniquement à la case cochée dans l’interface graphique : la tâche automatisée doit lire les valeurs finales dans les réglages de build.
xcodebuild \
-project DemoApp.xcodeproj \
-scheme DemoApp \
-showBuildSettings |
grep -E 'SUPPORTS_MACCATALYST|PRODUCT_BUNDLE_IDENTIFIER|SDKROOT'
La valeur de SUPPORTS_MACCATALYST doit être YES. Si le projet utilise un workspace, remplacez -project par -workspace. Exécutez ensuite xcodebuild -showdestinations afin de confirmer que le scheme partagé expose à la fois une destination iOS Simulator et une destination Mac Catalyst. Si le scheme est introuvable en ligne de commande, vérifiez d’abord qu’il est défini comme Shared au lieu de modifier sans cesse la chaîne de destination.
Examinez également, un par un, les plateformes prises en charge par les Swift Packages, les dépendances binaires et les modules internes. Le fait qu’un package compile sous iOS ne signifie pas qu’il déclare une compatibilité avec Mac Catalyst. Pour les dépendances binaires propriétaires, vérifiez que le XCFramework contient la tranche correspondant à Catalyst ; si elle est absente, l’échec ne surviendra qu’à l’étape de l’édition de liens.
Isoler les caches et les répertoires de résultats
iOS et Catalyst produisent des caches de modules, des objets intermédiaires et des artefacts d’édition de liens différents. Si l’intégration continue partage un même DerivedData, les échecs peuvent varier selon l’ordre d’exécution des tâches, puis disparaître temporairement après un nettoyage. Il est recommandé d’utiliser un répertoire fixe pour chaque plateforme et d’archiver également les bundles de résultats.
set -euo pipefail
xcodebuild \
-workspace DemoApp.xcworkspace \
-scheme DemoApp \
-destination 'generic/platform=iOS Simulator' \
-derivedDataPath build/derived-ios \
-resultBundlePath build/results-ios.xcresult \
CODE_SIGNING_ALLOWED=NO \
build-for-testing
xcodebuild \
-workspace DemoApp.xcworkspace \
-scheme DemoApp \
-destination 'platform=macOS,variant=Mac Catalyst,arch=arm64' \
-derivedDataPath build/derived-catalyst \
-resultBundlePath build/results-catalyst.xcresult \
CODE_SIGNING_ALLOWED=NO \
build-for-testing
La signature est ici désactivée afin que le contrôle porte uniquement sur la validité du code source, des dépendances et de l’édition de liens. L’archivage et la signature de distribution doivent être placés dans des tâches ultérieures, afin de ne pas mélanger les erreurs de compatibilité entre plateformes et les erreurs de configuration de signature dans un même journal.
Ne considérez pas la suppression complète de
~/Library/Developer/Xcode/DerivedDatacomme une solution par défaut. Supprimer précisément le répertoire de la tâche en cours évite de perturber les autres jobs exécutés sur la même machine et permet de conserver les éléments utiles à l’analyse de l’échec.
Centraliser les différences de plateforme plutôt que disperser les conditions
Le cycle de vie de l’interface, les menus, le comportement des fenêtres et certaines capacités système diffèrent entre Catalyst et iOS. La compilation conditionnelle doit être centralisée dans une couche d’adaptation ; évitez de disperser targetEnvironment(macCatalyst) dans le code métier.
enum PlatformLayout {
static var usesDesktopNavigation: Bool {
#if targetEnvironment(macCatalyst)
return true
#else
return false
#endif
}
}
Les API propres à une plateforme doivent également être protégées par des vérifications de disponibilité. La compilation conditionnelle indique uniquement la cible en cours de compilation ; elle ne garantit pas que la version du système utilisée à l’exécution propose l’API concernée. Définissez d’abord une interface stable dans un protocole commun, puis fournissez des implémentations distinctes pour iOS et Catalyst afin que les tests unitaires puissent vérifier directement le comportement sur les deux plateformes.
Les ressources doivent aussi être contrôlées séparément. Des polices, fichiers de description de confidentialité, ressources localisées ou modèles de données peuvent n’être intégrés qu’à un seul produit si leur Target Membership n’a pas été correctement sélectionnée. Après le build, examinez le répertoire du produit pour vérifier la présence des fichiers essentiels, au lieu d’attendre l’exécution pour découvrir une interface vide.
| Élément à vérifier | iOS | Mac Catalyst |
|---|---|---|
| Dépendances pouvant être liées | Tranche du simulateur disponible | Tranche Catalyst disponible |
| Affectation des ressources | Visibles dans le bundle de l’app | Visibles dans le bundle de l’app Catalyst |
| Compilation conditionnelle | Branche mobile | Branche d’adaptation au bureau |
| Preuves de résultat | xcresult distinct | xcresult distinct |
Décomposer les échecs en étapes identifiables
Une tâche à deux cibles ne doit pas se limiter à un unique état global « échec du build ». Il est recommandé de la décomposer successivement en résolution des dépendances, compilation sans signature, tests unitaires, archivage et validation de la livraison. Chaque étape ne doit démarrer qu’après la réussite de la précédente, et les journaux doivent être nommés selon la plateforme.
Examiner d’abord quatre catégories de signaux en cas d’échec de compilation
Face à No such module, commencez par vérifier les plateformes déclarées par la dépendance et la cible de build. En cas d’incompatibilité d’architecture, contrôlez les tranches du binaire au lieu d’ajouter immédiatement une exclusion globale d’architecture. Pour une API signalée comme unavailable, revenez à la couche d’adaptation afin d’ajouter la compilation conditionnelle et les vérifications de disponibilité nécessaires. Enfin, en présence de ressources dupliquées ou manquantes, examinez Copy Bundle Resources et Target Membership.
Les scripts de build peuvent eux aussi intégrer des hypothèses propres à une plateforme. Par exemple, si un script fixe le SDK à iphoneos, la tâche Catalyst lira un chemin incorrect. Les scripts doivent utiliser les valeurs PLATFORM_NAME, SDKROOT et TARGET_BUILD_DIR injectées par Xcode, et s’arrêter immédiatement lorsqu’ils rencontrent une plateforme inconnue.
Établir une checklist avant la fusion
Le contrôle minimal doit garantir que les deux cibles peuvent résoudre leurs dépendances et compiler sans signature depuis un répertoire propre, tout en enregistrant un fichier .xcresult distinct pour chacune. Les tests unitaires des modules principaux doivent couvrir les interfaces d’adaptation aux plateformes. Pour les comportements liés aux fenêtres, aux menus ou au glisser-déposer, ajoutez ensuite des tests propres à Catalyst.
Avant de soumettre les modifications, effectuez les vérifications suivantes dans cet ordre :
- le scheme est partagé et la ligne de commande peut répertorier les deux destinations ;
- iOS et Catalyst utilisent des DerivedData distincts ;
- les dépendances déclarent explicitement la prise en charge des deux plateformes et toutes les tranches binaires nécessaires sont présentes ;
- les conditions propres aux plateformes sont centralisées dans la couche d’adaptation, sans duplication de la logique commune ;
- les ressources essentielles sont intégrées aux deux produits ;
- les journaux d’échec, bundles de résultats et instantanés des réglages de build sont archivés séparément par plateforme ;
- le contrôle de compilation et la tâche de signature des archives sont exécutés séparément.
Dans G-Mini ou tout autre environnement de build distant, commencez par exécuter le processus deux fois de suite lors de sa première mise en place, puis inversez l’ordre d’exécution des deux cibles. Si le résultat change selon cet ordre, examinez en priorité les caches partagés, les répertoires temporaires des scripts et les fichiers générés qui n’ont pas été nettoyés. Un processus à deux cibles n’est pas stable parce qu’il réussit occasionnellement pour les deux plateformes, mais parce qu’il produit des résultats cohérents depuis un workspace propre, quel que soit l’ordre d’exécution.
Questions fréquentes
iOS et Mac Catalyst peuvent-ils partager le même DerivedData ?
C'est acceptable pour un essai local court, mais la CI doit utiliser des répertoires séparés afin d'éviter le mélange des fichiers intermédiaires et de simplifier le nettoyage.
Faut-il lancer toute la suite de tests dès le premier contrôle ?
Non. Commencez par la compilation sans signature et quelques tests unitaires sur les deux destinations, puis ajoutez les tests UI, les archives et la signature.
Que faut-il vérifier en premier après un échec Catalyst ?
Contrôlez l'activation de Catalyst, la compatibilité des dépendances, les branches de compilation conditionnelle, l'appartenance des ressources et les Build Phases.
Préparez un Mac dans le cloud pour votre prochaine file de build
Comparez deux configurations M4 sur cinq nœuds et choisissez une location à la journée, à la semaine, au mois ou au trimestre selon votre rythme de travail.