VPSSpark Блог
← К дневнику разработки

До Phone 18 мы заново прогнали iOS CI/CD: Xcode 27, iOS 27, GitHub Actions и Apple Silicon Runner — журнал граблей

Заметки с сервера · 2026.09.11 · ~12 мин

Частый поиск: Xcode 27 · iOS 27 · xcode-27 · Apple Silicon · Phone 18 CI

Крупный план экрана ноутбука с кодом на деревянном столе
Сначала закрепите образ runner и доступные симуляторы — маркетинговое имя Phone 18 подождёт.

За неделю до релиза Phone 18 в переговорке никто не обсуждал фокусные расстояния камеры — все смотрели на ленту Actions, усыпанную красными точками. Xcode 27, SDK iOS 27, хостед-лейбл GitHub xcode-27 плюс несколько только что подвешенных self-hosted runner’ов на Apple Silicon: четыре линии в одну неделю. Съедет любая — и скриншоты App Store, и внешняя TestFlight-бета встанут вместе. Мы пересобрали весь iOS CI/CD с «зеленеет» на «держит релизную неделю» и записали здесь реальные грабли.

Временной якорь жёсткий: 10 сентября 2026 GitHub в Changelog пишет, что образ runner’а Xcode 27 теперь крутится на macOS 27 — лейблы те же xcode-27 / xcode-27-xlarge, и есть только arm64. Job’ы, которые накануне «случайно зеленели» на базе macOS 26, после смены образа могут лечь пластом из‑за версии ОС, имени симулятора или дискового запаса. Окно GA Golden Gate и срез моделей — во внутренней статье macOS 27 Golden Gate: дата релиза и поддерживаемые модели; здесь только сторона CI: куда наступаешь, как зачищаешь.

9.10
GitHub: xcode-27 перешёл на macOS 27
arm64
Хостед xcode-27 только Apple Silicon
4 грабли
Лейблы · симулятор · кэш · диск/очередь

Зачем перед релизом прогонять всю цепочку — а не просто поправить matrix

Мышечная память многих команд: сменить xcode-version с 26 на 27, в destination поставить iPhone 18. Перед окном Phone 18 так почти всегда взрывается. Три слоя. Первый: SDK и образ runner’а не выходят синхронно — локально уже Xcode 27 GM, хостед-образ ещё в public preview или только что уехал с базы macOS 26 на 27. Второй: таблица устройств симулятора меняется. В runtime iOS 27 захардкоженного имени может не быть; name=iPhone 18 сразу даёт Unable to find a device matching the provided destination. Третий: cache key для DerivedData, SwiftPM и CocoaPods, если всё ещё привязан к путям «до большого скачка Xcode», попадание в старые артефакты — это вчерашний мусор в сегодняшнем компиляторе.

Мы разводим приёмку: «собирается» и «держит релизную неделю». Первое — зелёный PR-job. Второе — concurrency, диск, сертификаты и холодный старт Simulator не топчут друг друга на пике. Хостед — сначала документация GitHub-hosted runners; self-hosted — по лейблам разнести быстрый PR-фидбек и тяжёлые Archive — та же логика, что в GitHub Actions тормозит iOS/Xcode CI: новый подход, только триггер теперь Phone 18.

Четыре линии граблей iOS CI перед Phone 18: лейблы, симулятор, кэш, диск и очередь
Порядок: сначала зафиксировать runs-on и образ, затем динамически выбрать симулятор, инвалидировать старый кэш — и только потом self-hosted пул и дисковый запас.

Грабля 1: всё ещё macos-latest — или xcode-27 значит «подойдёт любой Mac»

macos-latest осенью 2026 всё ещё годится для «как-то бежит», но не годится как единственная поверхность приёмки в неделю Phone 18. Это политика дефолтного образа GitHub, а не «уже есть SDK iOS 27 + нужный Simulator». Для Xcode 27 явно пишите runs-on: xcode-27 (тяжёлые job’ы — xcode-27-xlarge), а не надейтесь, что maxim-lobanov/setup-xcode спасёт на кривой базе — без runtime даже правильный Xcode.app не вытянет simctl.

Тоньше: Changelog подчёркивает только arm64. Если в оргe остались Intel self-hosted узлы или runs-on: [self-hosted, macOS] читается как «любой Mac берёт Xcode 27 job», matrix превращается в «иногда зелёный, иногда красный» — зелёный попал на Apple Silicon, красный на Intel, где toolchain не встанет. Лейблы — реальное готовое множество, например [self-hosted, macOS, ARM64, xcode27], а неапгрейженные узлы с лейбла снять.

Дрейф лейблов опаснее дрейфа кода
В релизную неделю страшен не compile error, а runner-лейбл, смысл которого сменился без правки доки. Кто берёт xcode-27, кто compat-линию macos-26 — в runbook и раз в неделю сверять на странице Runners в Actions.

Грабля 2: destination жёстко на iPhone 18 — в образе ещё нет этого «телефона»

Маркетинговый Phone 18 и имя устройства в Simulator — разные таблицы. В actions/runner-images runtime iOS 27.0 сейчас перечисляет серию iPhone 17, iPhone Air, несколько iPad — без гарантии, что в день презентации появится симулятор с именем iPhone 18. YAML вида -destination 'platform=iOS Simulator,name=iPhone 18,OS=27.0' — самая частая самострельная рана перед релизом.

Воспроизводимо: в начале job’а xcrun simctl list devices available -j, разобрать JSON, отсортировать «runtime iOS 27 + имя начинается с iPhone», взять одну машину, UDID в $GITHUB_OUTPUT, дальше xcodebuild test только с id=…. Мультидевайс-matrix: пересечение доступного списка ∩ ваш whitelist; нет устройства — skip и алерт, а не красная вся Release. UI-тесты: первый холодный старт новой ОС часто съедает лишние минуты — timeout всё ещё как при Xcode 26 на 20 минут, и редкий флейк становится системным.

