Практика разработки

Изоляция Ruby и fastlane в CI на облачном Mac

Изоляция Ruby и fastlane в CI на облачном Mac

На одном и том же облачном Mac разработчик может успешно запустить fastlane из терминала, тогда как задание CI сообщит об отсутствии gem. После исправления runner терминал входа в систему может начать разрешать команды через другую установку Ruby. Обычно причина таких сбоев не в самом fastlane, а в расхождении путей, которое одновременно создают системный Ruby, менеджер версий, настройки Bundler и окружение задания. Решение состоит не в многократной установке gem, а в том, чтобы сделать всю цепочку инструментов Ruby частью проекта.

Проверьте, какая среда Ruby действительно используется

Не начинайте с команд установки. Сначала сохраните следующие результаты отдельно в терминале входа в систему и в задании CI:

command -v ruby
ruby -v
command -v bundle
bundle -v
gem env home
bundle config list
printf '%s
' "$PATH"

В первую очередь сравните, относятся ли ruby и bundle к одному и тому же префиксу пути. Если Ruby находится в каталоге менеджера версий, а Bundler загружается из системного каталога, последующая установка, скорее всего, запишет файлы в неверный путь gem. Также проверьте, заданы ли в CI переменные GEM_HOME, GEM_PATH или BUNDLE_PATH: оставшиеся от прежней конфигурации переменные могут переопределять настройки проекта.

Если команда работает в интерактивном терминале, это не означает, что автоматическое задание получает то же окружение. CI должен запускаться на основе явно заданного файла версии и конфигурации проекта, а не зависеть от того, что сценарий запуска shell случайно изменил PATH.

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

Зафиксируйте версии Ruby, Bundler и fastlane

В корень репозитория необходимо добавить .ruby-version, Gemfile и Gemfile.lock. Версия Ruby должна быть указана полностью и соответствовать доступной для установки версии, а не расплывчатому значению 3.3. Сначала команда выбирает версию, а затем выполняет:

printf '%s
' '3.3.6' > .ruby-version
bundle init
bundle add fastlane --version '~> 2.0'
bundle lock

Gemfile задаёт допустимый диапазон, а Gemfile.lock фиксирует точные версии, полученные при текущем разрешении зависимостей. CI не должен выполнять bundle update перед каждой сборкой, иначе файл блокировки перестаёт выполнять свою ограничивающую функцию. После установки запускайте все команды через точку входа Bundler:

bundle exec fastlane --version
bundle exec fastlane ios ci

Не используйте глобальный fastlane как сокращённый способ запуска. Он может относиться к другой установке Ruby, а также измениться после обновления системы или ручной отладки. Если проекту нужна конкретная версия Bundler, прочитайте её из раздела BUNDLED WITH файла Gemfile.lock, установите эту версию и только затем выполните bundle install.

Ограничьте установку зависимостей каталогом проекта

Настройки уровня проекта следует записывать в .bundle/config рядом с репозиторием. Не изменяйте глобальную конфигурацию Bundler для всего хоста:

bundle config set --local path vendor/bundle
bundle config set --local clean true
bundle install --jobs 4 --retry 2

Обычно vendor/bundle не добавляют в Git, а сохраняют в кэше CI. Это позволяет не засорять системные gem и использовать разные версии зависимостей в нескольких проектах. После установки целостность всей цепочки разрешения можно проверить следующими командами:

test -f Gemfile.lock
test "$(ruby -e 'print RUBY_VERSION')" = "$(cat .ruby-version)"
bundle check
bundle exec ruby -e 'require "fastlane"; puts Fastlane::VERSION'

Если последняя команда завершается с ошибкой, не удаляйте сразу весь кэш. Сначала проверьте bundle config list и bundle env, чтобы убедиться, что задание читает Gemfile из текущего репозитория, а не из родительского каталога или другого файла, указанного через BUNDLE_GEMFILE.

Смоделируйте чистое неинтерактивное окружение

Перед полноценным подключением runner выполните проверку в минимальном окружении:

RUBY_BIN="$(dirname "$(command -v ruby)")"
env -i \
  HOME="$HOME" \
  PATH="$RUBY_BIN:/usr/bin:/bin:/usr/sbin:/sbin" \
  BUNDLE_GEMFILE="$PWD/Gemfile" \
  bundle exec fastlane --version

