2026 SwiftPM и CocoaPods на общем удалённом Mac CI Package.resolved · изоляция Bundler · управление диском DerivedData

На общем или выделенном удалённом Mac одновременный запуск SwiftPM и CocoaPods сталкивает Package.resolved, Gemfile.lock и DerivedData с дисковыми квотами, а параллельные джобы перетирают одни и те же пути. Материал даёт платформенной команде чеклист для согласования: семь скрытых издержек, сравнительная таблица для стеков «только SPM / только Pods / смешанный», шестишаговый runbook по диску и кэшам и связка с нашими гайдами по воспроизводимым сборкам, self-hosted runner и снимкам и долгоживущим узлам.

01

Прежде чем смешивать стеки: семь скрытых издержек, которые «взрывают» lock-файлы и диск на общем удалённом Mac CI

В Linux вы разносите кэши зависимостей и артефакты сборки по томам. На общем macOS-хосте каталоги SwiftPM, дерево Pods и DerivedData по умолчанию часто лежат под одним домашним каталогом; при параллельных репозиториях раньше CPU упираются в диск или inode. Ниже — семь пунктов предполётной проверки.

  1. 01

    Дрейф Package.resolved и политики веток: без корзин на ветку параллельные ветки могут успешно резолвиться, но линковаться с неверными артефактами.

  2. 02

    Дрейф Ruby-экосистемы для CocoaPods: смешение системного Ruby, rbenv и Bundler даёт разные версии pod — «локально зелёно, в CI нестабильно».

  3. 03

    Общий корень DerivedData: несколько джобов, пишущих в один -derivedDataPath, затирают кэши модулей и индексы; сбои выглядят как «битые» зависимости.

  4. 04

    Дубли сторонних библиотек в Pods и SPM: одна и та же библиотека дважды удваивает объём загрузок и время резолва, часто не замеченное на архитектурном ревью.

  5. 05

    Скрипты очистки, удаляющие «живые» кэши: cron только по возрасту может снести артефакты для задач в очереди и вызвать каскад отказов.

  6. 06

    Несогласованная политика CDN / зеркал: когда pod repo update и резолв SPM идут разными выходами в сеть, джиттер превращается в «CI нестабилен».

  7. 07

    Нет контракта по дисковому порогу: без порога, при котором останавливают планирование или переходят в read-only, хосты падают по полному диску раньше, чем это заметят — дорого чинить.

Общая причина — перенос ментальной модели «один репозиторий, один пайплайн» с Linux VPS в мультитенантный Mac: нужны пространства имён для кэшей и lock-файлов, а не просто больший диск. Зафиксируйте это в платформенном журнале, затем используйте таблицу ниже, чтобы решить: сходить к одному стеку или принять смешанный с жёсткими границами каталогов.

SwiftPM фиксирует повторяемый резолв в Package.resolved в связке с резолвером компилятора; CocoaPods привязан к Ruby и обычно к Bundler в enterprise. Худший сценарий — когда инженеры меняют глобальные gem из интерактивной оболочки на CI, внося энтропию на каждый общий хост. Предпочтительны закоммиченный Gemfile.lock, только bundle exec pod install и vendor/bundle на разделе, который разрешено очищать.

Сочетайте с воспроизводимыми сборками: скрипты отпечатков должны фиксировать контрольные суммы swift package resolve и pod --version, а не только Xcode. Сочетайте с runner: при монтировании кэшей на общие тома используйте подкаталог репозитория — не один гигантский DerivedData для всех.

Сеть и CDN вернутся в разделе 4 рядом с Bundler. Далее сравнительная таблица превращает споры в правила разбиения разделов.

02

Только SwiftPM, только CocoaPods или смешанный стек: компромиссы по каталогам и рискам на общих удалённых Mac

Универсального рецепта нет: только SPM даёт однородные пути, но легаси ObjC часто требует Pods; только Pods привычнее, но Ruby и CDN обходятся дороже в эксплуатации. В ревью зафиксируйте три SLA: повторяемость резолва, пики по диску и объяснимость сбоев.

