После того как команда клонирует проект на облачный Mac, чаще всего недооценивают не ошибки компиляции, а ситуацию, когда конвейер вообще не находит ожидаемую схему либо одноимённая схема запускает в удалённой среде другие цели. Локальный Xcode читает пользовательские настройки из каталога разработчика, тогда как чистая рабочая область CI видит только файлы из репозитория. Чтобы точка входа сборки оставалась стабильной, Scheme необходимо версионировать и проверять так же, как скрипты и файлы блокировки зависимостей.
Рассматривайте Scheme как интерфейс CI
Scheme, доступная конвейеру, должна определять как минимум четыре аспекта: какие Target собирать, какую Build Configuration использовать, какие Test Bundle запускать и какую конфигурацию применять при Archive. То, что имя отображается в меню Xcode, ещё не означает, что эти сведения добавлены в репозиторий.
Сначала проверьте наличие общих файлов:
find . \( -path "*/xcshareddata/xcschemes/*.xcscheme" \
-o -path "*/xcuserdata/*/xcschemes/*.xcscheme" \) -print
Scheme в каталоге xcuserdata относится к пользовательским настройкам и не может служить точкой входа CI. Включите Shared в окне Manage Schemes в Xcode, а затем убедитесь, что файл появился по одному из следующих путей:
App.xcodeproj/xcshareddata/xcschemes/App.xcscheme
App.xcworkspace/xcshareddata/xcschemes/App.xcscheme
После этого с помощью git status и git check-ignore -v убедитесь, что файл отслеживается и не исключён по ошибке слишком широким правилом для *.xcuser* или xcshareddata.
«Видно локально» означает лишь, что конфигурация существует в каталоге текущего пользователя. «Доступно в CI» должно подтверждаться результатом команды в каталоге с чистым клоном репозитория.
Зафиксируйте Workspace, Scheme и конфигурацию
Конвейер не должен полагаться на автоматический выбор проекта в Xcode. При наличии CocoaPods, агрегирующих проектов или локальных Package результаты открытия .xcodeproj и .xcworkspace могут различаться. Явно задайте три значения точки входа в переменных CI:
WORKSPACE="App.xcworkspace"
SCHEME="App"
CONFIGURATION="Release"
xcodebuild \
-workspace "$WORKSPACE" \
-scheme "$SCHEME" \
-configuration "$CONFIGURATION" \
-list
Вывод -list должен содержать ожидаемую Scheme. Затем выполните автоматическую проверку вывода в JSON, чтобы не полагаться только на ручное чтение журналов:
xcodebuild -workspace "$WORKSPACE" -list -json > xcode-list.json
python3 - <<'PY'
import json
data = json.load(open("xcode-list.json"))
schemes = data.get("workspace", {}).get("schemes", [])
if "App" not in schemes:
raise SystemExit("Required Xcode scheme is missing")
PY
Если в репозитории одновременно поддерживаются точки входа для разработки, предварительной и рабочей среды, их назначение должно быть понятно из названий, например App-Dev, App-Staging и App-Release. Не выбирайте в скрипте первую Scheme по порядку в списке: после добавления зависимости порядок может измениться.
Проверяйте Build, Test и Archive по отдельности
Успешная публикация Scheme решает только проблему видимости, но ещё не подтверждает корректность действий. Рекомендуется разделить проверку на три уровня.
Проверьте итоговые настройки сборки
Сначала экспортируйте окончательные настройки и уделите особое внимание PRODUCT_BUNDLE_IDENTIFIER, CONFIGURATION, SDKROOT, SUPPORTED_PLATFORMS и каталогу артефактов:
xcodebuild \
-workspace "$WORKSPACE" \
-scheme "$SCHEME" \
-configuration "$CONFIGURATION" \
-showBuildSettings > build-settings.txt
grep -E "PRODUCT_BUNDLE_IDENTIFIER|CONFIGURATION =|SDKROOT|SUPPORTED_PLATFORMS" \
build-settings.txt
Здесь отображается результат объединения Scheme, настроек проекта и параметров командной строки, поэтому он точнее отражает фактическое выполнение, чем непосредственная проверка project.pbxproj.
Проверьте точку входа тестов
На этапе тестирования сначала соберите тестируемые артефакты, а затем запускайте тесты. Это позволяет отличить неисправную точку входа компиляции от сбоя выполнения тестов:
xcodebuild \
-workspace "$WORKSPACE" \
-scheme "$SCHEME" \
-destination "platform=iOS Simulator,name=iPhone 16" \
build-for-testing
Имя симулятора должно быть взято из вывода xcrun simctl list devices available на текущей машине. Не фиксируйте устройство, доступное только на личном компьютере. Если команде требуется определённая среда выполнения, одновременно зафиксируйте версию Xcode и целевую версию ОС.
Отдельно проверьте архивирование
Archive может использовать конфигурацию, отличную от Run и Test. Сначала проверьте конфигурацию ArchiveAction в .xcscheme, а затем выполните реальное архивирование в контролируемой задаче:
xcodebuild \
-workspace "$WORKSPACE" \
-scheme "$SCHEME" \
-configuration Release \
-destination "generic/platform=iOS" \
-archivePath "$PWD/artifacts/App.xcarchive" \
archive
Успешный обычный build не заменяет проверку Archive. Проблемы с обработкой ресурсов, этапами скриптов и параметрами архивирования часто проявляются только здесь.
Очистите локальное состояние и выполните повторную проверку
Самая надёжная проверка — не удаление DerivedData, а повторное клонирование в новый каталог. Так одновременно обнаруживаются незафиксированная Scheme, локальные символические ссылки, неотслеживаемые настройки и неявные зависимости:
ROOT="$(mktemp -d)"
git clone --no-local . "$ROOT/repo"
cd "$ROOT/repo"
git submodule update --init --recursive
xcodebuild -workspace App.xcworkspace -scheme App -list
Если проект зависит от этапа генерации, поместите соответствующую команду перед сборкой и проверьте стабильность результата. Не добавляйте в руководство шаг «один раз открыть проект в Xcode»: его невозможно выполнить в полностью автоматическом задании, и он маскирует отсутствие файлов.
Проверка в чистой среде должна охватывать как минимум следующее:
| Проверка | Критерий успешности |
|---|---|
| Видимость Scheme | xcodebuild -list -json возвращает указанное имя |
| Принадлежность файла | Scheme находится в xcshareddata и отслеживается Git |
| Конфигурация сборки | Результат обработки параметров командной строки соответствует ожиданиям конвейера |
| Точка входа тестов | build-for-testing создаёт тестовые артефакты |
| Точка входа архивирования | Универсальная цель iOS создаёт .xcarchive |
| Неинтерактивное выполнение | Не требуется открывать Xcode или читать личный каталог пользователя |
Блокируйте изменения точки входа до слияния
Scheme представляет собой XML-файл, а конфликты слияния в нём часто обрабатывают как обычные текстовые конфликты. В результате имя может сохраниться, а Testable или BuildActionEntry — исчезнуть. Добавьте в запрос на слияние лёгкую проверку: подтвердите наличие общего файла, запретите добавление xcuserdata и выполните xcodebuild -list -json.
Также проверяйте Pre-actions и Post-actions схемы. Если скрипт ссылается на абсолютный путь, интерактивную конфигурацию Shell или инструмент, установленный только на компьютере определённого разработчика, удалённое выполнение всё равно завершится сбоем. Скрипты должны находить файлы через переменные сборки, такие как SRCROOT, и явно завершаться с ошибкой при отсутствии зависимостей.
Итоговый критерий проверки прост: получив репозиторий в пустом каталоге и имея только указанную в документации версию Xcode и команды, система должна перечислить Scheme, обработать настройки, собрать тестовые артефакты и завершить архивирование. Только после этого облачный Mac в G-Mini будет выполнять точку входа, определённую репозиторием, а не воспроизводить случайное состояние компьютера одного из разработчиков.
Часто задаваемые вопросы
Почему локальный Xcode видит схему, а CI на облачном Mac не видит?
Обычно схема хранится в xcuserdata и не попадает в чистый клон. Перенесите её в xcshareddata/xcschemes, добавьте файл в Git и повторите проверку из нового каталога.
Достаточно ли сделать схему Xcode общей для успешного Archive?
Нет. Общая схема становится видимой, но её Release-конфигурация, цель Archive и подготовительные действия могут оставаться неверными. Нужна отдельная проверка и реальное архивирование.
Облачный Mac для следующей очереди сборки
Сравните две конфигурации M4 и пять узлов и выберите аренду на день, неделю, месяц или квартал в зависимости от рабочего цикла.