Воспроизводимая установка CocoaPods на облачном Mac

DevOps и CI/CD ·~4 мин чтения

Воспроизводимая установка CocoaPods на облачном Mac

Один и тот же коммит может успешно собираться на машине разработчика, но в новой задаче на облачном Mac внезапно разрешать другие зависимости или незаметно перезаписывать Podfile.lock после pod install. Обычно причина таких проблем не в самом Xcode, а в том, что версия CocoaPods, кэш и режим установки не учитываются как входные данные сборки. Правильная цель — не просто «каждый раз завершать установку», а гарантированно получать один и тот же граф зависимостей для одного и того же набора входных данных и немедленно останавливать процесс при любых изменениях.

Сначала определите входные данные воспроизводимой установки

На результат работы CocoaPods влияют как минимум пять входных параметров: Podfile, Podfile.lock, версия CocoaPods, окружение Ruby и Bundler, а также исходный код, на который указывают локальные Pod-зависимости для разработки. Если зафиксировать в репозитории только lock-файл, но разрешить исполнителю использовать произвольную версию CocoaPods, всё равно возможны различия в формате создаваемого проекта или в поведении установки.

В репозиторий следует добавить .ruby-version, Gemfile, Gemfile.lock, Podfile и Podfile.lock. Версию CocoaPods нужно явно указать в Gemfile, не полагаясь на последнюю версию, глобально установленную на машине:

source ENV.fetch("BUNDLE_GEM_SOURCE")

ruby "3.3.4"
gem "cocoapods", "1.15.2"

В качестве единой точки входа CI используйте bundle exec pod. Тогда версия в журнале будет соответствовать зафиксированному окружению, а интерактивный Shell не сможет случайно выбрать глобальную команду раньше локальной.

Podfile.lock фиксирует результат разрешения зависимостей, но не фиксирует саму программу CocoaPods, которая разрешает зависимости и создаёт проект.

Переведите команду установки в строгий режим

В обычных сборках нельзя запускать pod update. Эта команда повторно разрешает зависимости в пределах заданных ограничений версий, поэтому подходит для обновлений, запущенных вручную, но не для проверки уже существующего коммита. В CI можно использовать следующую точку входа для установки:

#!/bin/bash
set -euo pipefail

export LANG=en_US.UTF-8
export COCOAPODS_DISABLE_STATS=true
export CP_HOME_DIR="${RUNNER_TEMP}/cocoapods-home"

bundle config set path "${RUNNER_TEMP}/bundle"
bundle check || bundle install
bundle exec pod install --deployment --clean-install
git diff --exit-code -- Podfile.lock

Параметр --deployment завершает установку с ошибкой, если требуется изменить lock-файл, а --clean-install снижает вероятность того, что старое состояние установки скроет проблему. Последняя команда git diff служит вторым барьером: даже если какой-либо скрипт перезаписал lock-файл во время установки, задача не должна переходить к сборке.

Если репозиторий содержит несколько рабочих пространств, у каждого из них должны быть отдельные каталоги установки и файлы журналов. Не используйте один каталог Pods одновременно в нескольких параллельных задачах.

Изолируйте кэш, но не отключайте его без необходимости

Проблема не в самом кэше, а в нечётко определённых границах его использования. На облачном Mac загрязнение часто возникает, когда несколько репозиториев используют общий кэш загрузок CocoaPods или две задачи одновременно читают и изменяют один каталог Bundler. Рекомендуется формировать ключ кэша из «контрольной суммы lock-файла + версии инструмента» и разрешать запись в кэш только после успешной установки.

Объект кэширования Рекомендуемый ключ кэша Условие сброса
Каталог установки Bundler Контрольная сумма Gemfile.lock + версия Ruby Изменение любого входного параметра
Кэш загрузок CocoaPods Контрольная сумма Podfile.lock + версия CocoaPods Изменение lock-файла или инструмента
Каталог Pods Контрольная сумма Podfile.lock + контрольная сумма конфигурации проекта Изменение Podfile, скрипта или конфигурации