ИзмерениеSwiftPM-firstCocoaPods-firstСмешанный (типичное легаси)
Lock-файлы и повторяемостьPackage.resolved отражает связку резолвер/компиляторPodfile.lock + закреплённые Ruby/BundlerДва lock-файла и два дерева кэша — следите за дублями и конфликтами путей
Профиль дискаЧекауты + кэш SPM — предсказуемоPods объёмны; Ruby добавляет накладные расходыПики суммируются — явно бакетируйте DerivedData
КонкурентностьПростая изоляция каталогов; хорош для параллелизмаpod install может держать блокировки — ставьте в очередьРазделяйте рабочие каталоги джоб; никогда не два писателя в один путь
Операционные рычагиСогласуется с цепочкой Xcode — ложится в скрипты отпечатковНужно вести gem-источники, CDN и политику кэша BundlerСамый длинный runbook, но большинство реальных приложений здесь
Золотые образыПрогрев слоёв кэша SPMЧасто «запекают» Ruby и известные Pods в базовые образыОбразы растут — разделяйте слои toolchain / deps / изменяемые

Аренда Mac «как VPS» на уровне зависимостей — это покупка предсказуемых контрактов по каталогам и порогам, а не надежды, что «ноутбучный» диск выдержит. Относитесь к кэшам SPM и Pods как к арендаторам, а не к фоновому шуму.

Если вы ведёте корпоративный пул сборок, разделите джобы «резолв зависимостей» и «подпись и релиз»: первые могут терпеть агрессивную очистку, вторые не должны делить домашний каталог с экспериментальными состояниями Bundler. С снимками и долгоживущими узлами: смешанные стеки на долгоживущих хостах требуют еженедельной очистки и порогов; базовые снимки выигрывают от слоистого прогрева Bundler и SPM.

Когда решение — «Pods пока не снять», закрепите правила: какие сторонние пакеты остаются только в SPM или только в Pod (например, закрытые бинарники), и запретите один модуль через оба канала — иначе платите на линковке. Поставляйте минимальные фрагменты Gemfile и .bundle/config из шаблонного репозитория, чтобы команды копировали, а не изобретали.

03

Шесть шагов: от «SPM + Pods на общем удалённом Mac» к runbook для передачи

Порядок важен: сначала разбиение, затем резолв, в конце — попадания в кэш. Согласуйте с отпечатками из воспроизводимых сборок, чтобы слой зависимостей не ввёл вторую недокументированную среду.

  1. 01

    Зафиксируйте корень работ и корзину ветки на репозиторий: напр. /ci/work/<repo>/<branch-hash>; чекауты SPM, Pods и DerivedData держите в этой корзине — не откатывайтесь к ~/Library по умолчанию.

  2. 02

    Закрепите Bundler и CocoaPods: в образе CI или boot-скрипте используйте только bundle exec и направьте BUNDLE_PATH в vendor/bundle внутри корзины.

  3. 03

    Наблюдайте резолв SwiftPM отдельно от сборки: замеряйте время резолва; при сбое сохраняйте логи рядом с .build, чтобы не путать ошибки резолвера с ошибками компиляции.

  4. 04

    Изолируйте DerivedData на джобу: всегда передавайте явный -derivedDataPath в xcodebuild вместе с корнем работ выше.

  5. 05

    Определите политику очистки: триггер по порогу, запуск в окнах простоя, вытеснение LRU; никогда не rm -rf ~/Library «в лоб».

  6. 06

    Свяжите с лейблами runner: тяжёлые pod install ограничивайте отдельным лейблом по гайду по runner; не голодайте compile-heavy джобы.

bash · пример окружения рабочей корзины (подстройте под проект)
export CI_WORK_ROOT="/ci/work/${REPO_SLUG}/${BRANCH_KEY}"
export DERIVED_DATA="${CI_WORK_ROOT}/DerivedData"
export PODS_ROOT="${CI_WORK_ROOT}/Pods"
export BUNDLE_PATH="${CI_WORK_ROOT}/vendor/bundle"
mkdir -p "$DERIVED_DATA"
bundle config set path "$BUNDLE_PATH"
bundle exec pod install --deployment
swift package resolve
info

Совет: если на том же хосте идут релизы через Fastlane, держите релизные корзины отдельно от CI-резолва, чтобы Bundler и инструменты App Store Connect не перекрёстно загрязняли друг друга.

