DevOps / CI·CD 20 мая 2026 г.

2026-05-20 Пути профилей provisioning Xcode 16 и параллельный многопоточный archive на безголовом арендованном облачном Mac Apple Silicon (HK / JP / KR / SG / US)

MacXCode Engineering Team 20 мая 2026 г. ~18 мин чтения

Команды, которые арендуют билдеры Mac mini M4 в Гонконге, Токио, Сеуле, Сингапуре или США и гоняют Xcode 16 без графической сессии, сталкиваются с тихим breaking change: профили provisioning больше не «живут» только под устаревшим деревом MobileDevice. Xcode 16 читает и пишет каноническое хранилище в UserData, тогда как старые скрипты, хелперы Fastlane и сторонние CLI по-прежнему копируют .mobileprovision в путь «до Xcode». Когда на одном арендованном хосте добавляются несколько параллельных дорожек xcodebuild archive, этот дрейф путей превращается в гонки подписи, фантомные ошибки «профиль не найден» и прерывистые сбои errSecInternalComponent, которые на ноутбуке разработчика не воспроизводятся. Этот рунбук 2026-05-20 сразу формулирует политику: зеркалировать профили в UserData и legacy, пока каждый инструмент на машине не осознаёт Xcode 16, изолировать CI-связки ключей по дорожкам и корни DerivedData, и ограничить параллельные archive-дорожки двумя на стандартном Mac mini M4 без измеренного запаса NVMe и унифицированной памяти. Вы получите таблицу сравнения путей, матрицу многопоточности, триаж безголовой подписи, восемь шагов внедрения и ответы FAQ, согласованные с нашей базовой статьёй про связку ключей и provisioning в CI, руководством авто против ручной подписи и материалом о параллельных задачах xcodebuild.

Кому достаётся дрейф путей provisioning на арендованном безголовом Mac

Сбой редко объявляет себя как «неправильный каталог». Дорожка B успешно архивирует, а дорожка A выдаёт No profile for team 'XXXXXXXXXX' matching, хотя оба job импортировали один UUID. На арендованном хосте радиус поражения больше: golden-образы зашивают вспомогательные скрипты 2024 года, плагины Jenkins всё ещё зовут ~/Library/MobileDevice/Provisioning Profiles, а fastlane-лейн новичка качает в UserData, пока ночной cron обновляет только legacy-папку. Мульти-веточная CI с одним пользователем macOS усиливает дрейф: каждая дорожка читает один и тот же домашний каталог, если пути специально не разделить.

  • Релиз-инженеры, запускающие два и больше параллельных archive для разных bundle ID на одном Mac mini M4, нуждаются в детерминированном разрешении профиля на дорожку.
  • Платформенные команды, ведущие golden-образы, обязаны задокументировать, какой путь читают Xcode 16, xcodebuild и вспомогательные утилиты во время archive и exportArchive.
  • Специалисты по безопасности требуют доказательства, что дорожка A не читает сертификат распространения дорожки B — отдельные связки ключей на дорожку обязательны при горячей замене профилей.

Каждое архитектурное ревью начинайте с обещания главной MacXCode: Apple Silicon рядом с API Apple, затем отметьте, какие артефакты подписи должны жить рядом с checkout на арендованном хосте. Если вы копируете профили только в MobileDevice, считайте это техническим долгом, который блокирует устойчивую многопоточную пропускную способность — наш гайд по удалённому archive написан до сдвига UserData и читается вместе с этим обновлением.

Устаревший MobileDevice и пути UserData профилей Xcode 16

Xcode 16 считает ~/Library/Developer/Xcode/UserData/Provisioning Profiles основным on-disk-хранилищем при загрузке из панели учётных записей Xcode или когда xcodebuild -allowProvisioningUpdates обновляет entitlements. Устаревшее расположение ~/Library/MobileDevice/Provisioning Profiles остаётся важным: многие CI-рецепты, старые действия Fastlane и внутренние shell-инсталляторы пишут только туда. Пока не проверен каждый вход, безопасный операторский дефолт — байт-в-байт копии в обоих деревьях после каждой ротации профиля, с именем файла UUID из plist Apple (security cms -D -i file.mobileprovision → ключ UUID).

