Développement iOS avec des serveurs de build Mac distants

Compiler une app iOS nécessite macOS quelque part dans la boucle, mais cela ne signifie pas que votre éditeur doit tourner sur un Mac. Grâce au support Remote-SSH de Cursor, vous pouvez conserver une expérience d'édition locale rapide sous Windows ou Linux pendant qu'une machine Mac gère la compilation, la signature et l'exécution des simulateurs. Ce guide parcourt les options d'architecture, la configuration, les commandes de build et les pièges qui font perdre le plus de temps.
Pourquoi un Mac est nécessaire
La chaîne d'outils d'Apple ne tourne que sur macOS, et plusieurs parties du flux de travail iOS en dépendent directement.
- Signature de code : les identités de signature vivent dans le trousseau macOS et servent à produire des artefacts signés.
- Profils de provisionnement : les profils qui lient un ID d'app aux appareils et certificats sont gérés via l'outillage Apple.
- Simulateur iOS : le simulateur est une application macOS ; il n'existe aucun équivalent Linux ou Windows pris en charge pour une fidélité d'appareil réelle.
- Chaîne d'outils xcodebuild : le compilateur, l'éditeur de liens et les SDK sont livrés dans Xcode sur macOS.
- Soumission à l'App Store : téléverser vers App Store Connect nécessite l'outillage Apple, généralement via Transporter ou
altool/notarytoolsur macOS.
Comme tout cela est lié à macOS, la question pratique n'est pas de savoir si vous avez besoin d'un Mac, mais où ce Mac se trouve.
Options d'architecture
Il n'y a pas de réponse unique idéale ; le bon choix dépend de la taille de votre équipe, de votre budget et de la fréquence à laquelle vous compilez.
| Option | Fonctionnement | Avantages | Inconvénients |
|---|---|---|---|
| Remote-SSH vers un Mac | Cursor se connecte via SSH ; les fichiers et le terminal vivent sur le Mac | Source de vérité unique, accès complet à la chaîne d'outils, vrai simulateur | Nécessite un Mac persistant et joignable |
| rsync / git push vers un Mac | Éditez localement, poussez vers le Mac, compilez là-bas | Fonctionne hors ligne pour l'édition, modèle mental simple | Dérive de synchronisation, gros transferts lents, pas de LSP en direct |
| Runners macOS de CI cloud | Poussez vers Git, la CI compile sur un runner macOS hébergé | Aucun matériel à posséder, reproductible, bon pour les versions | Non interactif, coût à la minute, démarrages à froid |
| Fournisseurs de Mac cloud | Louez un Mac (MacStadium, AWS EC2 Mac) via SSH | Matériel réel à la demande, évolutif | Coût horaire, surcharge de provisionnement et de démontage |
Pour le développement au quotidien, Remote-SSH vers un Mac est généralement le meilleur équilibre. La CI cloud convient aux pipelines de version. Les fournisseurs de Mac cloud conviennent aux équipes qui veulent une flotte sans posséder les postes.
Utilisez Remote-SSH pour le développement interactif, la CI cloud pour les versions et les instances Mac cloud pour l'échelle ou les courtes rafales. La plupart des équipes matures en utilisent au moins deux plutôt que de forcer une seule option à tout faire.
Configurer un Mac distant
Ces étapes s'exécutent une fois sur le Mac qui servira de serveur de build. Vous avez besoin de droits d'administration et d'une connexion réseau stable.
1. Installer Xcode et les Command Line Tools
Installez Xcode depuis le Mac App Store, puis installez les command line tools pour que xcodebuild et les binaires associés soient disponibles :
xcode-select --install
# Point the toolchain at the full Xcode app, not the standalone CLT
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
2. Accepter la licence
Sur une installation fraîche, xcodebuild refuse de s'exécuter tant que la licence n'est pas acceptée :
sudo xcodebuild -license accept
3. Créer un utilisateur de build
Évitez de compiler avec un compte personnel. Créez un utilisateur dédié afin que les identifiants et les trousseaux restent isolés :
# Create a build user (adjust the short name and full name)
sudo dscl . -create /Users/builduser
sudo dscl . -create /Users/builduser UserShell /bin/zsh
sudo dscl . -create /Users/builduser RealName "Build User"
sudo dscl . -create /Users/builduser UniqueID 510
sudo dscl . -create /Users/builduser PrimaryGroupID 20
sudo dscl . -create /Users/builduser NFSHomeDirectory /Users/builduser
sudo passwd builduser
4. Activer la connexion à distance
Activez SSH pour que le Mac accepte les connexions :
sudo systemsetup -setremotelogin on
5. Installer la clé publique SSH
Ajoutez votre clé publique locale au fichier authorized_keys de l'utilisateur de build afin de vous connecter sans mot de passe :
mkdir -p /Users/builduser/.ssh
# Paste your public key (id_ed25519.pub) into authorized_keys
chmod 700 /Users/builduser/.ssh
chmod 600 /Users/builduser/.ssh/authorized_keys
chown -R builduser:staff /Users/builduser/.ssh
Un Mac de build exposé à Internet ne doit pas autoriser les connexions par mot de passe pour l'utilisateur de build. Désactivez l'authentification par mot de passe, restreignez les adresses sources autorisées là où c'est possible, et gardez la clé privée uniquement sur votre machine locale.
Connecter Cursor via Remote-SSH
Une fois le Mac joignable, connectez Cursor à celui-ci. Le distant devient l'environnement où les extensions, les terminaux et les builds s'exécutent.
1. Installer l'extension Remote-SSH
Ouvrez Cursor, allez dans la vue Extensions (Ctrl+Shift+X) et installez Remote - SSH (l'extension packagée par Microsoft est disponible dans le marketplace de Cursor).
2. Ajouter une entrée de configuration SSH
Modifiez votre configuration SSH locale, généralement à ~/.ssh/config, et ajoutez un alias d'hôte :
# ~/.ssh/config
Host mac-build
HostName 203.0.113.10
User builduser
IdentityFile ~/.ssh/id_ed25519
ServerAliveInterval 30
ServerAliveCountMax 4
TCPKeepAlive yes
Les paramètres keep-alive réduisent les déconnexions sur les réseaux instables. Notez que l'OpenSSH natif de macOS ne prend pas en charge mosh, donc les keep-alives sont votre première ligne de défense.
3. Se connecter
Ouvrez la palette de commandes (Ctrl/Cmd+Shift+P) et exécutez Remote-SSH: Connect to Host, puis choisissez mac-build. Vous pouvez aussi l'exécuter en une seule étape :
cursor --remote ssh-remote+mac-build /Users/builduser/projects/MyiOSApp
4. La fenêtre se recharge
Cursor se recharge dans une fenêtre distante. La barre d'état affiche le nom de l'hôte distant. Tous les terminaux, tâches et serveurs de langage s'exécutent désormais sur le Mac, tandis que l'interface et le rendu restent locaux.
5. Les extensions s'installent sur le distant
Lorsque vous ouvrez un fichier Swift, Cursor vous invite à installer les extensions de langage sur le distant. Acceptez-le. L'outillage Swift, les adaptateurs de débogage et l'extension Swift appartiennent au Mac, pas à votre machine locale.
Les extensions purement d'interface comme les thèmes s'exécutent localement. Les extensions qui lancent des processus, notamment le serveur de langage Swift et tout adaptateur de débogage, s'exécutent sur le Mac distant. Cursor gère la séparation pour vous si vous le laissez faire.
Éditer localement et compiler à distance
Le schéma productif est : éditez dans Cursor, lancez les builds dans le terminal intégré, qui est un terminal sur le Mac. Les fichiers vivent toujours sur le Mac, donc il n'y a aucune étape de synchronisation susceptible de dériver.
Build de base
Compilez un scheme pour la destination iOS générique, ce qui évite d'avoir besoin d'un appareil spécifique :
xcodebuild \
-project MyApp.xcodeproj \
-scheme MyApp \
-configuration Debug \
-destination 'generic/platform=iOS' \
build
Si vous utilisez un workspace au lieu d'un projet, remplacez -project par -workspace :
xcodebuild -workspace MyApp.xcworkspace -scheme MyApp -destination 'generic/platform=iOS' build
Archiver pour la version
L'archivage produit un .xcarchive que l'étape d'export convertit en .ipa pour la distribution :
xcodebuild \
-workspace MyApp.xcworkspace \
-scheme MyApp \
-configuration Release \
-destination 'generic/platform=iOS' \
-archivePath build/MyApp.xcarchive \
archive
Exporter l'archive
Après l'archivage, exportez à l'aide d'un plist d'options d'export qui décrit la méthode de distribution :
xcodebuild -exportArchive \
-archivePath build/MyApp.xcarchive \
-exportPath build/export \
-exportOptionsPlist ExportOptions.plist
Un ExportOptions.plist pour une distribution de style App Store ressemble à peu près à ceci :
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>method</key>
<string>app-store-connect</string>
<key>teamID</key>
<string>ABCDE12345</string>
<key>signingStyle</key>
<string>automatic</string>
<key>uploadSymbols</key>
<true/>
</dict>
</plist>
Compiler pour generic/platform=iOS évite de lier le build à un appareil ou un UDID de simulateur spécifique, ce qui fait que la même commande fonctionne sur n'importe quelle machine de build sans changement de configuration.
Exécuter les tests et lire les logs
Les tests s'exécutent de la même manière que les builds, avec simplement l'action test et une destination concrète.
Exécuter les tests
xcodebuild test \
-workspace MyApp.xcworkspace \
-scheme MyApp \
-destination 'platform=iOS Simulator,name=iPhone 16,OS=latest'
Rendre les logs lisibles
La sortie brute de xcodebuild est énorme et difficile à parcourir. Faites-la passer par un formateur pour la condenser en lignes lisibles :
# xcbeautify: modern, fast formatter
xcodebuild test -workspace MyApp.xcworkspace -scheme MyApp \
-destination 'platform=iOS Simulator,name=iPhone 16' | xcbeautify
# xcpretty: older but widely available alternative
xcodebuild test -workspace MyApp.xcworkspace -scheme MyApp \
-destination 'platform=iOS Simulator,name=iPhone 16' | xcpretty
Pour un fichier de log persistant en plus d'un flux lisible, utilisez tee sur la sortie :
xcodebuild build -scheme MyApp -destination 'generic/platform=iOS' \
2>&1 | tee build.log | xcbeautify
Comme le terminal est distant, le fichier de log atterrit sur le Mac. Lisez-le depuis la même fenêtre distante avec l'explorateur de fichiers de Cursor, ou diffusez-le localement avec une seconde session SSH si vous préférez.
Prendre des captures d'écran du simulateur
Comme le simulateur s'exécute sur le Mac, vous le capturez avec xcrun simctl via SSH plutôt qu'en prenant une capture d'écran locale.
Lister et démarrer un simulateur
# List available devices
xcrun simctl list devices available
# Boot a specific simulator by UDID or name
xcrun simctl boot "iPhone 16"
# Open the Simulator UI if a desktop session is available
open -a Simulator
Installer et lancer votre app
# Install a built .app onto the booted simulator
xcrun simctl install booted ./build/Build/Products/Debug-iphonesimulator/MyApp.app
# Launch it by bundle identifier
xcrun simctl launch booted com.example.MyApp
Capturer une capture d'écran
# Save a screenshot to a file on the Mac
xcrun simctl io booted screenshot /Users/builduser/shots/home.png
Comme ce fichier est sur le distant, ouvrez-le directement dans la fenêtre distante de Cursor. L'aperçu d'image fonctionne via Remote-SSH, donc vous n'avez pas besoin de le copier en local.
simctl peut démarrer un simulateur sans interface graphique, mais certains tests pilotés par l'interface et scénarios de capture plus riches attendent une session de bureau connectée. Sur un Mac headless, gardez une session connectée persistante, ou attendez-vous à des limitations sur les fonctionnalités d'interface du simulateur.
Signature de code
La signature est la partie la plus susceptible d'échouer sur une machine fraîche, donc configurez-la délibérément.
Certificats dans le trousseau
Les identités de signature vivent dans le trousseau de l'utilisateur de build. Importez un .p12 exporté depuis votre compte développeur Apple :
security import /path/to/distribution.p12 \
-k /Users/builduser/Library/Keychains/login.keychain-db \
-P "p12-password" \
-T /usr/bin/codesign \
-T /usr/bin/security
Les options -T accordent à codesign et security l'accès à l'identité importée afin que les builds ne se bloquent pas sur une invite d'accès.
Profils de provisionnement
Placez les profils là où Xcode les attend pour l'utilisateur de build :
mkdir -p /Users/builduser/Library/MobileDevice/Provisioning\ Profiles
cp ~/profiles/MyApp_AppStore.mobileprovision \
/Users/builduser/Library/MobileDevice/Provisioning\ Profiles/
Signature automatique vs manuelle
- Signature automatique : plus simple, exige que l'utilisateur de build soit connecté à un compte développeur Apple et dispose d'un accès réseau aux services d'Apple. Bon pour un seul Mac de confiance.
- Signature manuelle : plus reproductible pour la CI. Vous spécifiez
CODE_SIGN_STYLE=Manualet pointez vers une identité et un profil précis, en évitant un comportement dépendant du compte.
xcodebuild archive \
-workspace MyApp.xcworkspace \
-scheme MyApp \
-destination 'generic/platform=iOS' \
CODE_SIGN_STYLE=Manual \
CODE_SIGN_IDENTITY="Apple Distribution: Example Corp (ABCDE12345)" \
PROVISIONING_PROFILE_SPECIFIER="MyApp AppStore Profile" \
-archivePath build/MyApp.xcarchive
Sur un Mac non surveillé, déverrouillez le trousseau de connexion avant de compiler afin que codesign puisse atteindre l'identité sans invite d'interface. Gardez le mot de passe de déverrouillage dans un coffre de secrets, jamais dans un script commité.
Comparaison des coûts
Les prix changent, donc considérez ce qui suit comme des ordres de grandeur pour la planification plutôt que des devis actuels.
| Option | Coût mensuel approximatif | Remarques |
|---|---|---|
| Posséder un Mac mini comme serveur de build | Achat du matériel, puis quasi zéro en récurrent | Le moins cher à usage régulier ; vous assurez la maintenance |
| Fournisseur de Mac cloud (MacStadium, EC2 Mac) | Horaire, souvent plusieurs centaines par mois en usage 24/7 | Évolue à la hausse ou à la baisse ; surcharge de provisionnement |
| Runners macOS de CI cloud | À la minute, facturé uniquement pendant le build | Le moins cher pour des builds occasionnels ; les démarrages à froid pèsent |
| rsync / git vers un Mac partagé | Coût du Mac uniquement | Correct pour un individu, peu pratique pour des équipes |
Un Mac mini dédié gagne généralement pour une petite équipe qui compile toute la journée. La CI cloud gagne lorsque les builds sont peu fréquents ou strictement au moment des versions. Les instances Mac cloud gagnent lorsque vous avez besoin de capacité à la demande et que vous ne voulez pas posséder le matériel.
Pièges courants
L'essentiel des difficultés de build distant provient d'un petit ensemble d'erreurs récurrentes. Vérifiez-les avant de déboguer quoi que ce soit d'exotique.
Licence non acceptée
xcodebuild échoue immédiatement avec une erreur de licence. Corrigez-la une fois pour toutes avec :
sudo xcodebuild -license accept
Mauvaise version de Xcode sélectionnée
Plusieurs installations de Xcode perturbent la chaîne d'outils. Sélectionnez explicitement celle voulue :
sudo xcode-select -s /Applications/Xcode-16.app/Contents/Developer
xcodebuild -version
Identité de signature manquante
Un build qui s'archive mais échoue à l'export n'a généralement aucune identité ou profil correspondant. Confirmez ce que le trousseau contient réellement :
security find-identity -v -p codesigning
Si la liste est vide pour l'identité dont vous avez besoin, réimportez le .p12 et vérifiez que le profil correspond à l'identifiant de bundle de l'app.
Synchronisation lente
Si vous synchronisez du code plutôt que d'éditer via SSH, préférez git à rsync. Git transfère des deltas et vous donne l'historique et la gestion des conflits ; rsync déplace des arborescences entières et dérive silencieusement. Lorsque vous éditez directement via Remote-SSH, il n'y a aucune synchronisation, ce qui est l'option la plus rapide.
Coupures réseau cassant SSH
Les builds longs sur une liaison instable peuvent faire tomber la connexion et tuer le build. Atténuez avec des keep-alives SSH dans votre configuration, et lancez les builds longs dans tmux ou screen sur le Mac pour qu'ils survivent à une déconnexion :
tmux new-session -s build 'xcodebuild archive -workspace MyApp.xcworkspace -scheme MyApp -destination "generic/platform=iOS" -archivePath build/MyApp.xcarchive'
Reconnectez-vous plus tard et rattachez-vous avec tmux attach -t build.
Simulateur non fonctionnel en headless
Certains flux de test supposent un simulateur visible. Sur un Mac headless, démarrez le simulateur avec simctl, et soit gardez une session de bureau connectée, soit acceptez une couverture réduite de l'interface du simulateur.
Si vous compilez sur un réseau que vous ne contrôlez pas entièrement, exécutez chaque xcodebuild long dans tmux. Le build continue de tourner sur le Mac que votre session SSH survive ou non, et vous pouvez vous rattacher pour le regarder se terminer.
Les builds distants transforment une contrainte de plateforme dure en un choix de flux de travail. Choisissez l'architecture adaptée à votre équipe, configurez le Mac une fois, et laissez le Remote-SSH de Cursor garder une expérience d'édition rapide pendant que le Mac fait le travail qu'exige macOS.