Offre

Pourquoi xcodebuild est 2 à 3 fois plus lent en CI qu'en local ?

CI Flutter iOS CI
2026-06-08 ~12 min

Pour les équipes Flutter iOS, ce guide relie mesures terrain, causes racines et décisions d'architecture runner/caches.

Flutter iOS CI : écart de temps entre local et CI
Un build local peut sembler sain, mais en GitHub Actions l'environnement sans état refroidit toute la chaîne autour de xcodebuild et pousse souvent la durée totale à 2-3x.

Ce qu'il faut retenir d'abord

  1. Le problème n'est pas uniquement xcodebuild : cycle de vie des caches et de la machine compte davantage.
  2. GitHub-hosted repart à froid, un runner auto-hébergé garde mieux un état chaud.
  3. La persistance DerivedData + CocoaPods enlève souvent la plus grosse part du temps perdu.
  4. La pression disque et le bootstrap signing se déguisent en lenteur de compilation.
  5. Pour Flutter iOS, commencez par louer un Mac 48 h pour profiler, puis passez en runner auto-hébergé mensuel.

Mac local vs GitHub Actions : d'où viennent les minutes en plus

En local, la plupart des équipes comparent dans ce contexte :

  • Local : SSD persistant + dépendances chaudes ; CI : VM éphémère + graphe froid.
  • Local : même trousseau/signature ; CI : bootstrap keychain à chaque exécution.
  • Local : builds répétés sur la même machine ; CI : plus de miss cache inter-branches.

En CI, le pipeline fait généralement l'inverse :

  • Checkout sur un workspace propre à chaque run.
  • flutter build ipa ou xcodebuild archive en Release.
  • Sur runner hébergé, les caches ne tiennent pas — redémarrage à froid.

Beaucoup d'équipes comparent seulement `xcodebuild archive`. Pourtant checkout, pod install, keychain, invalidation DerivedData s'additionnent. C'est ce cumul qui produit l'impression 2-3x.

Pourquoi xcodebuild devient 2-3x plus lent en CI

Ces cinq facteurs reviennent dans la plupart des pipelines Flutter iOS, sur GitHub Actions comme en migration auto-hébergée.

  1. 1) Les runners sans état réinitialisent chaque couche Sur GitHub-hosted, VM neuve à chaque job : SPM, Pods, module cache et index ne restent pas durablement chauds.
  2. 2) Chemin DerivedData instable = perte d'incrémental Si le chemin change, xcodebuild ne réutilise plus correctement les artefacts et recompilera davantage.
  3. 3) Résolution Flutter plugins + Pods à froid Les variations de lockfile et de plugins rendent `pod install` long et imprévisible sans discipline de déploiement.
  4. 4) Signature et keychain ajoutent une latence sérielle Import certificats, création/déverrouillage keychain prennent du temps fixe à chaque exécution CI.
  5. 5) Pression disque/inode ralentit l'écriture Regarder seulement CPU masque le vrai problème : peu d'espace libre et saturation inode freinent index et sorties build.

Table des temps par étape (exemple Flutter iOS)

Mesurez d'abord ce tableau sur au moins dix runs avant de changer d'architecture.

Étape Mac local GitHub Actions Cause fréquente
flutter pub get0m25s1m10scache pub insuffisamment chaud
pod install1m30s4m45sSpecs/plugins résolus à froid
xcodebuild compile/archive6m40s12m20sDerivedData non réutilisé
codesign/export0m55s2m00sbootstrap keychain répété
total9m30s20m15senviron 2.1x plus lent

Avant d'accuser xcodebuild

Avant d'optimiser xcodebuild, regardez pod install. CocoaPods ralentit en CI pour trois raisons récurrentes :

  • Podfile.lock non commité — résolution imprévisible.
  • Pas de cache ios/Pods — artefacts supprimés après le job.
  • pod repo update inutile — le lockfile suffit en CI.

Utilisez pod install --deployment et gardez Pods sur le même SSD. Un Mac mini self-hosted évite l'upload/download de caches GitHub.