Путь Типичный потребитель Поведение Xcode 16 Заметка CI на аренде
~/Library/Developer/Xcode/UserData/Provisioning Profiles IDE Xcode, современный xcodebuild Каноническое чтение/запись для GUI-загрузок Закрепить xcode-select перед копированием; проверить экспортом учётных записей
~/Library/MobileDevice/Provisioning Profiles Legacy-скрипты, часть версий Fastlane По-прежнему учитывается многими CLI-потоками Зеркалировать сюда, пока скрипты не сняты
Staging по дорожке (напр. /var/ci/lane-a/profiles) Свои инсталляторы до копирования Не читается напрямую; снижает гонки в home Копировать в оба канонических пути на job дорожки
API ASC + -allowProvisioningUpdates Потоки автоматической подписи Может заполнять только UserData Изолировать ключи API; см. Fastlane против нативного ASC
Числовой ориентир: держите 7–14 дней перекрывающейся валидности профилей при ротации distribution; дорожки, стартующие посреди ротации, не должны удалять предыдущий UUID, пока не завершены все текущие archive. Логируйте три самых свежих mtime профилей рядом с security find-identity -v -p codesigning, как в базовой статье про связку ключей.

Матрица параллельного многопоточного archive на одном Mac mini M4

Параллельные archive — не «бесплатный параллелизм» на арендованном Mac mini M4: унифицированная память, глубина очереди NVMe и состояние сессии подписи под нагрузкой всё равно частично сериализуются. Матрица ниже предполагает выделенный -derivedDataPath на дорожку, отдельную CI-связку ключей и зеркалированные профили — шаблоны из гайда по параллельным задачам и статьи схемы + xcconfig для нескольких веток.

Число дорожек Типичный запас NVMe Унифицированная память Вердикт оператора
1 серийный archive 120 ГБ+ свободно на системном томе 16 ГБ минимум; 24 ГБ комфортно Дефолт для общих релизных поездов; проще всего логи
2 параллельных archive 120–180 ГБ свободно на дорожку под /var/ci 24 ГБ+ рекомендуется Практический потолок на серийном M4; изолируйте DerivedData и связки ключей
3+ параллельных archive Класс NVMe 2 ТБ; агрессивная уборка 32 ГБ+ или ждите затыков компрессии Только после замеров; планируйте гигиену диска
Archive + тяжёлый тестовый шард Раздельные корни; ModuleCache не шарить Оставьте 4 ГБ запаса под XCTest Предпочтительно серийный archive после тестов или отдельные хосты

Пример вызова archive в разрезе дорожки (ручная подпись с явным specifier):

KEYCHAIN_PATH=/var/ci/lane-a/ci.keychain-db DERIVED=/var/ci/lane-a/dd xcodebuild -workspace App.xcworkspace -scheme Release -configuration Release -archivePath /var/ci/lane-a/out/App.xcarchive -derivedDataPath "$DERIVED" CODE_SIGN_STYLE=Manual PROVISIONING_PROFILE_SPECIFIER='MyApp AppStore' archive

Триаж безголовой подписи, когда профили кажутся «установленными»

Сессии SSH ощущаются интерактивно; launchd и CI-раннеры — нет. Самые частые безголовые сбои после апгрейда до Xcode 16: профили скопированы только в MobileDevice, дистрибутивные идентичности импортированы в связку входа, пока xcodebuild смотрит на пустую CI-связку, и дорожка B разблокирует связку, которая дорожке A ещё нужна монопольно. Считайте каждый archive отпечатком из трёх частей: security list-keychains, security find-identity -v -p codesigning и ls -lt обоих каталогов профилей, ограниченный тремя новейшими UUID.

Когда codesign печатает errSecInternalComponent, разблокируйте CI-связку без диалога до archive, задайте partition lists для доступа codesign и убедитесь, что entitlements профиля совпадают с возможностями таргета — особенно Push, App Groups и Associated Domains после правок портала. Автоподпись через API ASC может скрывать дрейф путей до экспорта; ручные дорожки выставляют его сразу. Прочитайте авто против ручной подписи, прежде чем смешивать стили на одном хосте.

Никогда не гоняйте две дорожки против одной связки входа на общей аренде. Создайте ci-lane-a.keychain-db и ci-lane-b.keychain-db под /var/ci, импортируйте distribution .p12 через security import -k "$KEYCHAIN_PATH" -P "$P12_PASS" -T /usr/bin/codesign и ограничьте security list-keychains -s длительностью job.
Симптом Вероятная причина Следующая команда или исправление
Профиль отсутствует в отчёте Xcode UserData пуст; только legacy-установка Зеркалировать UUID-файл в UserData; повторить archive
errSecInternalComponent Заблокированная связка или неверный partition list security unlock-keychain -p "$PASS" "$KEYCHAIN_PATH"; set-key-partition-list
Дорожка A проходит, B падает на том же коммите Общий DerivedData или гонка профилей Разделить -derivedDataPath; сериализовать копирование профилей
Export успешен, upload отклонён Неверный тип профиля или устаревшие entitlements Проверить по чек-листу export + загрузка ASC

