Акция

Почему Flutter iOS CI замедляется на облачном Mac? Три уровня инвалидации кэша

CI Flutter · сборка iOS
2026-06-04 ~15 мин

Одна мысль: Flutter iOS CI — это распространение инвалидации кэша, а не три несвязанных тормоза. Сначала CocoaPods, затем DerivedData. После §0 и §0.1 ясно, что чинить на арендованном Mac или GitHub Actions macOS runner.

30 секунд (приоритет)

  1. Главный конфликт: Pods (сеть + resolve) > DerivedData > Flutter cache — сначала Pods.
  2. Одна причина: не «три слоя медленны», а промах вверх по цепочке заставляет переделывать вниз (§1).
  3. Одно правило (§0): кэш привязан к lockfile/SDK, не к дате.
  4. Мало времени: §0+§0.1+§4 CocoaPods; hot медленный → §5.
  5. §7: порядок restore важнее строк key.
Flutter iOS CI и кэш на облачном Mac
Обложка — схема; условия бенчмарка в таблице.

0. Единственный принцип кэша

До CocoaPods, DerivedData, PUB_CACHE:

CI-кэш связывает детерминизм сборки, а не «хранит файлы».

  • pubspec.lock / Podfile.lock изменение → miss → норма
  • апгрейд Xcode/Flutter → miss → норма
  • время Job, дата, суффикс → не должны сами вызывать miss

Симптом: каждый день cold start — кэш по дате или полный wipe.

0.1 Реальный порядок узких мест

Все разделы — одно суждение: слои не равны:

Time ceiling (optimize in this order):
  CocoaPods (network + resolve)  🔥🔥🔥  often ~70% of ceiling
        ↓
  DerivedData (compile increment) 🔥🔥    hot builds → ~6 min tier
        ↓
  Flutter cache (toolchain)       🔥      mainly first-run pub / engine

Итог: медленность редко три параллельные проблемыPods сначала держат потолок; без hit Pods DerivedData крутится вхолостую.

Маршрут: 10 мин → §0+§0.1+§4; cold ~15, hot медленный → §5; §6 для образа и первого pub.

1. Одна причина: распространение до IPA

Не три подсистемы — одна цепочка.

Зависло на Running pod install... — §0.1: кэш Pods / сеть. Сегменты: pub → pod → xcodebuild.

1.1 Цепочка

Читать по причинности:

  [start · primary]  CocoaPods miss / slow private specs
           │  Pod tree rebuild, Specs fetch
           ▼
  [propagate]        DerivedData forced cold (full xcodebuild)
           │
           ▼
  [end]              Slow IPA (often blamed on Flutter/Dart)

Обратно: pubspec.lock → плагины → pod → DerivedData. Restore: Pub первым; бюджет: Pods → DerivedData → Flutter.

Цитата: ~90 % «Flutter CI медленный» — сбой вверх по цепочке.

Документация: Flutter iOS deployment · CocoaPods environment · Xcode Build Settings.

2. Benchmark: веса §0.1

Таблица доказывает Pods, потом DerivedData. Типичный кейс kvmboot (средний Flutter, ~40 Pod, Release, облачный Mac M4); тот же commit.

Этап Вес cold hot Заметки
Без кэша ~28 min ~28 min Цепь разорвана
Только Pods+Specs 🔥🔥🔥 ~19 min ~12 min Макс. шаг; сначала
+ DerivedData 🔥🔥 ~11 min ~6 min Hot build
+ Flutter pub 🔥 ~10 min ~5 min Край

cold >20 → §4. cold ~11, hot >12 → §5.

Самотест: Четыре строки wall; фиксируйте commit и FLUTTER_VERSION.

3. Два агента на одном хосте

Статья-компаньон 2026-06-03: Два AI-агента на облачном Mac: изоляция Claude Code + Codex (2026-06-03)два CLI не ломают worktree. Ночной Flutter iOS CI на той же аренде Mac:

  • Разные корни кэша: CI DERIVED_DATA_PATH.
  • Разное расписание: xcodebuild + индексация → swap; 16 ГБ: агенты днём, CI ночью.
  • Без пересечений: не pod update в CI ios/Pods.

4. CocoaPods CI 🔥🔥🔥

Основной слой. ~70 % Flutter iOS build slow в pod install. §0: Podfile.lock.

4.1 Окружение

export CP_HOME_DIR="${CP_HOME_DIR:-$HOME/.cocoapods}"
export COCOAPODS_PARALLEL_CODE_DOWNLOAD=true
# persist: ~/.cocoapods/repos + ~/Library/Caches/CocoaPods

Приватные specs: GitHub Actions macOS runner / облачный Mac в регионе Git. Bitrise vs self-hosted Mac.

4.2 Дерево Pods/

  • Обычно: кэш ios/Pods.
  • Compliance: коммит Pods/, --deployment.

Без слепого pod update.

