На общем или выделенном удалённом Mac одновременный запуск SwiftPM и CocoaPods сталкивает Package.resolved, Gemfile.lock и DerivedData с дисковыми квотами, а параллельные джобы перетирают одни и те же пути. Материал даёт платформенной команде чеклист для согласования: семь скрытых издержек, сравнительная таблица для стеков «только SPM / только Pods / смешанный», шестишаговый runbook по диску и кэшам и связка с нашими гайдами по воспроизводимым сборкам, self-hosted runner и снимкам и долгоживущим узлам.
В Linux вы разносите кэши зависимостей и артефакты сборки по томам. На общем macOS-хосте каталоги SwiftPM, дерево Pods и DerivedData по умолчанию часто лежат под одним домашним каталогом; при параллельных репозиториях раньше CPU упираются в диск или inode. Ниже — семь пунктов предполётной проверки.
Дрейф Package.resolved и политики веток: без корзин на ветку параллельные ветки могут успешно резолвиться, но линковаться с неверными артефактами.
Дрейф Ruby-экосистемы для CocoaPods: смешение системного Ruby, rbenv и Bundler даёт разные версии pod — «локально зелёно, в CI нестабильно».
Общий корень DerivedData: несколько джобов, пишущих в один -derivedDataPath, затирают кэши модулей и индексы; сбои выглядят как «битые» зависимости.
Дубли сторонних библиотек в Pods и SPM: одна и та же библиотека дважды удваивает объём загрузок и время резолва, часто не замеченное на архитектурном ревью.
Скрипты очистки, удаляющие «живые» кэши: cron только по возрасту может снести артефакты для задач в очереди и вызвать каскад отказов.
Несогласованная политика CDN / зеркал: когда pod repo update и резолв SPM идут разными выходами в сеть, джиттер превращается в «CI нестабилен».
Нет контракта по дисковому порогу: без порога, при котором останавливают планирование или переходят в 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. Далее сравнительная таблица превращает споры в правила разбиения разделов.
Универсального рецепта нет: только SPM даёт однородные пути, но легаси ObjC часто требует Pods; только Pods привычнее, но Ruby и CDN обходятся дороже в эксплуатации. В ревью зафиксируйте три SLA: повторяемость резолва, пики по диску и объяснимость сбоев.
| Измерение | SwiftPM-first | CocoaPods-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 из шаблонного репозитория, чтобы команды копировали, а не изобретали.
Порядок важен: сначала разбиение, затем резолв, в конце — попадания в кэш. Согласуйте с отпечатками из воспроизводимых сборок, чтобы слой зависимостей не ввёл вторую недокументированную среду.
Зафиксируйте корень работ и корзину ветки на репозиторий: напр. /ci/work/<repo>/<branch-hash>; чекауты SPM, Pods и DerivedData держите в этой корзине — не откатывайтесь к ~/Library по умолчанию.
Закрепите Bundler и CocoaPods: в образе CI или boot-скрипте используйте только bundle exec и направьте BUNDLE_PATH в vendor/bundle внутри корзины.
Наблюдайте резолв SwiftPM отдельно от сборки: замеряйте время резолва; при сбое сохраняйте логи рядом с .build, чтобы не путать ошибки резолвера с ошибками компиляции.
Изолируйте DerivedData на джобу: всегда передавайте явный -derivedDataPath в xcodebuild вместе с корнем работ выше.
Определите политику очистки: триггер по порогу, запуск в окнах простоя, вытеснение LRU; никогда не rm -rf ~/Library «в лоб».
Свяжите с лейблами runner: тяжёлые pod install ограничивайте отдельным лейблом по гайду по runner; не голодайте compile-heavy джобы.
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
Совет: если на том же хосте идут релизы через Fastlane, держите релизные корзины отдельно от CI-резолва, чтобы Bundler и инструменты App Store Connect не перекрёстно загрязняли друг друга.
В GitHub Actions и аналогах разделите «установка зависимостей» и «сборка/тесты» и ключуйте кэши по содержимому lock-файлов, а не только по имени ветки. Если workspace смешивает SPM и Pods, закрепите аргументы xcodebuild -workspace, чтобы привычки GUI не расходились с CI бесконечно.
С XCTest / Simulator: если тесты переиспользуют compile DerivedData, явно зафиксируйте область чтения и записи; иначе очистка тестов может уничтожить кэши компиляции. Документируйте «кто что может удалять» так же строго, как для томов контейнеров в Linux.
Типичный production-инцидент CocoaPods в enterprise — не синтаксическая ошибка, а сетевой сбой на этапе резолва, ошибочно принятый за баг приложения. На общих хостах задайте единую политику gem-зеркала и транспорта Git (HTTPS против SSH), разрешите метаданные на уровне файрвола, встройте таймауты и ретраи pod install в workflow, а не полагайтесь на дефолты.
Для SwiftPM следите, чтобы Package.resolved соответствовал резолверу после крупных апгрейдов Xcode: прогоните swift package resolve и минимальную сборку на canary-джобе до открытия всей очереди. Согласуйте с воспроизводимыми сборками: любое изменение lock-файла должно сигнализировать о возможной инвалидации общих кэшей резолва, а не тихо их портить.
Внимание: не принимайте новые джобы на пороге полного диска — переключайтесь на запасной read-only (остановка планирования) и сначала чистите; иначе Xcode и git могут оставить полузаписанное состояние дороже короткой паузы очереди.
Для мультирегиональных парков стройте графики CDN hit rate и латентности резолва: при росте P95 сначала проверяйте зеркала, а не просите команды «сделать три ретрая». Сочетайте с чеклистом по SSH: версии Bundler и pod доставляйте через управление конфигурацией, а не разовые SSH-апгрейды.
Подстройте пороги под свой параллелизм и размеры репозиториев; ниже — точки выравнивания.
swift package resolve, bundle exec pod --version и контрольную сумму Podfile.lock как ворота слияния.Ноутбуки засыпают и дрейфуют по toolchain, из-за чего проблемы зависимостей кажутся «магией»; чистый Linux не поднимает официальный стек Apple для iOS. Перенос резолва и сборки на выделенный, всегда включённый, разбиваемый на разделы удалённый Mac превращает связку SwiftPM+CocoaPods в контракт. По сравнению с разовым железом или нестабильным shared hosting облачная аренда Mac Mini от NodeMini даёт фиксированный SSH, понятные дисковые уровни и повторяемые профили узлов — удобнее для платформенного управления зависимостями. Сравните уровни на странице тарифов аренды и подключайтесь через справочный центр.
Привяжите этот runbook к внутренним «уровням изменения зависимостей»: патчи lock-файла, минорные и мажорные апгрейды должны иметь разные согласования и правила инвалидации кэша.
Не рекомендуется. Разделяйте -derivedDataPath по корзинам репозитория/ветки и изолируйте Pods и пути vendor Bundler; иначе параллельные джобы затирают кэши. Платформенные рекомендации — в справочном центре.
Версии Ruby и Bundler, Gemfile.lock и политику CDN-зеркала для pod install; затем задайте пороги очистки на каждом общем хосте. Дисковые уровни сравните на странице тарифов аренды.
Воспроизводимые сборки — отпечатки Xcode и Keychain; статья про runner — лейблы и монтирование кэшей; здесь — контракты каталогов и диска при смешении SPM и CocoaPods. Читайте все три для сквозного покрытия «машина → зависимости».