2026: эксклюзивный удалённый Mac, iOS/macOS CI и SwiftPM — коммитить Package.resolved? Воспроизводимые сборки, прогрев кэша, дрейф блокировок и SSH-конвейер: матрица + FAQ
На выделенном удалённом Mac, которым вы управляете по SSH, каждая минута работы резолвера — это оплачиваемое время, а неожиданное обновление транзитивного Swift-пакета может «покраснить» main без единой правки в приложении. Package.resolved — это файл пинов SwiftPM: в нём фиксируются точные версии и контрольные суммы. Здесь — когда его коммит по умолчанию выигрывает для воспроизводимого iOS/macOS CI, когда для библиотек его лучше не хранить в репозитории, как прогрев кэша сочетается с пинами и как обновления Xcode / Swift дают дрейф lock-файла, который стоит автоматизировать. Практику headless CI по SSH разбираем в руководстве по headless CI-лаборатории на Mac mini; про устойчивый кластер и агентов — в материале самовосстанавливающийся AI-кластер на Mac mini через SSH.
1. Кратко — настройки по умолчанию, которые проходят ревью в 2026 году
- Приложения и внутренние инструменты (workspace Xcode + SPM): коммитьте
Package.resolved, если только у вас нет осознанного канала «всегда последняя версия». Это самый дешёвый предохранитель для воспроизводимых архивов, совпадения CI и локальной машины и бисекта по истории. - Опубликованные Swift-библиотеки: обычно не коммитьте
Package.resolved— резолвят потребители; в CI держите матрицу поддерживаемых версий инструментов SwiftPM. - Монорепозиторий с несколькими пакетами: у каждого пакета/workspace может быть свой resolved; документируйте, какой job отвечает за пересоздание lock.
- Эксклюзивный удалённый Mac CI: пины + тёплый кэш SPM / DerivedData почти всегда выигрывают у «холодного resolve»; пины делают ключи кэша осмысленными.
- SSH-конвейеры: пересоздавать lock по SSH нормально — коммитить дифф из CI только при явной учётной записи бота и защите веток; иначе пусть разработчики гоняют
swift package resolveлокально.
2. Что именно фиксирует Package.resolved
SwiftPM превращает semver-требования из Package.swift / зависимостей Xcode в конкретные ревизии git (и идентификаторы registry, где применимо). Результат резолвера попадает в Package.resolved у корня workspace / пакета, который использует Xcode. Файл связывает «semver разрешает ~> 2.1» с «сегодня мы собрались ровно на коммите abc123».
Без закоммиченного lock два CI-job на одном коммите могут собрать разные графы зависимостей, если сдвинулся downstream-тег — классический сценарий, когда «флейки» невозможно бисектить.
3. Воспроизводимость против «свежести» зависимостей
Воспроизводимость нужна для аудита, разборов инцидентов App Store и символикации крэшей у клиентов. Свежесть — для патчей безопасности и быстрого внедрения API. Вы выбираете не один раз навсегда, а автоматизацию.
- Пин + плановый bump: Dependabot / Renovate / еженедельный внутренний job пересобирает
Package.resolved, гоняет тесты и открывает PR. Для приложений это отраслевой дефолт. - Без пина: принимаете «плавающую голову» на каждом прогоне; компенсируете агрессивным кэшированием и терпите недетерминизм — для релизных веток редко приемлемо.
4. Прогрев кэша на выделенном Mac-раннере
SPM клонирует и собирает артефакты в зоне SourcePackages пользователя и в DerivedData Xcode. На эфемерных облачных раннерах часто холодный старт; на выделенной машине можно держать тёплый кэш на CI-пользователя — если job-ы не мешают друг другу чек-аутами.
- Ключи кэша: хэшируйте
Package.resolved+ версию Xcode + минор macOS в ключ восстановления, чтобы restore оставался честным. - Холодный resolve: без lock быстрее получить «последнее совместимое», но каждый раз тянете сеть и резолвер — дорого на фермах за SSH при лимитах и сбоях GitHub.
- Параллельные workspace: несколько агентов на одном Mac должны иметь разных пользователей или непересекающиеся корни DerivedData, чтобы избежать блокировок файлов SPM.
5. Дрейф lock в CI при смене Xcode или Swift
Apple регулярно обновляет toolchain внутри Xcode. Файл, сгенерированный в Xcode A, может переписаться в Xcode B — не только из‑за смены пакетов, но и из‑за изменений резолвера или артефактов. Обновление Xcode трактуйте как миграцию:
- Зафиксируйте в CI конкретный путь
xcode-selectна семейство веток (release против main). - Один раз прогоните
swift package resolve(или resolve в Xcode) в контролируемом PR, который меняет только lock. - После merge заново прогрейте кэши, чтобы следующие job-ы не смешивали артефакты до/после миграции.
6. Матрица решений (коммитить или игнорировать)
| Сценарий | Коммитить Package.resolved? |
Комментарий |
|---|---|---|
| iOS/macOS приложение в Git | Да (на workspace) | Согласует CI, TestFlight и крэш-логи; добавьте бота для bump. |
| Публикуемая библиотека | Нет (типично) | Пины оставьте приложениям; CI — матрица совместимости SwiftPM. |
| Внутренний монорепо из нескольких приложений | Да, на workspace каждого приложения | Общие пакеты можно не пинить, приложения — пинить. |
| Граф с бинарниками / XCFramework | Да | Стабильность checksum важна для кэша артефактов. |
| Экспериментальная ветка «всё последнее» | Опционально | Удаляйте lock осознанно; ждите шумный CI; в релиз не отдавать. |
7. SSH-first: что это меняет в пайплайне
Когда вы заходите по SSH для triage, вы видите ту же ФС, что и раннер. Хорошие привычки:
- Не правьте JSON lock вручную — только команды SwiftPM, чтобы checksum оставались согласованными.
- Разделяйте интерактивные shell и пользователя автоматизации, чтобы личные эксперименты не портили CI-кэш.
- Логируйте первые строки
Package.resolvedв логах сборки при расследовании «CI не совпадает с ноутбуком» — часто виноват неверный рабочий каталог.
8. FAQ
В1: «Reset Package Caches» в Xcode — безопасно в CI?
Считайте это крайней мерой: сбрасывает тёплый кэш и заново качает; полезно при подозрении на порчу, но дорого для фермы удалённых Mac. Лучше точечно удалить артефакт конкретного пакета.
В2: И CocoaPods, и SPM — где «истина»?
Они сосуществуют: пины SPM — в Package.resolved, Pods — в Podfile.lock. Для приложений коммитьте оба. На релизных ветках CI должен падать, если нет любого из lock-файлов.
В3: Нужно ли боту автокоммитить обновления lock?
Только при защите веток, подписанных коммитах (если требуется) и узком токене. Иначе пусть люди пушат PR с lock — на выделенном железе с параллельными тестами это всё ещё быстро.
В4: Registry и git-зависимости — пины отличаются?
Файл всё равно фиксирует разрешённые идентичности; стратегии кэша разные (метаданные registry против git-объектов). Коммит lock полезен в обоих случаях.
В5: Мы каждый раз вызываем xcodebuild -resolvePackageDependencies — всё равно коммитить?
Да — при совпадении графа с lock вызов должен стать no-op, что быстрее и проверяет, что никто случайно не сдвинул зависимости.
Почему для SwiftPM CI всё ещё важен выделенный Apple Silicon
Пропускная способность резолвера и компиляции чувствительна к дисковому I/O, полосе unified memory и стабильной установке Xcode — здесь класс Mac mini на Apple Silicon часто обходит типичные облачные ВМ. macOS месяцами остаётся без сюрпризов драйверного стека, Gatekeeper и SIP снижают риск подмены на безлюдных раннерах, а простое энергопотребление в простое делает экономически разумным держать тёплый кэш SPM круглосуточно. Когда Package.resolved фиксирует граф, важно, чтобы машина, которая его проигрывает, была одинаковой от job к job — на однотенантном удалённом Mac это проще, чем на шумном общем гипервизоре.
Если вы ужесточаете политику SwiftPM для iOS/macOS пайплайнов, Mac mini M4 остаётся самым выгодным якорем для воспроизводимых сборок: закоммиченные пины плюс железо, которое не «уплывает» под ногами. Если хотите прогнать описанную схему на максимально предсказуемой платформе, сейчас удачный момент перейти на Mac mini M4 — откройте главную SSHMac и подберите конфигурацию до заморозки образов CI.
Нужен выделенный удалённый Mac для воспроизводимого SwiftPM CI?
SSHMac удалённый Mac mini
Запускайте закреплённые графы SPM на Apple Silicon с приоритетом SSH: тёплый кэш, стабильные пути Xcode и меньше сюрпризов «собралось у меня, не собралось в CI».