Что важно понять сразу
- Проблема не только в xcodebuild: решает жизненный цикл кешей и самой машины.
- GitHub-hosted почти всегда cold start; self-hosted runner работает как прогретая рабочая станция.
- Персистентные DerivedData и CocoaPods обычно дают самый большой выигрыш по времени.
- Диск и шаги подписи часто маскируются под «медленный компилятор».
- Для Flutter iOS рационально начать с аренда Mac на 48 часов, затем перейти на месячный self-hosted runner.
Локальный Mac против GitHub Actions: откуда берутся лишние минуты
Локально команды обычно сравнивают в таких условиях:
- Локально: постоянный SSD и теплый toolchain; CI: эфемерная VM и холодные зависимости.
- Локально: уже готовый signing контекст; CI: повторный keychain bootstrap.
- Локально: повторные сборки на одном хосте; CI: больше cache miss из-за веток.
В CI-пайплайне чаще всего наоборот:
- Каждый запуск — чистый workspace после checkout.
flutter build ipaилиxcodebuild archiveв Release.- На hosted runner кеши не держатся — фактически cold start.
Обычно сравнивают лишь `xcodebuild archive`, но реальные потери накапливаются раньше: checkout, pod install, bootstrap keychain, инвалидирование DerivedData. В сумме это и дает эффект 2-3x.
Пять причин, почему xcodebuild в CI медленнее в 2-3 раза
Эти причины регулярно встречаются в Flutter iOS пайплайнах на GitHub Actions и при миграции на self-hosted runner.
- 1) Stateless runner сбрасывает все dependency-слои GitHub-hosted VM создается заново для каждого job, поэтому SPM, Pods и модульные кеши не успевают стабильно прогреваться.
- 2) Плавающий путь DerivedData ломает инкрементальность Если путь меняется, xcodebuild хуже переиспользует результаты и компилирует лишнее.
- 3) Flutter plugins провоцируют холодное pod-разрешение При дрейфе lockfile `pod install` тратит минуты на разрешение и скачивание. Нужна жесткая воспроизводимость.
- 4) Подпись и keychain дают последовательный overhead Импорт сертификатов, создание и unlock keychain в CI происходят почти в каждом запуске и добавляют фиксированную задержку.
- 5) Давление на диск и inode замедляет запись Одного мониторинга CPU недостаточно. Недостаток свободного места и inode замедляет индексирование и вывод артефактов.
Таблица таймингов этапов (пример Flutter iOS)
Сначала соберите такую таблицу по 10+ прогонам, и только потом меняйте платформу или архитектуру.
| Этап | Локальный Mac | GitHub Actions | Типичная причина |
|---|---|---|---|
| flutter pub get | 0m25s | 1m10s | pub cache не прогрет |
| pod install | 1m30s | 4m45s | холодные Specs и plugin pods |
| xcodebuild compile/archive | 6m40s | 12m20s | DerivedData не переиспользуется |
| codesign/export | 0m55s | 2m00s | bootstrap keychain каждый запуск |
| итого | 9m30s | 20m15s | примерно в 2.1 раза медленнее |
Прежде чем винить xcodebuild
Перед оптимизацией xcodebuild проверьте pod install. CocoaPods в CI обычно тормозит по трём причинам:
- Не закоммичен
Podfile.lock— нестабильное разрешение зависимостей. - Нет кеша
ios/Pods— артефакты удаляются после job. - Лишний
pod repo update— при lockfile обычно не нужен.
В CI используйте pod install --deployment и храните Pods на одном SSD-пути. Self-hosted Mac mini избавляет от загрузки кешей в GitHub.
DerivedData, Pods и кеши: практичная архитектура
Для Flutter iOS наибольший эффект обычно дает фиксированный абсолютный путь DerivedData и предсказуемый CocoaPods cache. На self-hosted runner CI становится не одноразовой VM, а управляемым сборочным контуром.
Даже на GitHub-hosted помогает детерминированность путей и lockfile. Если миграция возможна, аренда Mac на Apple Silicon и переход на self-hosted обычно приближают поведение к локальному.
- Фиксируйте derivedDataPath для каждого target.
- Используйте `pod install --deployment` против дрейфа lockfile.
- Мониторьте свободный диск и inode как ключевые CI-метрики.
Паттерн исправления: стабильные пути + детерминированные зависимости
Ниже базовый, эксплуатационно устойчивый вариант без хрупких YAML-трюков.
- Добавить
concurrency— отменять устаревшие сборки ветки. - Тесты на Linux — не тратить Mac-время на
flutter test. - Персистентные Pods + DerivedData — фиксированные пути self-hosted, тонкие cache keys hosted.
- Собирать только shipping flavor — не сканировать шесть flavor ради одного IPA.
- Зафиксировать версии Xcode/Flutter — без
upgradeв 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
Этого часто хватает, чтобы вернуть 30-50% времени. Далее масштабируйте через пул self-hosted runner.
Чеклист перед merge против регрессий скорости
Запускайте после обновлений Flutter/Xcode и при любом скачке времени.
- ☐ CI собирает Release/IPA, локально сравнивают Debug
- ☐ В job есть
flutter cleanили удаление DerivedData - ☐ В логах
pod installкаждый раз много Installing - ☐ GitHub-hosted runner без локального SSD-кеша
- ☐ 16 ГБ машина с несколькими job или симуляторами
- ☐ Изменение README запускает полную iOS-сборку
- ☐ Версия Xcode в CI не совпадает с локальной
Первые три пункта — чинить кеши. Последние два — триггеры и характеристики машины.
FAQ
GitHub Actions всегда медленнее локальной сборки?
Не всегда, но для Flutter iOS это частый сценарий при холодных кеш-слоях. Self-hosted runner обычно уменьшает разрыв.
Нужно сразу мигрировать на self-hosted runner?
Сначала измерения. Если доминируют cold start потери, перенесите один workflow и сравните медиану по 10 прогонам.
Нужен дорогой сервер для ускорения?
Обычно нет. аренда Mac на Apple Silicon с устойчивыми кешами дает заметный эффект.
Как обосновать это команде?
Покажите таблицу этапов, переведите минуты в стоимость ожидания и добавьте выигрыш в предсказуемости релизов.
Связанные ссылки к кластеру
Если вы перестраиваете Flutter iOS CI под предсказуемую скорость, двигайтесь по этому порядку.
- Хаб: архитектура Flutter iOS CI и карта решений
- Кеш-стратегия: DerivedData, CocoaPods, SPM
- Self-hosted runner на cloud Mac для GitHub Actions
- iOS code signing в CI без keychain-хаоса
- Мониторинг диска и inode для долгоживущих runner
Нужна стабильная скорость iOS CI на реальном Apple Silicon?
kvmboot cloud Mac дает выделенные M-series хосты для Flutter iOS. Сначала аренда Mac посуточно для профилирования, затем месячный self-hosted runner.
Если хотите заменить CI-лотерею предсказуемыми релизами, посмотреть тарифы cloud Mac