Когда один облачный Mac одновременно обслуживает стабильную ветку, ветку повседневной разработки и ветку проверки обновлений, самым незаметным источником изменений часто оказывается не код, а активная версия Xcode. Если одна задача глобально переключит Xcode, следующие конвейеры могут незаметно начать использовать другой компилятор Swift или SDK. В результате проект собирается локально, но падает на удалённой машине, либо один и тот же коммит в разные дни даёт разные результаты сборки.
Рассматривайте инструментарий как зависимость проекта
Xcode следует воспринимать не просто как приложение на компьютере, а как версионируемую зависимость наравне со средой выполнения языка. Храните установки в каталогах с указанием версии и не позволяйте новому пакету перезаписывать предыдущий:
/Applications/Xcode_16.1.app
/Applications/Xcode_16.2.app
/Applications/Xcode_16.3.app
Сначала проверьте установленные версии и текущий глобальный выбор:
find /Applications -maxdepth 1 -name 'Xcode*.app' -print
xcode-select --print-path
xcodebuild -version
xcrun --find swift
swift --version
Как минимум фиксируйте версию и номер сборки Xcode, версию Swift и используемый проектом SDK. Формулировка «использовать последнюю версию» не позволяет воспроизвести историческую сборку или определить, вызван ли сбой изменениями в коде либо инструментарии.
Цель фиксации версии — не отказаться от обновлений, а превратить каждое обновление в наблюдаемое инженерное изменение с возможностью отката.
Выбирайте Xcode с учётом области действия
Способ выбора зависит от области действия задачи. Глобальное значение по умолчанию удобно для ручного администрирования, а переменная окружения на уровне задачи лучше подходит для автоматизации.
| Способ | Область действия | Рекомендуемый сценарий | Риск при параллельном выполнении |
|---|---|---|---|
xcode-select |
Весь хост | Обслуживание одним администратором, единая версия по умолчанию | Влияет на другие задачи |
DEVELOPER_DIR |
Текущий процесс и его дочерние процессы | CI, скрипты, параллельные проекты | Низкий |
| Временное присваивание перед командой | Одна команда | Быстрая проверка | Низкий |
Чтобы изменить глобальную версию по умолчанию, выполните:
sudo xcode-select --switch /Applications/Xcode_16.2.app/Contents/Developer
Не выполняйте эту команду многократно в CI. Вместо этого задавайте переменную в начале задачи:
export DEVELOPER_DIR="/Applications/Xcode_16.2.app/Contents/Developer"
xcodebuild -version
xcrun --sdk iphoneos --show-sdk-version
DEVELOPER_DIR наследуется дочерними процессами, поэтому последующие вызовы xcrun, swift и xcodebuild будут разрешать инструменты из одного и того же каталога Developer. Каждый конвейер может указать собственный путь, не конкурируя за глобальное состояние.
Предотвращайте незаметный дрейф с помощью скрипта предварительной проверки
Недостаточно просто вывести версии в журнал. Если версия не соответствует требованиям, предварительная проверка должна немедленно завершать задачу, а не обнаруживать проблему после десятков минут компиляции. Следующий скрипт фиксирует Xcode 16.2 и проверяет каталог приложения, фактическую версию, SDK и путь к Swift:
#!/bin/bash
set -euo pipefail
XCODE_APP="/Applications/Xcode_16.2.app"
EXPECTED_VERSION="16.2"
test -x "$XCODE_APP/Contents/Developer/usr/bin/xcodebuild"
export DEVELOPER_DIR="$XCODE_APP/Contents/Developer"
ACTUAL_VERSION="$(xcodebuild -version | awk 'NR == 1 {print $2}')"
test "$ACTUAL_VERSION" = "$EXPECTED_VERSION"
echo "developer=$DEVELOPER_DIR"
echo "xcode=$ACTUAL_VERSION"
echo "swift=$(xcrun --find swift)"
echo "sdk=$(xcrun --sdk iphoneos --show-sdk-version)"
xcodebuild -checkFirstLaunchStatus
Поместите скрипт в репозиторий, например по пути ci/verify-toolchain.sh, чтобы требования к версии проходили ревью вместе с кодом. При обновлении одновременно меняйте скрипт и проектную документацию: история таких изменений надёжнее ручных операций на хосте.
Если проекту требуется конкретная версия SDK, добавьте точную проверку. Не определяйте SDK по основной версии Xcode: разные выпуски одной линейки могут содержать разные SDK.
Явно задавайте и команду сборки
После успешной предварительной проверки запускайте сборку через явно заданную точку входа:
export DEVELOPER_DIR="/Applications/Xcode_16.2.app/Contents/Developer"
xcodebuild \
-workspace Example.xcworkspace \
-scheme Example \
-configuration Release \
-derivedDataPath "$PWD/.derived-data" \
CODE_SIGNING_ALLOWED=NO \
build
Здесь явно указаны рабочее пространство, scheme, конфигурация и путь к Derived Data. Параметр CODE_SIGNING_ALLOWED=NO подходит только для проверочной компиляции, не требующей подписи. Для архивирования или сборки под устройство используйте предусмотренный проектом безопасный процесс подписания и не копируйте этот параметр без изменений.
Проверяйте новую версию параллельно и только потом переключайтесь
После установки новой версии Xcode сохраните предыдущую, а проверку обновления выполняйте в отдельной задаче. Первичную подготовку может выполнить администратор:
sudo DEVELOPER_DIR="/Applications/Xcode_16.3.app/Contents/Developer" \
xcodebuild -runFirstLaunch
Затем соберите один и тот же коммит старой и новой версиями, сравнив успешность компиляции, количество тестов, изменения предупреждений, архитектуры артефактов и метаданные архивов. Не удаляйте старый каталог сразу: при несовместимости зависимостей или параметров компиляции вы лишитесь быстрого пути отката.
В рамках приёмки обновления также проверьте, не был ли автоматически перезаписан формат проекта. Если новая версия Xcode изменила файлы проекта, вынесите эти изменения в отдельный коммит и проведите самостоятельное ревью, не смешивая их с бизнес-логикой. Так проще отделить автоматическую миграцию инструментов, обновление зависимостей и реальные функциональные изменения.
Типичные ошибки и контрольный список перед сдачей
Первая распространённая ошибка — переключить только xcode-select, забыв об оставшейся в окружении задачи переменной DEVELOPER_DIR. Переменная окружения имеет более высокий приоритет, поэтому глобальный путь в журнале не обязательно совпадает с путём, который фактически использует задача. Вторая ошибка — называть приложение просто Xcode.app: после перезаписи при обновлении невозможно определить, какую версию использовали старые задачи. Третья — проверять только компилятор, но не SDK и путь xcrun, из-за чего в сборку попадают непредусмотренные инструменты.
Перед сдачей выполняйте проверки в следующем порядке:
- Каталог приложения Xcode содержит точный номер версии, а предыдущая версия остаётся доступной.
- Требуемая версия записана в репозитории и не зависит от человеческой памяти.
- Каждая задача CI отдельно задаёт
DEVELOPER_DIR. - Предварительная проверка одновременно сверяет Xcode, Swift, SDK и пути к инструментам.
- Команда сборки явно задаёт workspace, scheme и конфигурацию.
- Новая версия становится версией по умолчанию только после проверки компиляции, тестов и артефактов.
- Параллельные задачи не изменяют глобальный
xcode-select.
После выполнения этих шагов состояние инструментария облачного Mac можно будет отслеживать по журналам. Если результаты сборки различаются, сначала исключите дрейф версий, а затем исследуйте зависимости, код и конфигурацию задачи вместо слепого перебора нескольких переменных.
Часто задаваемые вопросы
Могут ли параллельные задачи CI использовать разные версии Xcode?
Да. Для каждой задачи задайте собственный DEVELOPER_DIR и не изменяйте глобальный путь xcode-select во время параллельного выполнения.
Достаточно ли проверить только xcodebuild -version?
Нет. Нужно также проверить DEVELOPER_DIR, версии SDK и Swift, а также пути к инструментам, которые возвращает xcrun.
Как безопасно перевести проекты на новую версию Xcode?
Установите её под именем с номером версии, выполните первичную подготовку и прогоните проверки, сборку и тесты, сохранив предыдущую версию.
Нужен выделенный физический узел Mac mini
Просмотрите доступные конфигурации и узлы, а также фиксированные расчётные периоды, чтобы перенести шаги статьи в облачную среду Mac для постоянного использования.