Compiler et distribuer des XCFramework sur un Mac cloud : cache binaire pour intégration multi-dépôts
La semaine dernière, trois dépôts ont poussé des commits en même temps, et dans la CI, le même framework interne de traitement audio a été recompilé trois fois, chaque archive prenant près de huit minutes. Au troisième échec de build, signalant un certificat de signature expiré, un collègue de garde a demandé dans le chat : « Ce truc a été compilé hier, pourquoi chaque dépôt doit-il le refaire ? » Cette remarque met le doigt sur un problème que beaucoup d'équipes connaissent : un framework binaire partagé ne devrait être compilé qu'une seule fois, mais sans mécanisme de cache, cela devient du travail redondant où chaque pipeline refait tout indépendamment.
Sur un Mac cloud dédié chez HireVPS, nous avons reconstruit ce workflow : compilation centralisée d'un XCFramework multi-slice, signature, calcul du checksum, puis dépôt dans un cache binaire local léger, permettant à plusieurs dépôts de récupérer directement l'artefact sans le recompiler chacun de leur côté. Voici la méthode concrète.
Pourquoi mettre en cache soi-même ses XCFramework
Swift Package Manager permet de référencer directement un XCFramework précompilé via binaryTarget — ce n'est pas une nouveauté en soi. Mais pour que cela fonctionne vraiment, il faut que l'artefact soit fiable, traçable et partageable entre plusieurs parties. Si chaque CI de dépôt exécute son propre archive, on obtient en réalité plusieurs binaires théoriquement identiques mais jamais réellement vérifiés comme tels. Dès qu'un léger écart apparaît dans l'environnement de build — une mise à jour mineure de Xcode, par exemple — le débogage devient laborieux. Seule une compilation centralisée avec distribution unifiée garantit que tous les consommateurs en aval reçoivent exactement le même artefact.
Retour d'expérience : tout framework interne référencé par 3 dépôts ou plus mérite un cache binaire dédié ; en dessous de 3 références, le coût de recompilation répétée peut rester inférieur à celui de la maintenance du cache.
L'avantage en ressources d'un Mac cloud
Ce workflow impose deux contraintes matérielles strictes : d'une part, un environnement macOS dédié (graphique et ligne de commande) est nécessaire pour exécuter un xcodebuild archive complet ; d'autre part, la compilation de plusieurs slices d'architecture génère une charge I/O disque importante, et les machines à ressources partagées se ralentissent facilement entre voisins. Sur HireVPS, on loue à la journée une machine M4 ou M4 Pro, on choisit un nœud, puis on reçoit des identifiants SSH dédiés avec des droits root immédiatement disponibles — pas besoin de se partager des tranches de CPU avec d'autres utilisateurs, et une compilation multi-architecture de plusieurs heures ne sera pas interrompue par une tâche voisine. Pour savoir quelle machine et quel nœud sont disponibles à un instant donné, il suffit de vérifier dans le panneau de contrôle.
Compiler un XCFramework multi-slice
Un XCFramework destiné à fonctionner à la fois sur appareil réel et en simulateur doit contenir au minimum deux slices : ios-arm64 (appareil réel) et ios-arm64-simulator (simulateur sur Apple Silicon). Si l'équipe utilise encore un Mac Intel pour lancer le simulateur, il faut ajouter ios-x86_64-simulator, sinon cette machine signalera une incompatibilité d'architecture à chaque test.
Archiver séparément les deux slices
xcodebuild archive \
-scheme AudioCore \
-destination "generic/platform=iOS" \
-archivePath build/AudioCore-iOS.xcarchive \
SKIP_INSTALL=NO \
BUILD_LIBRARY_FOR_DISTRIBUTION=YES
xcodebuild archive \
-scheme AudioCore \
-destination "generic/platform=iOS Simulator" \
-archivePath build/AudioCore-iOSSim.xcarchive \
SKIP_INSTALL=NO \
BUILD_LIBRARY_FOR_DISTRIBUTION=YES
Fusionner en un XCFramework
xcodebuild -create-xcframework \
-framework build/AudioCore-iOS.xcarchive/Products/Library/Frameworks/AudioCore.framework \
-framework build/AudioCore-iOSSim.xcarchive/Products/Library/Frameworks/AudioCore.framework \
-output build/AudioCore.xcframework
Une fois la compilation terminée, on crée une archive zip : c'est le fichier final à distribuer, qu'il ne faut plus modifier manuellement par la suite.
Signature et checksum
Il est recommandé de signer aussi les frameworks internes, afin que les consommateurs en aval puissent vérifier la provenance :
codesign --sign "Apple Distribution: Your Team" \
--timestamp \
build/AudioCore.xcframework
zip -r AudioCore-1.4.0.xcframework.zip build/AudioCore.xcframework
Une fois la signature et l'empaquetage effectués, on calcule le checksum avec la commande intégrée à SwiftPM ; cette valeur doit être ajoutée au Package.swift du dépôt en aval :
swift package compute-checksum AudioCore-1.4.0.xcframework.zip
La cause la plus fréquente d'échec de vérification du checksum est une modification du contenu du zip après empaquetage, ou un changement d'outil de compression entraînant un ordre interne des fichiers différent. Le principe est simple : une fois le checksum calculé, ce fichier zip est figé et ne doit plus être touché.
Mettre en place un cache binaire local
Nul besoin d'un service complexe : une simple arborescence de répertoires servant des fichiers statiques en téléchargement suffit :
| Niveau | Contenu | Exemple de chemin |
|---|---|---|
| Nom du framework | Un répertoire par framework interne | /cache/AudioCore/ |
| Numéro de version | Un sous-répertoire par version | /cache/AudioCore/1.4.0/ |
| Artefact | Archive zip + fichier texte de checksum | AudioCore-1.4.0.xcframework.zip, checksum.txt |
Dans le Package.swift du dépôt en aval, la référence se fait ainsi :
.binaryTarget(
name: "AudioCore",
url: "https://cache.internal.example/AudioCore/1.4.0/AudioCore-1.4.0.xcframework.zip",
checksum: "Checksum calculé à l'étape précédente"
)
En termes de planification du stockage, pour une équipe de taille moyenne, conserver 3 à 4 frameworks essentiels avec leurs 10 dernières versions chacun suffit largement. Une archive compressée pèse généralement entre 20 et 80 Mo, pour un volume total maîtrisable en quelques Go. Les anciennes versions non récupérées depuis plus de 90 jours peuvent être nettoyées régulièrement.
Intégration dans les pipelines CI multi-dépôts
La CI de chaque dépôt en aval n'exécute plus xcodebuild archive, mais se contente d'appeler swift package resolve pour télécharger la version spécifiée — le temps passe ainsi de plusieurs minutes de compilation à quelques secondes de téléchargement. Pour publier une nouvelle version, il suffit de créer un nouveau répertoire de version dans le cache et de mettre à jour la référence de checksum correspondante dans le dépôt concerné, sans toucher au code du serveur de cache lui-même.
Piège à éviter : ne jamais laisser plusieurs dépôts référencer simultanément une étiquette mutable comme « latest ». Le mécanisme de checksum exige une correspondance stricte entre URL et contenu ; autoriser l'écrasement d'un fichier sous le même numéro de version fait perdre tout son sens à la vérification d'intégrité de SwiftPM.
Liste de vérification
- Les slices couvrent-ils à la fois l'appareil réel et toutes les architectures de simulateur nécessaires ?
- Le checksum est-il calculé immédiatement après l'empaquetage, sans modification ultérieure du zip ?
- Le répertoire de version dans le cache n'est-il écrit qu'une seule fois, sans être écrasé ?
- Le checksum dans le
Package.swiften aval correspond-il bien au fichier réel ? - Les anciennes versions non récupérées depuis 90 jours ont-elles été nettoyées ?
Questions fréquentes
Quelles tranches un XCFramework doit-il contenir pour couvrir appareil et simulateur ?
Au minimum ios-arm64 pour l'appareil réel et ios-arm64-simulator pour les simulateurs Apple Silicon ; si un Mac Intel de l'équipe fait tourner des simulateurs, ajoutez aussi ios-x86_64-simulator, sinon les tests échouent immédiatement avec une incompatibilité d'architecture.
Pourquoi la vérification de la somme de contrôle du binaryTarget échoue-t-elle ?
La cause la plus fréquente est une modification manuelle du zip après empaquetage, ou l'utilisation d'un autre outil de compression qui change l'ordre interne des fichiers ; la bonne pratique est d'exécuter swift package compute-checksum une seule fois sur le zip final à distribuer, sans plus jamais le toucher ensuite.
Quel espace de stockage prévoir pour un serveur de cache binaire local ?
Pour une équipe de taille moyenne conservant les 10 dernières versions de 3-4 frameworks principaux, chaque artefact compressé pèse en général entre 20 et 80 Mo, donc quelques Go suffisent au total ; réservez une partition dédiée sur le SSD du Mac cloud et purgez les versions non téléchargées depuis plus de 90 jours.
HireVPS
Essayez dès aujourd'hui un Mac mini dédié dans le cloud
Location à la journée, identifiants SSH/VNC en 2 minutes, configuration évolutive à tout moment.