Симптом — пайплайн заявляет Xcode 26, а в журнале появляется SDK от Xcode 27.
Быстрое решение — установите версии в отдельные каталоги, а в каждой задаче явно задавайте DEVELOPER_DIR. Не переключайте глобальный xcode-select во время работы общего узла. Xcode 27 и Xcode 26 действительно могут сосуществовать на совместимом Mac с Apple silicon, но стабильность определяется не фактом установки, а доказательством того, какой инструментарий использовала конкретная задача.
Кому нужен этот разбор
Эта схема рассчитана на команды, которые продолжают выпускать приложения через Xcode 26 и параллельно проверяют проект в Xcode 27 Beta.
Она также нужна DevOps-инженерам, маршрутизирующим репозитории и ветки к разным версиям Xcode, и администраторам удалённых Mac-узлов, отвечающим за обновления, место на диске и восстановление.
Если вам требуется только одна стабильная версия для локальной разработки, совместная установка добавит операционные риски без заметной пользы. В таком случае лучше закрепить один инструмент и не усложнять узел.
Последняя проверка этой инструкции выполнена 1 сентября 2026 года. Системные требования и ограничения Apple silicon сверены с требованиями Apple к Xcode, а поведение Beta следует дополнительно перепроверять по заметкам к выпуску Xcode 27. В период Beta требования, номера сборок и поведение компонентов могут измениться.
Почему одна установка ломает две линии сборки
На общем узле конфликт обычно выглядит не как ошибка установки. Приложение Xcode запускается, часть команд проходит, но разные процессы получают разные пути к инструментам.
Есть несколько независимых источников выбора:
- глобальный выбор командных инструментов через
xcode-select; - переменная окружения
DEVELOPER_DIR; - PATH и окружение оболочки;
- команды, которые запускают дочерние процессы внутри CI;
- кэш DerivedData и уже созданные архивы;
- симуляторы и дополнительные платформы, установленные для другой версии.
Например, оболочка агента могла стартовать с Xcode 26, а скрипт задания позднее изменить глобальный выбор на Xcode 27. Другая задача, запущенная параллельно, в этот момент получит уже новый путь. В результате название версии в конфигурации CI не доказывает фактический источник xcodebuild.
Перед каждой сборкой сохраняйте в журнал как минимум:
echo "DEVELOPER_DIR=${DEVELOPER_DIR:-<не задан>}"
xcode-select -p
xcodebuild -version
xcrun --find xcodebuild
xcrun --show-sdk-path
swiftc --version
Эти команды должны выполняться в том же процессе и с тем же окружением, из которого запускается сборка. Apple отдельно описывает настройку командных инструментов и выбор активного набора в официальной документации Xcode. Это важно: проверка в интерактивном SSH-сеансе не заменяет проверку внутри CI-агента.
Сценарий отказа: «версия указана правильно, источник — нет»
Представьте два задания. Первое собирает релиз из ветки release-placeholder, второе проверяет новый API из ветки beta-placeholder. В YAML-файле первого задания написано «Xcode 26», но его шаг запускается после скрипта, изменившего глобальный выбор инструментов. В журнале появляются SDK и путь от Xcode 27.
Исправление состоит не в повторной установке Xcode. Сначала зафиксируйте путь к каждому приложению, затем задайте DEVELOPER_DIR непосредственно перед всеми командами сборки. После этого сравните ожидаемый и фактический путь в журнале. Если проверка не совпала, задание должно завершиться до создания архива.
Отдельные каталоги против глобального переключателя
Ниже — рабочее разделение ответственности. Оно не заменяет проверку на конкретной машине, но помогает выбрать безопасную схему.
| Операция | Глобальный xcode-select |
DEVELOPER_DIR на уровне задачи |
|---|---|---|
| Область действия | Влияет на активный выбор инструментов системы и процессы, которые его используют | Ограничивает выбор текущим процессом и его дочерними командами |
| Подходит для | Выделенного узла с одной версией Xcode или ручной диагностики | Общего CI-узла с несколькими версиями |
| Риск при параллельных заданиях | Один процесс может изменить состояние для другого | Конфликт ниже, если переменная задаётся внутри каждой задачи |
| Удобство отката | Нужно вернуть системный выбор и проверить окружение | Достаточно изменить маршрут конкретного задания |
| Контроль результата | Требуется проверять состояние после каждой смены | Проверяется перед стартом сборки и сохраняется в журнале |
Для общего удалённого Mac выбирайте DEVELOPER_DIR. Глобальный xcode-select оставьте как узловой дефолт для ручного обслуживания или аварийной диагностики. Параллельные задания не должны менять его во время работы.
Пути должны быть различимыми и не пересекаться. Например:
/Applications/Xcode-26.app
/Applications/Xcode-27-Beta.app
Названия здесь условные. Используйте свои каталоги, но не храните две версии под одним именем. Перезапись усложняет аудит, обновление и возврат к прежнему состоянию.
Можно ли держать Xcode 27 и Xcode 26 на одном Mac одновременно?
Да, если Mac и установленная macOS удовлетворяют требованиям обеих версий. Перед загрузкой проверяйте именно таблицу совместимости Apple, а не только архитектуру процессора. Системные требования Xcode нужно сверять перед каждой установкой Beta или обновлением macOS.
Не изменяйте содержимое приложения вручную и не пытайтесь обходить несовместимость подменой пакетов. Если хост не поддерживает нужную версию, используйте отдельный совместимый узел.
Установка без перезаписи и скрытого обновления
Разделите операцию на проверяемые этапы.
-
Снимите инвентаризацию узла.
Сохраните модель Mac, архитектуру, версию macOS, свободное место, текущий путь командных инструментов и список установленных Xcode. В отчёт добавьте источник загрузки и номер сборки каждого приложения. -
Проверьте совместимость.
Сопоставьте требования Xcode 26 и Xcode 27 с фактической macOS. Для Beta не переносите выводы из старой документации: Apple может изменить поддерживаемый диапазон системы или состав компонентов. -
Установите приложения в разные каталоги.
Не запускайте обновление поверх каталога, который использует рабочая линия. Формальное имя приложения не должно быть единственным способом различить версии — путь должен присутствовать в конфигурации CI. -
Запустите каждую версию отдельно.
Первый запуск может потребовать принятия лицензии, подготовки внутренних компонентов и разрешений. Выполните инициализацию в том контексте пользователя, под которым работает агент. Открывающийся интерфейс ещё не означает готовность к автоматической сборке. -
Привяжите компоненты к нужному пути.
Если проекту нужны дополнительные платформы или симуляторы, устанавливайте их через соответствующий экземпляр Xcode. Apple описывает установку дополнительных компонентов Xcode и добавление дополнительных симуляторов. Не устанавливайте все доступные runtime на общий узел без требования проекта. -
Запишите два профиля маршрутизации.
Для стабильной линии задайте путь к Xcode 26, для проверочной — к Xcode 27 Beta. Переменная должна экспортироваться внутри задания, а не только в профиле SSH-пользователя.
Пример:
bash
export DEVELOPER_DIR="/Applications/Xcode-26.app/Contents/Developer"
xcodebuild -version
xcodebuild \
-workspace "AppPlaceholder.xcworkspace" \
-scheme "ReleasePlaceholder" \
-configuration Release \
-destination "generic/platform=iOS" \
archive
Для тестовой линии меняется только путь и параметры, предусмотренные самим проектом:
bash
export DEVELOPER_DIR="/Applications/Xcode-27-Beta.app/Contents/Developer"
xcodebuild -version
Не копируйте эти имена в рабочую систему без адаптации. Пользователь, хост, рабочая область и схема приведены как безопасные заполнители.
-
Поставьте read-only-предпроверку.
Сравнитеxcodebuild -version, путьxcrun, SDK, Swift-компилятор и ожидаемыйDEVELOPER_DIR. Проверка не должна изменять системное состояние. Если один параметр не совпал, остановите задачу. -
Только после этого запускайте сборку.
Не продолжайте после провала предпроверки «для получения дополнительных логов». Такой архив уже может быть создан не теми инструментами и загрязнить кэш.
Компоненты и симуляторы: открытие приложения не является приёмкой
Командная сборка может пройти, а UI-тест — остановиться из-за отсутствующего runtime. Обратная ситуация тоже возможна: симулятор присутствует, но выбран не тот SDK.
Разделяйте три проверки:
- архивирование без симулятора;
- модульные тесты;
- UI-тесты на конкретном устройстве или runtime.
Для каждой версии повторите целевую команду обнаружения SDK:
export DEVELOPER_DIR="/Applications/Xcode-26.app/Contents/Developer"
xcodebuild -showsdks
xcrun simctl list runtimes
Затем выполните аналогичные команды с Xcode 27 Beta. Сравнивайте не только наличие записи, но и соответствие платформе проекта. Если линия выпускает архивы, ей не обязательно устанавливать все симуляторы. Если линия выполняет UI-тесты, нужный runtime должен быть установлен и доступен агенту.
Будут ли версии совместно использовать симуляторы и DerivedData?
Часть состояния может находиться на уровне пользователя или системы, поэтому рассчитывать на полную автоматическую изоляцию нельзя. DerivedData, каталоги архивов, логи и результаты тестов следует разделять по версии и линии.
Пример структуры:
/Users/ci/BuildState/derived/xcode-26/
/Users/ci/BuildState/derived/xcode-27-beta/
/Users/ci/BuildState/archives/xcode-26/
/Users/ci/BuildState/archives/xcode-27-beta/
Параметры вывода можно передать явно:
xcodebuild \
-derivedDataPath "/Users/ci/BuildState/derived/xcode-26" \
-archivePath "/Users/ci/BuildState/archives/xcode-26/App.xcarchive"
Конкретные build settings и их взаимодействие проверяйте по справочнику настроек сборки Apple. Не считайте пустую папку DerivedData доказательством чистого состояния: зависимости, симуляторы и архивы хранятся отдельно.
Изоляция кэшей, зависимостей и подписи
Кэш сокращает повторную работу, но одновременно способен скрыть несовместимость. Сначала выполните сборку с чистым DerivedData и отдельной папкой зависимостей. Затем повторите тот же сценарий с разрешённым кэшем.
Минимальный порядок такой:
- создать уникальные пути для DerivedData и архива;
- удалить или переименовать состояние только тестовой линии;
- собрать проект выбранным Xcode;
- сохранить журнал идентификации инструментов;
- повторить сборку с теми же параметрами;
- сравнить результат и причину использования кэша.
Не делайте две копии сертификатов и профилей только потому, что установлены две версии Xcode. Важнее единая контролируемая граница доступа агента и проверка, что обе версии видят разрешённые signing identities и provisioning profiles.
Секреты не должны попадать в командную строку и журнал. Не выводите приватные ключи, токены или содержимое сертификатов. Проверяйте наличие нужной идентичности безопасным способом, принятым в вашей системе CI.
Если архив первой линии случайно использует кэш второй, это не всегда проявится ошибкой. Поэтому для каждого результата сохраняйте:
- выбранный путь
DEVELOPER_DIR; - версию и build number Xcode;
- фактический SDK;
- путь DerivedData;
- путь архива;
- идентификатор схемы;
- итог проверки подписи.
DEVELOPER_DIR или xcode-select: граница выбора
Когда лучше оставить xcode-select, а когда перейти на DEVELOPER_DIR?
Если узел выделен одной линии и задания не выполняются параллельно с другой версией, глобальный выбор может быть приемлемым. На общем узле, где разные репозитории требуют Xcode 26 и Xcode 27, выбирайте DEVELOPER_DIR.
xcode-select меняет состояние, которое могут увидеть другие процессы. Это делает его плохим механизмом маршрутизации в конкурентной очереди. DEVELOPER_DIR можно задать перед конкретным вызовом и передать дочерним процессам, не перестраивая глобальную конфигурацию. Оба механизма нужно проверять через фактические команды, а не по значению переменной в панели CI.
При необходимости определить активные инструменты вручную используйте официальные рекомендации Apple по настройке командных инструментов. После диагностики верните узел в заранее описанное состояние. Диагностическая смена глобального пути не должна становиться частью обычного job-скрипта.
Матрица приёмки и безопасный откат
После установки не расширяйте использование Xcode 27 сразу на все ветки. Сначала проведите матрицу из трёх контуров:
- релизный контур — сборка и подписание через Xcode 26;
- проверочный контур — тестовая сборка через Xcode 27 Beta;
- контур отката — повторный запуск релизной сборки после перезапуска узла или отмены обновления Beta.
Для каждого контура зафиксируйте ожидаемый путь, фактическую версию, SDK, результат тестов, архив и подпись. Затем выполните пять проверок:
- новый запуск задания на чистом рабочем каталоге;
- повторную сборку с теми же параметрами;
- перезапуск удалённого Mac;
- повторную маршрутизацию после перезапуска агента;
- имитацию обновления Xcode 27 Beta без изменения каталога Xcode 26.
После обновления повторно сверяйте заметки к выпуску Xcode 26 и актуальные заметки к Xcode 27. Если изменились требования macOS, поведение SDK или механизм установки компонентов, старый результат приёмки больше не является достаточным доказательством.
Решение принимайте по условиям:
- Если обе версии проходят предпроверку, сборку, тесты, архивирование, подпись и восстановление после перезапуска, оставляйте общий узел, но сохраняйте маршрутизацию через
DEVELOPER_DIR. - Если релизная линия стабильна, а Beta требует отдельного runtime или конфликтует с очередью, выделите отдельный удалённый Mac для Xcode 27.
- Если после обновления Beta меняются SDK или компоненты, а повторная приёмка не завершена, заморозьте расширение Beta-линии и верните новые задания на проверенный Xcode 26.
- Если задания всё ещё меняют глобальный
xcode-select, не объявляйте узел готовым: сначала удалите гонку состояний из пайплайна.
На этом этапе удалённый Mac становится не просто машиной с двумя приложениями, а воспроизводимым CI-узлом. Для подключения и ручной проверки вам пригодятся инструкция по доступу к удалённой консоли MacHTML и раздел помощи по работе с сервисом. Используйте ручной доступ для диагностики, но не заменяйте им автоматические проверки в job.
Когда отдельный узел лучше общей машины
Совместная установка экономит обслуживание только при контролируемой очереди и понятных границах состояния. Она плохо подходит, если релизные задания выполняются круглосуточно, Beta часто обновляется, а UI-тесты требуют разные runtime одновременно.
Текущая схема на Linux или Windows обычно проигрывает не из-за самой операционной системы, а из-за отсутствия нативного macOS-инструментария: нельзя надёжно использовать полный набор Xcode, приходится поддерживать обходные среды, а диагностика подписи и симуляторов становится менее прямой. Виртуализация и неофициальная установка добавляют зависимость от совместимости хоста и усложняют восстановление.
Покупка Mac mini, напротив, разумна для долгой стабильной нагрузки, физического доступа к устройству и постоянного владения диском. Но для временной Beta-проверки она означает отдельную капитальную покупку, обслуживание и простой после неудачного обновления.
Если вам нужна именно изолированная проверочная линия на период миграции, аренда удалённого MacHTML позволяет не трогать действующий релизный узел и подключаться к реальному Mac через VNC, SSH или веб-консоль с root-доступом. Перед выбором проверьте актуальные условия аренды MacHTML: срок, способ доступа, доступность нужной конфигурации и правила восстановления должны соответствовать вашей матрице приёмки.
Сначала завершите проверки Xcode 26 и Xcode 27 на текущем узле. Если параллельность, требования к откату или частота обновлений создают конфликт, подготовьте отдельный удалённый Mac для Xcode 27, а не заменяйте им рабочую линию.
Читайте также: Mac mini M4 для CI/CD: ускорение сборок и настройка облачной ноды Тестирование совместимости приложений с macOS 27: чек-лист перед релизом Удалённый Mac mini в облаке: SSH, рабочая среда и доступ к симулятору
Подготовьте удалённый Mac для задач CI
С MacHTML вы можете арендовать удалённый Mac для сборки, тестирования и проверки проектов в разных версиях Xcode. Разделяйте рабочие окружения, кэши и инструменты, чтобы удобнее совмещать Xcode 26 и Xcode 27 на одном узле. Подключайтесь к Mac удалённо и управляйте конфигурацией узла без необходимости поддерживать отдельное физическое устройство. Выберите подходящий тариф MacHTML и организуйте стабильный удалённый CI-процесс для вашей команды.