Инженерные заметки Oakmini

Фиксация Xcode для воспроизводимых сборок на облачном Mac

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

Фиксация Xcode для воспроизводимых сборок на облачном Mac

Когда один облачный 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, из-за чего в сборку попадают непредусмотренные инструменты.

Перед сдачей выполняйте проверки в следующем порядке:

  1. Каталог приложения Xcode содержит точный номер версии, а предыдущая версия остаётся доступной.
  2. Требуемая версия записана в репозитории и не зависит от человеческой памяти.
  3. Каждая задача CI отдельно задаёт DEVELOPER_DIR.
  4. Предварительная проверка одновременно сверяет Xcode, Swift, SDK и пути к инструментам.
  5. Команда сборки явно задаёт workspace, scheme и конфигурацию.
  6. Новая версия становится версией по умолчанию только после проверки компиляции, тестов и артефактов.
  7. Параллельные задачи не изменяют глобальный xcode-select.

После выполнения этих шагов состояние инструментария облачного Mac можно будет отслеживать по журналам. Если результаты сборки различаются, сначала исключите дрейф версий, а затем исследуйте зависимости, код и конфигурацию задачи вместо слепого перебора нескольких переменных.

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

Могут ли параллельные задачи CI использовать разные версии Xcode?

Да. Для каждой задачи задайте собственный DEVELOPER_DIR и не изменяйте глобальный путь xcode-select во время параллельного выполнения.

Достаточно ли проверить только xcodebuild -version?

Нет. Нужно также проверить DEVELOPER_DIR, версии SDK и Swift, а также пути к инструментам, которые возвращает xcrun.

Как безопасно перевести проекты на новую версию Xcode?

Установите её под именем с номером версии, выполните первичную подготовку и прогоните проверки, сборку и тесты, сохранив предыдущую версию.

Нужен выделенный физический узел Mac mini

Просмотрите доступные конфигурации и узлы, а также фиксированные расчётные периоды, чтобы перенести шаги статьи в облачную среду Mac для постоянного использования.

Арендовать Mac mini