ЗаписьРиск релизной неделиБолее стабильная замена
name=iPhone 18,OS=27.0Имени нет → failДинамический выбор по UDID
OS=27.0 в жёсткий пинОбраз может быть 27.0.1Префикс runtime или последний доступный
Только macos-latestSDK/симулятор неопределённыЯвная приёмка на xcode-27
Intel + Apple Silicon вперемешкуОдин job, спорадические arch-failРазнести пулы, линия 27 только ARM64

Грабля 3: cache key не инвалидировали с Xcode 27 — зелёный это «старый мир»

На major-апгрейде самая убедительная иллюзия «починили» — из кэша. SwiftPM SourcePackages, CocoaPods Pods, свои tarball DerivedData: если в key только ветка или hash lockfile без $(xcodebuild -version) / версии образа, Actions радостно restore’ит артефакты эпохи Xcode 26. Симптомы: нет символов на линковке, падает Swift driver, тесты собираются, но падает проверка module cache.

Мы жёстко кладём в key три сегмента: короткий номер Xcode, версия runner image (хостед — из env или доки), плюс Package.resolved / Podfile.lock. В день апгрейда меняем префикс — лучше +8–12 минут холодного старта, чем в релизную неделю разбирать, почему тот же commit локально зелёный, а в CI нет. Self-hosted Apple Silicon: общий DerivedData между job’ами без каталогов по версии Xcode смешивает preview и GM.

Критерий приёмки
Рост cache hit rate — не метрика успеха апгрейда; «холодный кэш и стабильно зелёный» — да. Перед релизом хотя бы одна nightly с принудительным miss.

Грабля 4: Apple Silicon runner’ы есть — диск и очередь возвращают вас на старт

Xcode 27 + runtime iOS 27 + несколько Simulator — убийцы диска. Хостед-образ xcode-27 уже тяжёлый; self-hosted Mac mini с Xcode 26, двумя бетами и DerivedData трёх приложений часто взрывается ближе к концу Archive: No space left on device. Перед релизной неделей: одна «текущая приёмка» Xcode, whitelist runtime’ов, DerivedData чистить по job’у, ночной cron с df -h и xcrun simctl delete unavailable.

Очередь не исчезает от Silicon. Перед презентацией matrix раздувается с «один SDK» до «compat 26 + приёмка 27» — хостед concurrency и self-hosted слоты забиваются одновременно. Быстрый PR-фидбек и TestFlight Archive обязаны жить на разных лейблах: минуты для первого, очередь ок для второго, но без блокировки первого. Кто берёт слот на пике — это дизайн лейблов, а не M-серия на постере.

Поведение toolchain — по документации Apple Xcode; какие Simulator в образе — по Readme runner-images за эту неделю, не по скриншоту блога прошлого года.

Runbook релизной недели: как мы реально переключались

День 1: приёмочный job прод-PR явно на xcode-27, оставить compat-сентинел macos-26 (или предыдущее поколение) — 27 красный блокирует merge, 26 красный только алерт. День 2: все test destination на динамический UDID, маркетинговые имена из YAML убрать. День 3: инвалидировать префикс кэша, форсировать холодную сборку, зафиксировать wall time как baseline. День 4: self-hosted пул разнести: ci-pr и ci-release, без Intel-узлов на лейбле 27.

Дежурство ежедневно: Changelog / runner-images на новый образ, диск runner’ов, отношение queued/run — жёлтый длиннее зелёного? Цифры хуже — сначала режем параллелизм matrix, потом машины: больше машин без разделения лейблов только ускоряет неправильные job’ы.

Когда брать облачный Mac / выделенный Silicon runner
Когда хостед xcode-27 уже стоит в очереди дольше компиляции, или нужно закрепить конкретный GM-образ под нотаризацию и аплоад — суточная аренда Apple Silicon облачного Mac как self-hosted часто дешевле лотереи в общей очереди. Выделенные узлы — для Release и цепочки сертификатов; массовый PR-конкуренс можно оставить на хостед preview-образах как первый шлюз.

FAQ

Нужно ли сразу переносить все job’ы на xcode-27?

Нет. Релизная приёмка и store-сборки — на 27; compat старых ОС и хотфиксы могут пока остаться на предыдущем образе. Главное — ясные лейблы и branch protection, без «любой зелёный = merge».

Локально Xcode 27 зелёный, CI всё ещё красный — почему?

Сначала три проверки: runner правда arm64 xcode-27, имя симулятора существует, кэш не попал в старый DerivedData. Локально полный runtime, в хостед-образе — не обязательно.

Можно ли смешивать self-hosted и GitHub-hosted в одном workflow?

Можно, но разными job’ами и лейблами. Не давайте одному массиву runs-on матчить и хостед, и любой self-hosted — иначе scheduling невоспроизводим.

Может ли тест на реальном Phone 18 заменить Simulator CI?

Нет. Устройство — камера, сотовая сеть, ощущение перфоманса; CI — compile, юнит-тесты и регрессия на каждый PR. Релизная неделя нужна обеим сторонам — устройство не как единственный merge-gate.

Окно Phone 18 · Дежурство Xcode 27

Нужен Apple Silicon без лотереи общей очереди?

Закрепите Release и нотаризацию на выделенном облачном Mac; PR оставьте на хостед xcode-27. Суточная проверка образа и цепочки сертификатов спокойнее, чем гонка за concurrency в день презентации.

Смотреть облачные Mac →

Ограниченное предложение

Нужен Apple Silicon вне общей очереди?

Xcode 27 · iOS 27 · выделенный cloud Mac · день/месяц

На главную
Акция Смотреть тарифы