После archive загрузите dSYM до удаления каталогов дорожек — гайд по символикации dSYM предполагает, что archive остаются адресуемыми по дорожке под /var/ci/…/out.

Восемь шагов: пути Xcode 16 + многопоточный archive

  1. Закрепить xcode-select -p на целевой Xcode 16.app; записать xcodebuild -version в манифест golden-образа.
  2. Проверить каждый скрипт, копирующий .mobileprovision; обновить инсталляторы, чтобы писать и в UserData, и в MobileDevice с UUID-именами файлов.
  3. Создать CI-связки ключей по дорожкам и импортировать сертификаты распространения; запретить дорожкам чужие файлы связок.
  4. Экспортировать KEYCHAIN_PATH, PROVISIONING_PROFILE_SPECIFIER (или UUID профиля) и DERIVED_DATA_ROOT в разрезе дорожки в блоках окружения CI.
  5. Сухой прогон xcodebuild -showBuildSettings на дорожку; убедиться, что CODE_SIGN_STYLE соответствует политике из гайда по режимам подписи.
  6. Включать -allowProvisioningUpdates только когда учётные данные API ASC изолированы и мониторятся — см. нативный ASC против Fastlane.
  7. Начать с одной archive-дорожки, пока зеркалирование профилей не стабилизируется; добавить вторую с отдельными корнями NVMe и логами.
  8. Планировать еженедельную уборку диска и ротировать профили с перекрытием; держать горячие копии в staging-каталогах дорожек.

Шаблон неинтерактивной установки профиля (подставьте свой staging-каталог дорожки):

UUID=$(security cms -D -i "$PROFILE" | plutil -extract UUID raw -) && cp "$PROFILE" "$HOME/Library/Developer/Xcode/UserData/Provisioning Profiles/$UUID.mobileprovision" && cp "$PROFILE" "$HOME/Library/MobileDevice/Provisioning Profiles/$UUID.mobileprovision"

FAQ

Где Xcode 16 хранит профили provisioning? Основной путь — ~/Library/Developer/Xcode/UserData/Provisioning Profiles. Устаревшие инструменты могут читать ~/Library/MobileDevice/Provisioning Profiles — зеркалировать оба на CI-хостах, пока все инсталляторы не обновлены.

Сколько параллельных archive-дорожек на Mac mini M4? Две дорожки с запасом NVMe 120–180 ГБ каждая — практичный дефолт; третья дорожка требует 2 ТБ хранилища, агрессивной уборки и аккуратного бюджета памяти по матрице выше.

Почему codesign падает с errSecInternalComponent по SSH? Обычно заблокированная связка ключей, отсутствие partition list для CI-связки или профили только в legacy-пути, пока Xcode 16 сначала разрешает UserData.

Нужно ли дорожкам шарить DerivedData для ускорения сборок? Нет для release-archive — ModuleCache и index store гоняются при параллельных archive. Используйте -derivedDataPath на дорожку и опциональные общие только для чтения кэши зависимостей только после того, как один writer разрешил пакеты.

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

Почему аренда Mac mini M4 подходит под многопоточные archive Xcode 16

Apple Silicon M4 даёт достаточную пропускную способность унифицированной памяти, чтобы компилировать два средних iOS-графа параллельно при изоляции DerivedData и честном ограничении дорожек. Нативная подпись macOS обходит странности связки ключей гипервизора, которые мучают эмулируемые Mac-среды — это важно, когда три ночные ветки каждый раз требуют обновления distribution-профиля. Аренда убирает CapEx в пиковые релизные недели: поднять билдер в Токио для близости к App Store Connect, в Сингапуре для выхода в ASEAN или в США для конечных точек API US-West, затем снизить нагрузку после релиза. Сравните регионы на странице цены, отработайте SSH в справочном центре и сочетайте эту статью с удалёнными сценариями archive при онбординге нового репозитория на безголовую аренду.

Арендуйте Apple Silicon там, где подпись Xcode 16 остаётся предсказуемой

HK / JP / KR / SG / US · безголовый SSH · готово к многопоточному archive