Не кэшируйте Pods/Manifest.lock, чтобы затем перезаписать им результат новой установки. Этот файл отражает фактическое состояние текущей песочницы и должен создаваться в ходе каждой установки. Если после восстановления кэша возникают проблемы, сначала выполните установку без кэша в изолированном каталоге. Считать кэш причиной можно только в том случае, если установка без него завершается нормально.

Проверяйте граф зависимостей до сборки

Успешная установка ещё не означает, что граф зависимостей корректен. Как минимум нужно проверить совпадение Podfile.lock и Pods/Manifest.lock, а также убедиться в отсутствии незафиксированных изменений:

test -f Podfile.lock
test -f Pods/Manifest.lock

cmp -s Podfile.lock Pods/Manifest.lock || {
  echo "CocoaPods sandbox differs from Podfile.lock" >&2
  exit 1
}

git status --porcelain -- Podfile Podfile.lock Gemfile Gemfile.lock

Если последняя команда что-либо выводит, задача должна завершаться с ошибкой, а не ограничиваться предупреждением. Для локальных Pod-зависимостей, подключённых через :path, дополнительно сохраняйте идентификатор коммита или контрольную сумму содержимого целевого каталога. Иначе lock-файл может остаться прежним, а содержимое каталога — измениться, что исключает воспроизводимость.

Создайте отдельный процесс обновления

В задаче обновления зависимостей можно запускать pod update SomePod для указанной цели, но необходимо выводить три типа различий: изменения версий в lock-файле, изменения SPEC CHECKSUMS, а также добавленные или удалённые транзитивные зависимости. Коммит с обновлением должен содержать только изменения зависимостей без одновременных правок бизнес-логики. Тогда для отката будет достаточно восстановить предыдущий lock-файл.

Сохраняйте состояние при сбое и проверяйте причины по порядку

После сбоя не удаляйте сразу весь кэш. Сначала сохраните версию CocoaPods, версию Ruby, контрольную сумму lock-файла, журнал установки и вывод git status, а затем определите, на каком уровне возникла проблема.

  1. Если завершается ошибкой bundle check, сначала проверьте Ruby и платформу в Gemfile.lock.
  2. Если строгая установка требует изменить lock-файл, проверьте, не изменил ли кто-либо Podfile, забыв добавить обновлённый lock-файл в коммит.
  3. Если сбой возникает только в задаче после восстановления кэша, выполните установку без кэша в отдельном каталоге и сравните результаты.
  4. Если Manifest.lock не совпадает, проверьте, не была ли установка прервана и не используют ли несколько задач общий каталог Pods.
  5. Если содержимое локального Pod меняется, добавьте проверку идентификатора коммита или контрольной суммы каталога с исходным кодом.

Стабильный процесс CocoaPods в итоге должен быть предельно предсказуемым: если входные данные не изменились, результат установки остаётся прежним; если входные данные изменились, различия попадают на проверку кода; если кэш повреждён, это влияет только на скорость, но не меняет граф зависимостей. Размещение этих проверок перед сборкой Xcode обычно позволяет заменить неясную ошибку через десятки минут понятным сбоем уже через несколько десятков секунд.

Часто задаваемые вопросы

Что запускать в CI: pod install или pod update?

Обычные задания CI должны выполнять pod install --deployment. pod update запускают отдельно, а изменения Podfile.lock обязательно проверяют при ревью.

Почему результат меняется даже при наличии Podfile.lock?

Причиной могут быть разные версии Ruby или CocoaPods, изменяемые локальные Pod, повреждённый кэш либо сценарий, который переписывает Podfile перед установкой.

Нужно ли хранить каталог Pods в Git?

Обычно нет. Достаточно хранить Podfile, Podfile.lock, Gemfile и Gemfile.lock, если CI стабильно восстанавливает зависимости из зафиксированных входных данных.

Выделенный физический узел

Выберите облачный Mac LemonVM под срок задачи

Lemon M4 и Lemon M4 Pro доступны в Сингапуре, Токио, Сеуле, Гонконге и на западе США с оплатой за день, неделю, месяц или квартал.

Арендовать облачный Mac