Явный pod install --verbose перед flutter build ipa.

5. DerivedData 🔥🔥

Hot ~6 мин после hit Pods.

export DERIVED_DATA_PATH="/var/ci/derived/flutter-${FLUTTER_VERSION}-xcode-${XCODE_VERSION}"
xcodebuild -workspace ios/Runner.xcworkspace -scheme Runner \
  -configuration Release -derivedDataPath "$DERIVED_DATA_PATH" \
  -destination 'generic/platform=iOS' build

Tier-очистка; диск: управление диском/inode runner.

6. Кэш Flutter 🔥

Первые загрузки.

Не restore build/ios между job; Docker-разделение.

7. Pipeline GitHub Actions

YAML §4–§6.

Блок Пути Ключи (§0)
1pub 🔥PUB_CACHEpubspec.lock + Flutter SDK
2–3pods 🔥🔥🔥~/.cocoapods + ios/PodsPodfile.lock + CP version
4deriveddata 🔥🔥$DERIVED_DATA_PATHXcode + Flutter + lockfiles
# .github/workflows/flutter-ios.yml (structure; macos-latest or self-hosted cloud Mac)
jobs:
  build-ios:
    runs-on: macos-14   # or runs-on: [self-hosted, cloud-mac]
    steps:
      - uses: actions/checkout@v4

      # ── Restore (order: Pub → Pods Specs → Pods tree → DerivedData) ──
      - name: Restore Pub Cache
        uses: actions/cache/restore@v4
        with:
          path: ${{ env.PUB_CACHE }}
          key: pub-${{ hashFiles('pubspec.lock') }}-flutter-${{ env.FLUTTER_VERSION }}

      - name: Restore CocoaPods Specs
        uses: actions/cache/restore@v4
        with:
          path: |
            ~/.cocoapods
            ~/Library/Caches/CocoaPods
          key: pods-specs-${{ env.COCOAPODS_VERSION }}

      - name: Restore Pods Tree
        uses: actions/cache/restore@v4
        with:
          path: ios/Pods
          key: pods-tree-${{ hashFiles('ios/Podfile.lock') }}

      - name: Restore DerivedData
        uses: actions/cache/restore@v4
        with:
          path: ${{ env.DERIVED_DATA_PATH }}
          key: dd-${{ env.XCODE_VERSION }}-${{ env.FLUTTER_VERSION }}-${{ hashFiles('pubspec.lock', 'ios/Podfile.lock') }}

      # ── Build ──
      - name: Flutter Pub Get
        run: flutter pub get

      - name: Pod Install
        run: cd ios && pod install --verbose

      - name: Flutter Build IPA
        run: flutter build ipa --release --export-options-plist=ios/ExportOptions.plist

      # ── Save (mirror restore; save costly layers even on failure) ──
      - name: Save Pub Cache
        uses: actions/cache/save@v4
        if: always()
        with:
          path: ${{ env.PUB_CACHE }}
          key: pub-${{ hashFiles('pubspec.lock') }}-flutter-${{ env.FLUTTER_VERSION }}

      - name: Save Pods + DerivedData
        uses: actions/cache/save@v4
        if: always()
        with:
          path: |
            ~/.cocoapods
            ~/Library/Caches/CocoaPods
            ios/Pods
            ${{ env.DERIVED_DATA_PATH }}
          key: dd-${{ env.XCODE_VERSION }}-${{ env.FLUTTER_VERSION }}-${{ hashFiles('pubspec.lock', 'ios/Podfile.lock') }}

Подпись: codesign / Notarization.

Ловушки: pod до restore; нет save при fail — if: always().

8. Диск и inode

Три слоя → 60 % на 256 ГБ; inode раньше места. матрица релизного спринта.

du -sh ~/Library/Developer/Xcode/DerivedData ~/.cocoapods "${PUB_CACHE:-$HOME/.pub-cache}" 2>/dev/null
df -h .; df -i .

9. Приёмка

① pod <90s ② hot −40 % ③ pub опционален.

Симптом Причина Действие
hot≈cold 20+ DerivedData каждый job путь/restore
только pod CDN/авторизация CP_HOME_DIR
ошибки link смешение Xcode ключ+Xcode
SSH тормозит диск/inode df

10. FAQ

10.1 Почему так медленно?

Промах CocoaPods. Сначала Pods.

10.2 Pods или DerivedData?

Pods.

10.3 Только DerivedData?

Нет.

10.4 Кэш на runner?

§7; if: always().

10.5 Два агента?

2026-06-03: worktree; здесь корни CI.

11. Итог

CocoaPods (🔥🔥🔥) → DerivedData (🔥🔥) → Flutter (🔥).

Проверка за 48 ч

После Два AI-агента на облачном Mac: изоляция Claude Code + Codex (2026-06-03) — ночной pipeline на том же Mac.

Онбординг: чеклист аренды Mac · спецификации · тарифы