В GitHub Actions и аналогах разделите «установка зависимостей» и «сборка/тесты» и ключуйте кэши по содержимому lock-файлов, а не только по имени ветки. Если workspace смешивает SPM и Pods, закрепите аргументы xcodebuild -workspace, чтобы привычки GUI не расходились с CI бесконечно.

С XCTest / Simulator: если тесты переиспользуют compile DerivedData, явно зафиксируйте область чтения и записи; иначе очистка тестов может уничтожить кэши компиляции. Документируйте «кто что может удалять» так же строго, как для томов контейнеров в Linux.

04

Bundler, CDN и запасной read-only: убрать сетевой джиттер из диагноза «код сломан»

Типичный production-инцидент CocoaPods в enterprise — не синтаксическая ошибка, а сетевой сбой на этапе резолва, ошибочно принятый за баг приложения. На общих хостах задайте единую политику gem-зеркала и транспорта Git (HTTPS против SSH), разрешите метаданные на уровне файрвола, встройте таймауты и ретраи pod install в workflow, а не полагайтесь на дефолты.

Для SwiftPM следите, чтобы Package.resolved соответствовал резолверу после крупных апгрейдов Xcode: прогоните swift package resolve и минимальную сборку на canary-джобе до открытия всей очереди. Согласуйте с воспроизводимыми сборками: любое изменение lock-файла должно сигнализировать о возможной инвалидации общих кэшей резолва, а не тихо их портить.

warning

Внимание: не принимайте новые джобы на пороге полного диска — переключайтесь на запасной read-only (остановка планирования) и сначала чистите; иначе Xcode и git могут оставить полузаписанное состояние дороже короткой паузы очереди.

Для мультирегиональных парков стройте графики CDN hit rate и латентности резолва: при росте P95 сначала проверяйте зеркала, а не просите команды «сделать три ретрая». Сочетайте с чеклистом по SSH: версии Bundler и pod доставляйте через управление конфигурацией, а не разовые SSH-апгрейды.

05

Ориентиры для вставки в дизайн-ревью

Подстройте пороги под свой параллелизм и размеры репозиториев; ниже — точки выравнивания.

  • Порог на системном томе: держите ≥20% свободного места; ниже — сначала пауза планирования, затем удаление с логированием для аудита.
  • Пики dual-stack: на крупных iOS-приложениях DerivedData + Pods + чекауты SPM легко уходят в десятки ГБ — закладывайте ёмкость как «самый тяжёлый репозиторий × параллельные джобы × запас», а не как пик одной сборки.
  • Пробы повторяемости резолва: скрипты отпечатков должны фиксировать код возврата swift package resolve, bundle exec pod --version и контрольную сумму Podfile.lock как ворота слияния.

Ноутбуки засыпают и дрейфуют по toolchain, из-за чего проблемы зависимостей кажутся «магией»; чистый Linux не поднимает официальный стек Apple для iOS. Перенос резолва и сборки на выделенный, всегда включённый, разбиваемый на разделы удалённый Mac превращает связку SwiftPM+CocoaPods в контракт. По сравнению с разовым железом или нестабильным shared hosting облачная аренда Mac Mini от NodeMini даёт фиксированный SSH, понятные дисковые уровни и повторяемые профили узлов — удобнее для платформенного управления зависимостями. Сравните уровни на странице тарифов аренды и подключайтесь через справочный центр.

Привяжите этот runbook к внутренним «уровням изменения зависимостей»: патчи lock-файла, минорные и мажорные апгрейды должны иметь разные согласования и правила инвалидации кэша.

FAQ

FAQ

Не рекомендуется. Разделяйте -derivedDataPath по корзинам репозитория/ветки и изолируйте Pods и пути vendor Bundler; иначе параллельные джобы затирают кэши. Платформенные рекомендации — в справочном центре.

Версии Ruby и Bundler, Gemfile.lock и политику CDN-зеркала для pod install; затем задайте пороги очистки на каждом общем хосте. Дисковые уровни сравните на странице тарифов аренды.

Воспроизводимые сборки — отпечатки Xcode и Keychain; статья про runner — лейблы и монтирование кэшей; здесь — контракты каталогов и диска при смешении SPM и CocoaPods. Читайте все три для сквозного покрытия «машина → зависимости».