Такая проверка заранее выявляет пути, псевдонимы и переменные окружения, заданные только в личной конфигурации shell. Если команда завершается с ошибкой, исправьте явную инициализацию runner, а не заставляйте CI загружать всю личную конфигурацию пользователя.

Создайте ключи кэша, исключающие смешивание окружений

Кэшировать gem с нативными расширениями только по Gemfile.lock нельзя. Различия в версии Ruby, архитектуре CPU или основной и дополнительной версиях macOS могут сделать уже скомпилированные файлы непригодными для загрузки. Ключ кэша должен включать как минимум следующие поля:

Поле Способ получения Назначение
Версия Ruby ruby -e 'print RUBY_VERSION' Изоляция различий ABI
Архитектура CPU uname -m Разделение разного нативного кода
Версия macOS sw_vers -productVersion Защита от различий системных библиотек
Хеш файла блокировки shasum -a 256 Gemfile.lock Инвалидация при изменении зависимостей

Стабильный отпечаток можно сформировать так:

RUBY_VERSION_KEY="$(ruby -e 'print RUBY_VERSION')"
ARCH_KEY="$(uname -m)"
OS_KEY="$(sw_vers -productVersion | cut -d. -f1,2)"
LOCK_KEY="$(shasum -a 256 Gemfile.lock | cut -d' ' -f1)"
printf '%s-%s-%s-%s
' "$RUBY_VERSION_KEY" "$ARCH_KEY" "$OS_KEY" "$LOCK_KEY"

После восстановления кэша всё равно необходимо выполнить bundle check. Если проверка не пройдена, запустите bundle install, чтобы установить недостающие компоненты. Успешную распаковку кэша нельзя считать доказательством того, что зависимости готовы к использованию.

Превратите сбои в диагностируемый этап контроля

Итоговое задание должно состоять из трёх этапов: проверки окружения, проверки зависимостей и выполнения рабочего lane. На первом этапе проверяются версии и пути, на втором запускается bundle check, и только на третьем вызывается fastlane. При сбое на любом этапе выполнение следует немедленно остановить, чтобы позднее ошибка не проявилась как непонятное отсутствие плагина или исключение при загрузке константы.

Рекомендуется включить в проверки перед слиянием следующие условия:

  • Версия в .ruby-version полностью совпадает с Ruby, фактически используемым runner.
  • Gemfile.lock добавлен в репозиторий и не изменяется до и после выполнения задания.
  • fastlane запускается только через bundle exec.
  • .bundle/config использует каталог зависимостей уровня проекта.
  • Ключ кэша включает Ruby, архитектуру, версию системы и хеш файла блокировки.
  • В журнале сохраняются только версии, пути и результаты проверок; конфиденциальные значения окружения не выводятся.
  • Runner и терминал входа в систему используют одну и ту же явно заданную логику инициализации.

После введения этих ограничений цепочка инструментов Ruby превращается из «набора команд, когда-то установленных на хосте» в контролируемый вход сборки. При последующем обновлении Ruby или fastlane можно отдельным коммитом изменить файл версии и файл блокировки, а затем проверить область изменений теми же этапами контроля. Больше не придётся после сбоя гадать, какое именно окружение использовало задание.

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

Почему версия fastlane меняется при сохранённом Gemfile.lock?

Обычно задача вызывает глобальный fastlane напрямую либо пересоздаёт файл блокировки перед запуском. Используйте bundle exec fastlane и считайте Gemfile.lock неизменяемым входным файлом.

Можно ли переносить vendor/bundle между облачными Mac?

Только при строгом ключе кэша, который включает версию Ruby, архитектуру CPU, основную и дополнительную версии macOS, а также хеш Gemfile.lock.

Что проверить, если CI работает, а вход через терминал завершается ошибкой?

Сравните command -v ruby, ruby -v, bundle config list и переменные окружения. Частая причина — разные PATH и сценарии инициализации менеджера Ruby.

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

Запускайте рабочие процессы на Mac mini в облаке без перерывов

Выберите RunAMac M4, срок аренды и узел размещения, чтобы выполнять задачи разработки, сборки или тестирования на выделенном физическом узле.

Выбрать тариф аренды