Compiler et distribuer des apps Mac et iOS sans ouvrir Xcode
(scottwillsey.com)- Une fois la configuration initiale du compte Apple et des certificats terminée, il est possible d’automatiser la compilation, la signature et la distribution d’apps Mac et iOS uniquement avec les outils CLI internes à Xcode.app
- Dans
project.yml, XcodeGen régénère le.xcodeproj, puisxcodebuild,notarytool,stapleretdevicectlprennent en charge tout le flux, de la création du projet à la notarisation Mac et à l’installation sur iPhone - Pour la distribution Mac, un seul
scripts/release.shexécute dans l’ordre l’archivage, la signature Developer ID, la notarisation, l’agrafage du ticket, la vérification Gatekeeper et l’installation dans/Applications, en s’arrêtant immédiatement en cas d’échec - Pour une compilation et des tests rapides, on peut utiliser
CODE_SIGNING_ALLOWED=NO, mais ce build à signature temporaire ne passe pas Gatekeeper, et les droits iCloud KVS et App Group ne sont pas associés au véritable ID d’équipe - Si la clé privée de signature et le mot de passe de notarisation sont conservés dans le trousseau de session, et que les commandes et règles de distribution sont documentées dans
CLAUDE.mdouAGENTS.md, un agent comme Claude Code peut réexécuter tout le workflow
Ce que l’on peut faire sans l’interface graphique de Xcode
- Xcode lui-même doit être installé, mais il n’est pas nécessaire de le lancer ;
xcodebuild,notarytool,stapleretdevicectlsont tous des outils shell internes à Xcode.app qui s’exécutent indépendamment - Les réglages initiaux comme la connexion à l’Apple ID, la création d’un certificat Developer ID ou l’enregistrement d’un mot de passe de notarisation nécessitent l’interface graphique ou un terminal interactif
- Une fois la configuration initiale terminée, il est possible de créer le projet, compiler, archiver, exporter une app signée, notariser, vérifier, installer et déployer sur un vrai iPhone sans utiliser la souris
- Si vous bloquez sur la configuration ou l’écriture des scripts, vous pouvez fournir les exigences et les valeurs propres au projet à Claude Code ou à un autre outil de codage basé sur un LLM pour créer le workflow
Sélectionner toute la chaîne d’outils Xcode
- Il faut d’abord vérifier que le résultat de
xcode-select -pest/Applications/Xcode.app/Contents/Developer - Si les Command Line Tools autonomes sont sélectionnés, indiquez la chaîne d’outils Xcode complète avec la commande suivante
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
- Le répertoire
/Library/Developer/CommandLineToolsinstallé parxcode-select --installcontientclangetgit, mais pas les outils complets de développement d’apps comme le SDK iOS,notarytooloudevicectl - Si Xcode est déjà installé, le paquet Command Line Tools séparé n’est pas nécessaire
- L’acceptation de la licence et l’installation des composants supplémentaires peuvent aussi se faire en CLI
sudo xcodebuild -license accept
sudo xcodebuild -runFirstLaunch
Reproduire le projet avec XcodeGen
- XcodeGen gère la configuration du projet Xcode en YAML et régénère le
.xcodeprojà chaque compilation - Il peut être téléchargé depuis GitHub ou installé avec Homebrew
brew install xcodegen
- Un
.xcodeprojest un dossier qui apparaît comme un fichier sous macOS, et comme Xcode modifie sans cesse ses fichiers internes et ses références, il peut provoquer des conflits ou des changements inutiles dans le dépôt Git - En ne commitant que
project.ymldans Git et en ignorant le.xcodeprojgénéré, on garde la configuration du projet reproductible
xcodegen generate
Configurer le compte développeur Apple et les certificats
- Il faut ajouter un Apple ID dans
Settings → Accountsde Xcode - La distribution et la notarisation d’apps nécessitent un compte Apple Developer payant
- Créez un certificat de distribution dans
Settings → Accounts → Apple ID → Manage Certificates… → + → Developer ID Application - Apple Development et Developer ID Application ont des usages différents
- Apple Development sert à exécuter l’app sur ses propres appareils ou à la transférer sur un iPhone pour le débogage local
- Developer ID Application est nécessaire pour distribuer une
.appnotarisée qui passe Gatekeeper sur le Mac d’autres personnes
- Lors de la création d’un certificat Developer ID Application, le certificat et la clé privée correspondante sont installés dans le trousseau de session
- La signature réelle utilise la clé privée, qui ne peut pas être retéléchargée ; il ne faut donc pas la supprimer et il faut sauvegarder le trousseau
Enregistrer les identifiants de notarisation dans le trousseau
- La notarisation d’une app Mac consiste à envoyer l’app signée à Apple pour une analyse anti-malware
notarytools’authentifie avec un profil enregistré dans le trousseau, et lors de la première création du profil, il faut saisir de manière interactive un mot de passe spécifique à l’app
xcrun notarytool store-credentials App-Name \
--apple-id "you@example.com" \
--team-id YOUR-TEAM-ID
- Le profil de notarisation ne doit pas être emprunté à une autre app : il faut le créer avec le nom de l’app pour éviter des échecs silencieux dans d’autres environnements
- Le mot de passe spécifique à l’app est différent du mot de passe de l’Apple ID et se crée sur le site Apple ID, dans
Sign-In & Security → App-Specific Passwords - Si vous changez le mot de passe de l’Apple ID, les mots de passe spécifiques aux apps peuvent expirer sans avertissement séparé
- Si une erreur
401 invalid credentialssurvient pendant la notarisation, vérifiez d’abord l’expiration du mot de passe spécifique à l’app avant de suspecter toute la configuration
- Si une erreur
- Vérifiez que le profil est bien enregistré avec la commande suivante
xcrun notarytool history --keychain-profile App-Name
- Si vous stockez le mot de passe spécifique à l’app dans un coffre 1Password accessible à Claude Code, vous pouvez aussi confier à l’agent la création du profil de notarisation d’une nouvelle app
- Il est possible d’automatiser jusqu’à la saisie du mot de passe avec
--password, mais comme le mot de passe peut rester dans l’historique du shell, il vaut mieux le saisir une fois manuellement puis le conserver dans le trousseau ou 1Password
Séparer la configuration locale de signature et les secrets
- L’ID d’équipe et le préfixe de bundle nécessaires à la vraie signature sont stockés dans
Local.xcconfig
cp Local.xcconfig.example Local.xcconfig
BUNDLE_PREFIX = your.real.prefix
DEVELOPMENT_TEAM = YOUR-TEAM-ID
- Ajoutez
Local.xcconfigà.gitignorepour éviter que les réglages locaux propres au projet n’entrent dans le dépôt - La clé privée de signature reste dans le trousseau de session, et le mot de passe spécifique à l’app pour la notarisation reste dans le profil de trousseau de
notarytool, donc aucun secret n’est écrit dans Git
Automatiser la distribution Mac avec release.sh
- Le fichier
scripts/release.shdu dépôt exécute toute la pipeline de distribution de l’app Mac en une seule commande
./scripts/release.sh
-
Le script fonctionne dans l’ordre suivant
- Vérification préalable de l’existence de
xcodegenet du profil de notarisation - Régénération du projet avec
xcodegen generate - Création d’une archive Release avec
xcodebuild archive - Export de l’app signée Developer ID avec
ExportOptions.plistetxcodebuild -exportArchive - Compression de l’app en ZIP et soumission à Apple avec
notarytool submit --wait - Agrafage du ticket de notarisation avec
stapler staple - Vérification de l’autorisation Gatekeeper avec
spctl - Arrêt du processus existant et copie de l’app dans
/Applications - Enregistrement de l’app installée avec
lsregister - Nouvelle vérification du ticket et de l’état Gatekeeper du bundle installé
- Vérification préalable de l’existence de
-
Même si le modèle connaît la procédure de distribution générale, les informations propres au projet, comme le nom du schéma, l’ID d’équipe, le nom du profil de notarisation et l’emplacement d’installation, doivent être fournies par l’utilisateur
-
Si le premier script échoue, il faut un processus itératif de correction à partir des résultats d’exécution, mais une fois le workflow stabilisé, il suffit d’exécuter la commande de distribution sans modification supplémentaire
-
Éviter les échecs et les distributions incomplètes
set -euo pipefailarrête immédiatement le script dès qu’une commande échoue, afin de ne pas traiter un état incomplet comme un succèscd "$(dirname "$0")/.."déplace l’exécution à la racine du dépôt, quel que soit le répertoire depuis lequel le script est appelé- Le bloc de vérifications préalables contrôle
xcodegenet le profil de notarisation avant l’archivage, qui prend du temps - Le script vérifie non seulement l’app exportée, mais aussi celle copiée dans
/Applications, afin de détecter une corruption du bundle pendant la copie - Si un autre profil de notarisation est nécessaire, on peut le remplacer via une variable d’environnement
TZED_NOTARY_PROFILE=<name> ./scripts/release.sh
Transmettre les règles de distribution à l’agent
- Si
release.sheffectue la distribution en une seule commande,CLAUDE.mdouAGENTS.mdpermet à l’agent d’utiliser cette commande à chaque session sans instruction supplémentaire - Le document doit consigner la régénération du projet, les tests unitaires, le build macOS rapide et la vraie commande de distribution
xcodegen generate
swift test
xcodebuild -project YOUR-APP-NAME.xcodeproj \
-scheme YOUR-APP-NAME-macOS \
-destination 'platform=macOS' \
CODE_SIGNING_ALLOWED=NO build
./scripts/release.sh
- Il faut aussi documenter la différence entre les deux chemins de build
- Le build avec
CODE_SIGNING_ALLOWED=NOconvient à la CI et aux vérifications locales rapides - Pour distribuer réellement l’app de barre de menus, passer les attributs de quarantaine et synchroniser iCloud, il faut
release.sh, qui effectue la signature Developer ID et la notarisation
- Le build avec
- Si vous indiquez que la méthode créée pour une app doit servir de référence dans un autre dépôt, Claude Code peut répliquer la même configuration pour les apps suivantes
Build rapide non signé et tests
- Les tests unitaires basés sur Swift Package Manager s’exécutent sans build Xcode
swift test
- La vérification de compilation rapide de l’app macOS se fait avec la signature désactivée
xcodebuild -project TZed.xcodeproj \
-scheme TZed-macOS \
-destination 'platform=macOS' \
CODE_SIGNING_ALLOWED=NO build
- Les builds simulateur de l’app iOS et de l’extension widget peuvent aussi être lancés de la même manière
xcodebuild -project TZed.xcodeproj \
-scheme TZed-iOS \
-destination 'generic/platform=iOS Simulator' \
CODE_SIGNING_ALLOWED=NO build
- Un build à signature temporaire créé avec
CODE_SIGNING_ALLOWED=NOpeut servir à la compilation et à l’exécution dans le simulateur - Ce build est rejeté par Gatekeeper et, faute de préfixe d’équipe, les droits comme iCloud KVS et App Group ne sont pas associés ; il ne peut donc pas servir à la distribution
Déployer sans interface sur un vrai iPhone
- Le déploiement sur appareil iOS n’a pas d’étape de notarisation comme une app Mac ; il utilise
xcodebuildetdevicectl - L’app destinée à un vrai appareil est compilée et signée avec un certificat Apple Development et un profil de provisionnement de développement
xcodebuild -project TZed.xcodeproj \
-scheme TZed-iOS \
-destination 'generic/platform=iOS' \
-allowProvisioningUpdates \
-derivedDataPath build/ios \
archive -archivePath build/TZed-iOS.xcarchive
- La
.appgénérée s’installe en indiquant l’UDID de l’appareil connecté
xcrun devicectl device install app \
--device <DEVICE-UDID> build/ios/…/TZed.app
devicectl list devicesaffiche les appareils connectés et jumelés, ainsi que leurs UDID- L’option
-allowProvisioningUpdatesrécupère les profils de provisionnement de développement nécessaires
Fonctionnement de la signature de code sans interface graphique
-
Lors de la création d’un certificat Developer ID Application, Apple émet le certificat et le Mac génère la clé privée correspondante, puis les deux sont stockés dans le trousseau de session
-
codesign, appelé parxcodebuild, signe les binaires avec la clé privée et inclut dans l’app les certificats menant au certificat racine Apple afin de permettre la vérification du signataire -
Avec
signingStyle: automatic,xcodebuildchoisit un certificat correspondant à l’ID d’équipe et récupère chez Apple les profils de provisionnement nécessaires -
Il n’est donc pas nécessaire de commiter des profils de provisionnement dans le dépôt
-
Droits, notarisation et agrafage
- Le fichier
.entitlementsde chaque cible contient des droits comme la sandbox, le client réseau, iCloud KVS ou App Group - Les droits sont associés lors de la signature avec le véritable ID d’équipe ; iCloud et App Group ne fonctionnent donc pas correctement dans les builds à signature temporaire
- La signature et la notarisation sont deux étapes différentes
- La signature prouve qui a créé l’app
- La notarisation est la procédure par laquelle Apple analyse l’app signée à la recherche de code malveillant, puis émet un ticket
- L’agrafage attache ce ticket à l’app pour que Gatekeeper lui fasse confiance même hors ligne
- Pour une app
LSUIElement, c’est-à-dire une app de barre de menus avec interface cachée, la notarisation est nécessaire afin d’éviter les détections de XProtect
- Le fichier
-
Commandes de vérification manuelle
codesign -dv --verbose=4 /Applications/TZed.app spctl -a -vvv -t exec /Applications/TZed.app stapler validate /Applications/TZed.appcodesignvérifie le signataire et les certificatsspctlvérifie si Gatekeeper autorisera l’exécutionstapler validatevérifie que le ticket de notarisation est attaché
Outils standard exécutés par l’agent
- Aucun serveur MCP de build séparé ni plugin dédié n’est nécessaire ; Claude Code exécute les CLI standard dans un shell non interactif
- Les outils utilisés sont
xcodebuild,xcrun notarytool,xcrun stapler,spctl,codesign,devicectl,xcodegenetswift CLAUDE.mdtransmet à l’agent les règles de nommage du profil de notarisation, la différence entre build rapide et distribution réelle, ainsi que l’obligation d’utiliserrelease.shpour distribuer- Seul le premier
notarytool store-credentialsreste une étape interactive ; tout le reste peut être automatisé
Remplacer les actions de l’interface Xcode par la CLI
- La création du
.xcodeprojgérée par Xcode est remplacée parproject.ymletxcodegen generate ⌘Bou le bouton Run correspond àxcodebuild … buildProduct → Archiveest traité avecxcodebuild … archive- La distribution de l’app dans Organizer peut être remplacée par
xcodebuild -exportArchive - L’envoi depuis Organizer se fait avec
xcrun notarytool submit --wait - Pour l’attachement automatique du ticket, utilisez
xcrun stapler staple - L’installation par glisser-déposer dans
/Applicationsest gérée aveccp -Retlsregister - L’action Run sur un vrai iPhone est remplacée par
xcodebuild archiveetdevicectl device install - Une fois Xcode et XcodeGen installés, les premiers identifiants configurés, et
release.shainsi queCLAUDE.mdrédigés, les déploiements de nouveaux builds peuvent ensuite s’exécuter avec une phrase ou une commande
1 commentaires
Réactions sur Hacker News
Comme il y a beaucoup de choses qu’on ne peut pas résoudre dans un sandbox, j’exécute les agents IA directement sur mon Mac, mais l’incident où xAI a téléversé le répertoire personnel, y compris les clés SSH, me pousse à sérieusement reconsidérer ce choix
Je ne m’inquiète pas particulièrement du fait que la machine elle-même soit cassée. Dans la plupart des cas, tout est sauvegardé ou commité dans Git, donc je peux restaurer en quelques heures. En revanche, je ne veux absolument pas que le répertoire
.sshsoit envoyé à un agent IA, et il ne faut pas essayer de l’empêcher par prompt : il faut le bloquer au niveau du système. J’envisage de créer un utilisateur séparé et d’appliquer des permissions700au répertoire personnel. On a l’impression que la sécurité est revenue aux années 1990, mais en contrepartie on peut finir le travail à une vitesse sans précédent. Je n’essaie pas de blâmer les autres : moi aussi j’utilise cette méthode risquéeComme solution packagée, on peut utiliser les projets open source Tart https://tart.run/ ou VirtualBuddy https://github.com/insidegui/VirtualBuddy, ou même coder soi-même un outil qui encapsule l’API de l’OS https://developer.apple.com/documentation/virtualization. Récemment, il y a aussi eu Apple container https://github.com/apple/container
agent. Il y a un an, j’avais échoué à cause d’OAuth et du trousseau, mais récemment j’ai réussi grâce à l’indication de Claude qu’on pouvait utiliserCLAUDE_CODE_OAUTH_TOKENIl faut un outil auxiliaire pour configurer les permissions via ACL, mais cela fonctionne actuellement sur macOS. Le niveau de risque se situe quelque part entre le sandbox par défaut de Claude et une isolation complète par conteneur ou machine. À mesure que le sandbox natif de Claude s’améliorait, je doutais du rapport coût/bénéfice de ce projet de défense en profondeur, mais vu le risque de fuite du répertoire personnel, il y a largement de quoi continuer. Je ne l’ai pas encore publié en open source, mais je suis prêt à en discuter avec les personnes intéressées
Pour le plaisir, je build et teste des apps iOS uniquement sous Linux, et c’est étonnamment simple ; https://github.com/xtool-org/xtool fonctionne très bien
Sans avoir besoin de passer par TestFlight ou l’App Store, on peut aussi les installer directement sur un iPhone via USB depuis Linux. Si vous n’êtes pas sûr, vous pouvez demander à votre agent de code de créer et déployer une app iOS Hello World pour démarrer facilement
Mon projet open source Axiom aide les outils d’exécution de code à effectuer efficacement des tâches de développement pour les systèmes d’exploitation Apple
Il inclut non seulement diverses technologies et divers agents, mais aussi des outils pour LLM comme
xclog,xcprof,xcsymetxcui. Ils exposent les fonctionnalités nécessaires de manière efficace en tokens et peuvent aussi être utiles à des technologies ou agents extérieurs à Axiom. Axiom : https://charleswiltgen.github.io/Axiom/, outils CLI : https://charleswiltgen.github.io/Axiom/tools/La phrase « J’ai fait écrire à Claude Code un script qui archive sans ouvrir Xcode, effectue la signature Developer ID, la notarisation, le stapling, l’installation dans
/Applications, et s’arrête clairement à n’importe quelle étape en cas d’échec » produit un effet étrange. On a l’impression que le texte qu’on lit en ce moment même est aussi en train d’être raconté par ClaudeIl est aussi curieux de voir, à presque chaque étape du texte, revenir des indications du genre « montrez cet article à Claude Code ou à l’outil de code LLM de votre choix et laissez-le se débrouiller », « si vous ne savez pas, demandez à un LLM de vous aider à configurer », « le but d’utiliser un LLM est d’éviter le travail manuel non souhaité », « demandez-lui de créer un workflow » : on a l’impression qu’on nous dit sans cesse de redemander au LLM
Après plus d’un an à développer des apps Mac et iOS avec des agents, je trouve que cet article donne de mauvais conseils. En particulier, l’excellent Xcode MCP exige que Xcode soit lancé
Avec les outils MCP, on peut accéder à de nombreuses fonctions bien plus vite et de manière plus concise qu’avec
xcodebuild, et il est aussi possible de générer et de rendre des#Preview, ce qui devient impossible si Xcode est fermé. Le MCP de Xcode 27 et l’agent intégré contrôlent aussi très bien les simulateurs via DeviceHub. DeviceHub remplace Simulator.app et semble permettre à l’agent de comprendre et manipuler l’API d’accessibilité sans recourir à des bricolages commeaxeXcode MCP n’est pas parfait. Le problème des fenêtres d’autorisation qui s’affichent à chaque lancement d’un agent, je l’ai contourné en automatisant l’approbation avec Keyboard Maestro. Malgré cela, Xcode 27 a nettement amélioré l’utilisabilité du vibe coding dans Xcode. Désormais, au lieu de gérer MCP séparément, je lance Codex directement depuis l’interface agent de Xcode, et cet environnement d’exécution guide mieux le modèle pour qu’il exploite les outils disponibles. Au final, on obtient surtout un feedback plus rapide et une validation plus robuste
Xcode-mcp-proxy, qui sert de proxy pour Xcode MCP, clique sur les boîtes de dialogue et rétablit aussi les connexions. Je me demande si vous utilisez Peekaboo pour les tests d’apps macOS, ou bien une automatisation classique par captures d’écranSi vous voulez éviter au maximum de toucher aux IDE iOS et Android, React Native et Expo sont aussi un bon choix
Xcode et Android Studio ne servent qu’à installer les simulateurs d’appareil, et pour le reste jusqu’au déploiement sur les stores, c’est la toolchain Expo qui s’en charge. Même sous Windows, où il est impossible d’installer Xcode, on peut exécuter directement une app iOS sur un iPhone avec les apps gérées d’Expo. Comme point de départ basé sur RN et Expo, Ignite est excellent et bien maintenu : https://github.com/infinitered/ignite
À strictement parler, on utilise toujours Xcode. Xcode est globalement une GUI très buggée qui encapsule plusieurs utilitaires et apps UNIX de bas niveau plutôt stables
La publication d’apps en CLI est une pratique ancienne, et c’était déjà possible au moins depuis 2012, quand j’ai publié ma première app Xcode
Au moins pour une partie du débogage, tout le monde doit finir par utiliser Xcode
Pour la compilation, les agents peuvent déjà faire la plupart des choses en ligne de commande, et avec des instructions explicites selon le contexte, ils peuvent compiler plus vite. Si vous utilisez les groupes de synchronisation du système de fichiers de Xcode, XcodeGen peut aussi n’être qu’un travail supplémentaire inutile
Sur iOS, ce que je recommanderais surtout, c’est d’ajouter à l’agent des fonctions App Store Connect : https://github.com/rorkai/App-Store-Connect-CLI. Non seulement cela évite de devoir laisser Xcode ouvert en permanence, mais aussi d’avoir à rester près de son MacBook. On peut modifier depuis l’iPhone avec Codex, demander à
ascde lancer un build et l’envoi sur TestFlight, télécharger et exécuter la nouvelle version, puis recommencerLe fait que les développeurs qui n’ont pas le budget pour acheter un Mac soient pratiquement exclus du développement iOS me dérange toujours. À chaque hausse des prix des produits Apple, il y a davantage de personnes qui ne peuvent plus participer au développement
Si l’on fait du vibe coding pour des apps, Expo aurait été un meilleur choix à long terme. Comme c’est basé sur React, les données d’entraînement des LLM contiennent bien plus de code React que de Swift, et la qualité du code produit est aussi meilleure
Tout peut se faire en ligne de commande, et comme c’est une webview, le débogage est facile. Une fois compilée en app iOS native, l’app donne une impression comparable à celle des autres apps natives. En combinant Expo et Fastlane, on peut automatiser entièrement la soumission et la distribution sur iOS, au point d’envoyer une nouvelle version sur l’App Store avec une seule commande