Offre

Xcode Product → Archive : le pipeline complet (et pourquoi la CI bloque ici)

CI Xcode Archive · Release iOS
2026-06-15 ~14 min

En local, un clic sur Product → Archive et l'Organizer sort l'IPA. En CI, xcodebuild archive reste souvent bloqué vingt minutes, time-out ou échoue sans message clair au même endroit. L'écart ne vient pas de la maîtrise d'Xcode — il vient du fait de traiter Archive comme une boîte noire plutôt qu'un pipeline observable.

Cet article parcourt chaque étape : ce que font réellement Build → Archive → Sign → Export, comment xcodebuild reproduit l'action du menu, et un tableau en sept catégories pour les pannes que rencontrent le plus souvent les équipes iOS et Flutter sur GitHub Actions et les runners Mac auto-hébergés.

Schéma du pipeline Xcode Archive et du workflow CI
En local, Archive donne un retour visuel ; en CI, la même chaîne exige une segmentation des logs — sinon vous ne voyez que « job terminé en 40 minutes ».

À retenir

  1. Archive ≠ Build : Archive force Release + Any iOS Device et produit un .xcarchive. Un build Debug local qui passe ne garantit rien pour Archive.
  2. 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.
  3. xcodebuild archive équivaut au menu, mais la CI doit passer explicitement -scheme, -configuration Release et -destination 'generic/platform=iOS'.
  4. « 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.
  5. 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 à :

  1. Sélectionner un Scheme partagé avec la configuration Release ;
  2. Régler la Destination sur Any iOS Device (pas un simulateur) ;
  3. Lancer xcodebuild archive et écrire la sortie dans ARCHIVE_PATH ;
  4. (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 de Installing … 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_PATH sur 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 CodeSign avec Provisioning profile … doesn't match.
  • Activer -allowProvisioningUpdates par 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-list au 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 :

  • method incompatible avec le type de profil : profil development avec app-store.
  • teamID / signingCertificate manquants : 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

  1. 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.
  2. Ajouter un chronométrage en quatre segments en CI : pod installarchiveexportArchiveupload ; reporter chaque durée dans le résumé du step.
  3. Conserver les logs bruts : évitez les prettifiers trop agressifs ; les échecs de signature ont besoin des lignes CodeSign intactes.
  4. Épingler la version Xcode : verrouiller le chemin avec xcode-select ou sudo xcode-select -s ; aligner avec .xcode-version ou macos-15 du workflow.
  5. 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.

Voir les offres Mac cloud · Specs M4 · Checklist onboarding