À retenir
- Archive ≠ Build : Archive force Release + Any iOS Device et produit un
.xcarchive. Un build Debug local qui passe ne garantit rien pour Archive. - Les blocages CI se regroupent en quatre segments : Scheme/config, cache de compilation, signature & trousseau et exportArchive — identifiez le segment dans les logs avant toute correction.
xcodebuild archiveéquivaut au menu, mais la CI doit passer explicitement-scheme,-configuration Releaseet-destination 'generic/platform=iOS'.- « Bloqué sans sortie » signifie le plus souvent attente d'autorisation trousseau, téléchargement de profil de provisioning ou swap mémoire — pas un xcodebuild mort.
- Les runners Mac cloud auto-hébergés avec DerivedData persistant et trousseau de signature dédié sont souvent un ordre de grandeur plus stables qu'un Archive à froid sur runner hébergé éphémère.
1. Archive vs Build : ce qui change vraiment
Beaucoup d'équipes résument les échecs CI par « xcodebuild a planté ». En réalité, Cmd+B Build et Product → Archive suivent deux chemins distincts :
| Dimension | Build (⌘B) | Archive |
|---|---|---|
| Configuration typique | Debug (dev local) | Release (App Store / TestFlight) |
| Destination | Simulateur ou appareil branché | Any iOS Device (arm64) |
| Sortie | .app dans DerivedData |
.xcarchive + IPA exportable |
| Signature | Certificat de dev peut suffire pour compiler | Certificat de distribution + profil de provisioning obligatoires |
| Optimisation | Faible — compilation rapide | Optimisation complète — compilation nettement plus longue |
« 3 minutes en local » et « 20 minutes en CI puis échec » ne se contredisent pas : vous tournez peut-être Debug + simulateur en local, alors que la CI enchaîne Release + architecture appareil + chaîne de signature complète. Alignez ce que vous comparez avant d'optimiser. Pour l'écart de temps de compilation, voir pourquoi xcodebuild est 2 à 3 fois plus lent en CI qu'en local.
2. Du menu au CLI : l'équivalent de Product → Archive
Un clic sur Product → Archive dans l'interface Xcode correspond grosso modo à :
- Sélectionner un Scheme partagé avec la configuration Release ;
- Régler la Destination sur Any iOS Device (pas un simulateur) ;
- Lancer
xcodebuild archiveet écrire la sortie dansARCHIVE_PATH; - (Optionnel) Distribute App dans l'Organizer → équivalent de
xcodebuild -exportArchive.
Commande Archive minimale pour la CI (adaptez les chemins à votre projet) :
xcodebuild archive \
-workspace MyApp.xcworkspace \
-scheme MyApp \
-configuration Release \
-destination 'generic/platform=iOS' \
-archivePath "$RUNNER_TEMP/MyApp.xcarchive" \
CODE_SIGN_STYLE=Manual \
DEVELOPMENT_TEAM=XXXXXXXXXX \
| tee archive.log
Trois paramètres souvent oubliés :
-destination 'generic/platform=iOS': sans lui, xcodebuild peut retomber sur un simulateur — Archive échoue ou devient imprévisible.-archivePath: doit être inscriptible ; des problèmes de droits sur les répertoires temporaires des runners hébergés provoquent « compilé mais échec d'écriture de l'archive ».- Le Scheme doit être Shared et versionné dans Git : un Scheme local non partagé n'existe pas après le checkout CI →
scheme not found.
Références officielles : Apple — Building your app, Distributing your app.
3. Pipeline en quatre étapes (vue d'ensemble)
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 1. Resolve │ → │ 2. Archive │ → │ 3. Sign │ → │ 4. Export │
│ SPM/Pods │ │ Release full │ │ Certs/profils│ │ IPA / Upload │
│ DerivedData │ │ → .xcarchive │ │ Auth trousseau│ │ TestFlight │
└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘
pod install xcodebuild codesign exportArchive
souvent 5–15 min archive 8–25 min zone de blocage erreurs plist fréquentes
Quand la CI « bloque ici », demandez-vous d'abord : à quelle étape correspond la dernière ligne de log ? Arrêt sur CompileSwift = compilation ; sur CodeSign = signature ; sur exportArchive = config d'export. Mélanger les étapes mène à des purges de cache sans fin.
4. Étape 1 : Resolve & Compile (pourquoi la CI est plus lente)
La résolution des dépendances et la compilation avant Archive sont « chaudes » en local et « froides » en CI :
- CocoaPods / SPM : la CI relance
pod installà chaque job — logs remplis deInstalling …et dix minutes perdues avant même Archive. Voir propagation du cache Flutter iOS CI. - DerivedData à froid : compilation Release complète sans cache = recompilation de chaque module natif Pod. Épingler
DERIVED_DATA_PATHsur un runner auto-hébergé SSD divise souvent le second Archive par deux. - Pression mémoire : Archive pic plus haut que Debug ; une machine 16 Go avec jobs parallèles swap — CPU bas mais rien n'avance. Voir gouvernance mémoire et swap du runner.
Vérification rapide : si les logs montrent beaucoup de CompileC / SwiftCompile sur des pods tiers que vous n'avez pas touchés, corrigez le cache d'abord — pas la signature.
5. Étape 2 : Archive (génération du .xcarchive)
Après un xcodebuild archive réussi, vous devriez voir cette structure à -archivePath :
MyApp.xcarchive/
Info.plist
Products/Applications/MyApp.app
dSYMs/...
Modes d'échec fréquents :
- Action Archive du Scheme sans cible cochée : un autre Scheme compile en local, mais celui de la CI n'archive rien.
- Multi-cibles / extension mal configurée : l'app principale passe, la signature de la Notification Service Extension échoue, Archive entier en échec.
- Conflit numéro / version de build : la CI n'incrémente pas
CFBundleVersion; l'upload échoue plus tard mais on accuse « Archive lent ».
Validez immédiatement : ls -la "$ARCHIVE_PATH" et plutil -p "$ARCHIVE_PATH/Info.plist" pour confirmer ApplicationProperties — n'attendez pas l'échec d'export pour découvrir une archive incomplète.
6. Étape 3 : Code Sign (le piège n°1 en CI)
En local, Archive peut demander l'accès au trousseau ; vous cliquez « Toujours autoriser » et le pipeline repart. La CI sans surveillance n'a pas cette étape, d'où :
errSecInternalComponent,User interaction is not allowed: clé privée dans le trousseau login ; l'utilisateur CI n'a pas d'autorisation GUI.- Profil expiré / Bundle ID incohérent : le log s'arrête sur
CodeSignavecProvisioning profile … doesn't match. - Activer
-allowProvisioningUpdatespar erreur : exige une connexion Apple ID interactive ; les environnements headless attendent indéfiniment. - Trousseau non déverrouillé : le job oublie
security unlock-keychain/set-key-partition-listau démarrage.
Recommandé sur Mac cloud / runners auto-hébergés : créer un fichier trousseau dédié (pas le trousseau login), importer le certificat de distribution + clé privée, déverrouiller et le définir par défaut au début du job. Chaîne complète : CI iOS Mac cloud : codesign et frontières du trousseau.
# Début du job (mot de passe via CI Secret)
KEYCHAIN=$RUNNER_TEMP/ci-signing.keychain-db
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN"
security import dist.p12 -k "$KEYCHAIN" -P "$P12_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN"
security list-keychains -s "$KEYCHAIN" login.keychain-db
Avec fastlane match ou Xcode Cloud pour les credentials, le principe reste le même : le processus CI doit accéder à la clé privée sans interaction — pas seulement « le certificat est quelque part sur la machine ». Voir fastlane match.
7. Étape 4 : Export IPA (exportArchive)
Archive ne produit que le .xcarchive ; l'upload TestFlight exige encore l'Export. Distribute App dans le menu correspond à :
xcodebuild -exportArchive \
-archivePath "$RUNNER_TEMP/MyApp.xcarchive" \
-exportPath "$RUNNER_TEMP/export" \
-exportOptionsPlist ExportOptions.plist
La clé method dans ExportOptions.plist fixe le type d'export (app-store, ad-hoc, development, etc.). Problèmes CI courants :
methodincompatible avec le type de profil : profil development avecapp-store.teamID/signingCertificatemanquants : erreurs Manual signing à l'export, prises pour un Archive lent.- Upload mélangé à l'export : latence réseau
altool/ Transporter comptée comme « timeout Archive ».
Séparez archive, export et upload en trois étapes chronométrées. Upload via xcrun altool --upload-app ou xcrun notarytool (distribution macOS) — découplé d'Archive.
8. Sept causes racines : symptôme / étape / action
| Symptôme / mot-clé log | Étape | Première action |
|---|---|---|
| Long silence, CPU bas | Sign | Vérifier déverrouillage trousseau ; désactiver provisioning auto dépendant du GUI |
scheme 'Foo' not found |
Pré-vol | Marquer Scheme Shared et committer ; vérifier avec -list en CI |
No profiles for … were found |
Sign | Vérifier Bundle ID ; confirmer profils en repo ou fetch match réussi |
CompileC massif sur Pods non modifiés |
Compile | Persister DerivedData ; éviter clean dans le job |
pod install > 8 minutes |
Resolve | Mettre en cache ios/Pods ; utiliser pod install --deployment |
| Job tué par la plateforme (sans stack trace) | Global | Augmenter timeout ; vérifier swap / disque plein (gouvernance disque) |
exportArchive failed + erreurs plist |
Export | Exécuter l'export isolément ; vérifier method dans ExportOptions.plist |
En astreinte : parcourez les logs depuis la fin pour le premier error:, puis croisez avec le tableau — plus rapide qu'un flutter clean généralisé.
9. Runbook de dépannage : segmenter d'abord, corriger ensuite
- Reproduire Release Archive en local : Product → Scheme → Edit Scheme → Archive en Release ; Destination = Any iOS Device. Si ça échoue en local, inutile de brûler des minutes CI.
- Ajouter un chronométrage en quatre segments en CI :
pod install→archive→exportArchive→upload; reporter chaque durée dans le résumé du step. - Conserver les logs bruts : évitez les prettifiers trop agressifs ; les échecs de signature ont besoin des lignes
CodeSignintactes. - Épingler la version Xcode : verrouiller le chemin avec
xcode-selectousudo xcode-select -s; aligner avec.xcode-versionoumacos-15du workflow. - Validation sur 48 h : archiver deux fois le même commit ; le second run doit être nettement plus rapide (cache chaud). Sinon, inspecter mémoire et disque.
Pour un sprint de release ou une capacité de build temporaire, envisagez un Mac distant court terme pour les sprints de release plutôt que de lutter contre les timeouts Archive sur runners hébergés éphémères.
10. Modèle GitHub Actions
jobs:
archive-ios:
runs-on: [self-hosted, macOS, ios] # ou macos-14 hébergé
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- name: Install pods
run: pod install --deployment
working-directory: ios
- name: Archive
run: |
set -o pipefail
xcodebuild archive \
-workspace ios/MyApp.xcworkspace \
-scheme MyApp \
-configuration Release \
-destination 'generic/platform=iOS' \
-archivePath "$RUNNER_TEMP/MyApp.xcarchive" \
| tee archive.log
env:
DEVELOPER_DIR: /Applications/Xcode_16.4.app/Contents/Developer
- name: Export IPA
run: |
xcodebuild -exportArchive \
-archivePath "$RUNNER_TEMP/MyApp.xcarchive" \
-exportPath "$RUNNER_TEMP/export" \
-exportOptionsPlist ios/ExportOptions.plist
Pour le routage par labels du runner auto-hébergé, le keepalive launchd et l'isolation utilisateur CI, voir configuration runner Mac mini auto-hébergé et vue d'ensemble Flutter + GitHub Actions.
11. Conclusion
Product → Archive n'est pas « un build un peu plus long » — c'est la chaîne complète compilation Release + archive + signature distribution + export optionnel. Quand la CI bloque ici, Xcode n'est rarement en panne. Le plus souvent, les équipes appliquent des habitudes Debug à Release, traitent la signature comme un sujet GUI local uniquement, ou fusionnent export et archive en une seule étape opaque.
Prochaines actions concrètes : valider Archive en local → ajouter le chronométrage CI en quatre segments → trousseau dédié + cache persistant → puis ajouter des machines ou monter en spec. Pour les équipes Apple, un runner Mac cloud auto-hébergé économise souvent plus de temps total que des ajustements de cache sur runner hébergé — parce qu'Archive exige un chemin chaud stable, pas un chip le plus rapide en one-shot.
Faire tourner tout le pipeline Archive sur Mac cloud
Bare metal M4 dédié pour xcodebuild archive, un trousseau de signature dédié et un DerivedData persistant. Location à la journée pour benchmarker temps et stabilité Archive ; passage au mensuel quand vous voulez un runner permanent.