DerivedData, Pods et cache : architecture pragmatique

Sur Flutter iOS, le plus rentable est souvent de fixer un chemin absolu DerivedData et de rendre CocoaPods prévisible. Avec runner auto-hébergé, la CI devient une machine durablement optimisable.

Même en GitHub-hosted, imposez des chemins et lockfiles déterministes. Si possible, louer un Mac Apple Silicon et passer en auto-hébergé rapproche le comportement du local.

  • Un derivedDataPath stable par cible applicative.
  • `pod install --deployment` pour éviter la dérive lockfile.
  • Suivre espace disque libre et inodes comme métriques CI majeures.

Pattern de correction : chemins stables + restore déterministe

Le snippet ci-dessous reste volontairement simple : robustesse d'exploitation avant optimisation fragile.

  1. Ajouter concurrency — annuler les builds obsolètes sur la branche.
  2. Tests sur Linux — ne pas consommer le Mac pour flutter test.
  3. Persister Pods + DerivedData — chemins fixes en self-hosted, clés cache fines en hosted.
  4. Builder seulement le flavor livré — pas six flavors pour un IPA.
  5. Épingler Xcode/Flutter — pas d'upgrade dans la CI.
# .github/workflows/ios-ci.yml
jobs:
  ios:
    runs-on: [self-hosted, macOS, ARM64, flutter-ios]
    steps:
      - uses: actions/checkout@v4
      - name: Restore caches
        run: |
          mkdir -p "$HOME/Library/Caches/CocoaPods"
          mkdir -p "$HOME/.pub-cache"
      - name: Build with stable DerivedData path
        run: |
          flutter pub get
          cd ios
          xcodebuild             -workspace Runner.xcworkspace             -scheme Runner             -configuration Release             -destination 'generic/platform=iOS'             -derivedDataPath "$HOME/ci-derived/runner"             CODE_SIGNING_ALLOWED=NO
      - name: Keep pod install deterministic
        run: |
          cd ios
          pod install --deployment

Cette base suffit souvent pour récupérer 30-50% du temps, puis affiner via un pool de runners auto-hébergés.

Checklist pré-merge contre les régressions de vitesse

À lancer après chaque upgrade Flutter ou Xcode.

  • ☐ CI en Release/IPA, comparaison locale en Debug
  • ☐ Job avec flutter clean ou suppression DerivedData
  • ☐ Logs pod install avec Installing massif à chaque run
  • ☐ Runner GitHub-hosted sans cache SSD local
  • ☐ Machine 16 Go avec plusieurs jobs ou simulateurs
  • ☐ Changement README déclenche build iOS complet
  • ☐ Version Xcode CI différente du poste local

Cochez les trois premiers : priorité cache. Les deux derniers : déclencheurs et taille machine.

FAQ

GitHub Actions est-il toujours plus lent que le local ?

Pas toujours, mais c'est fréquent en Flutter iOS quand les couches de cache restent froides. L'auto-hébergement réduit souvent l'écart.

Faut-il migrer immédiatement vers un runner auto-hébergé ?

Mesurez d'abord. Si le coût de cold start domine, migrez un workflow pilote et comparez la médiane sur dix runs.

Faut-il du matériel coûteux ?

Souvent non. louer un Mac Apple Silicon avec cache persistant apporte déjà un gain net.

Comment convaincre l'équipe ?

Montrez les timings par étape, convertissez les minutes en coût d'attente et ajoutez le bénéfice de prédictibilité release.

Pour corriger durablement la lenteur xcodebuild en CI, suivez cet ordre.

Besoin d'une vitesse iOS CI stable sur Apple Silicon réel ?

kvmboot cloud Mac propose des hôtes dédiés M-series pour Flutter iOS. Commencez par louer un Mac à la journée, puis passez au runner auto-hébergé mensuel.

Si vous voulez remplacer la variabilité CI par des releases prévisibles, voir les offres cloud Mac