Задача архивирования завершается успешно, но конвейер останавливается на этапе -exportArchive — при таком сбое последняя строка «export failed» часто уводит расследование в неверном направлении. Настоящей причиной может быть несовместимость способа экспорта с материалами для подписи, изменение прав внутри архива или использование в CI другой установки Xcode. Вместо многократных перезапусков следует сохранить один и тот же xcarchive, а затем разделить архивирование, экспорт и диагностику на независимо воспроизводимые этапы.
Сначала зафиксируйте состояние после сбоя
После сбоя не очищайте рабочий каталог и не перезаписывайте исходный архив. Сохраните как минимум xcarchive, фактически использованный файл ExportOptions.plist, полный стандартный вывод, а также путь к Xcode и его версию. Выделенные физические узлы RunnerVM подходят для сохранения такого состояния, но конвейер всё равно должен явно копировать диагностические файлы в каталог артефактов задачи.
set -euo pipefail
RUN_DIR="$PWD/export-diagnostics"
ARCHIVE_PATH="$PWD/build/App.xcarchive"
mkdir -p "$RUN_DIR"
xcode-select -p > "$RUN_DIR/xcode-path.txt"
xcodebuild -version > "$RUN_DIR/xcode-version.txt"
sw_vers > "$RUN_DIR/macos-version.txt"
cp ExportOptions.plist "$RUN_DIR/ExportOptions.plist"
ditto "$ARCHIVE_PATH" "$RUN_DIR/App.xcarchive"
Также зафиксируйте контрольную сумму архива. Если впоследствии кто-либо заменит архив, это позволит сразу обнаружить, что объект расследования изменился.
ditto -c -k --keepParent "$ARCHIVE_PATH" "$RUN_DIR/App.xcarchive.zip"
shasum -a 256 "$RUN_DIR/App.xcarchive.zip" > "$RUN_DIR/checksums.txt"
Сбой экспорта не означает, что архив недействителен. Сначала подтвердите целостность архива, а затем определите, относится ли проблема к этапу архивирования или распространения.
Проверьте готовность архива к экспорту
По сути, xcarchive — это каталог. Сначала убедитесь, что метаданные, пакет приложения и исполняемый файл существуют, а затем прочитайте заявленные в архиве идентификатор и версию приложения. Не формируйте путь на основе имени проекта — получите его из Info.plist.
INFO="$ARCHIVE_PATH/Info.plist"
APP_RELATIVE=$(/usr/libexec/PlistBuddy -c \
"Print :ApplicationProperties:ApplicationPath" "$INFO")
APP_PATH="$ARCHIVE_PATH/Products/$APP_RELATIVE"
plutil -lint "$INFO"
test -d "$APP_PATH"
test -f "$APP_PATH/Info.plist"
plutil -p "$INFO"
plutil -p "$APP_PATH/Info.plist"
codesign --verify --deep --strict --verbose=2 "$APP_PATH"
В первую очередь сверьте ApplicationPath, CFBundleIdentifier, номер версии и результат проверки подписи. Если пакет приложения отсутствует или команда codesign --verify уже завершается с ошибкой, искать проблему нужно в команде archive, скриптах этапа сборки или процессе копирования артефактов, а не в параметрах экспорта.
Сопоставьте права приложения и профиль подготовки
На этапе экспорта Xcode повторно проверяет взаимосвязь подписей. Экспортируйте фактические права приложения и права из встроенного профиля подготовки в отдельные файлы, а затем сравните их структуру.
codesign -d --entitlements "$RUN_DIR/app-entitlements.plist" "$APP_PATH"
PROFILE="$APP_PATH/embedded.mobileprovision"
security cms -D -i "$PROFILE" > "$RUN_DIR/profile.plist"
plutil -extract Entitlements xml1 \
-o "$RUN_DIR/profile-entitlements.plist" \
"$RUN_DIR/profile.plist"
plutil -p "$RUN_DIR/app-entitlements.plist"
plutil -p "$RUN_DIR/profile-entitlements.plist"
Не сравнивайте только текстовый порядок записей в файлах. Проверьте, разрешает ли профиль права, используемые приложением, и совпадают ли идентификаторы приложения и команды, а также права, зависящие от окружения. Каждое расширение также необходимо проверить отдельно: успешная проверка основного приложения не гарантирует, что все встроенные компоненты можно экспортировать.
Проверьте ExportOptions.plist
Сначала выполните plutil -lint ExportOptions.plist, чтобы исключить ошибки формата, а затем сверьте допустимые ключи и значения со справкой текущей версии Xcode. В разных версиях могут меняться значения method или прекращаться поддержка старого синтаксиса, поэтому не запускайте конфигурацию, просто скопированную из прежней задачи.
Минимальная конфигурация может содержать только поля, действительно необходимые текущему процессу распространения:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>method</key>
<string>development</string>
<key>signingStyle</key>
<string>manual</string>
<key>stripSwiftSymbols</key>
<true/>
</dict>
</plist>
Значение method здесь приведено только как пример экспорта для разработки и не подходит автоматически для других способов распространения. Сначала выполните следующие команды и ориентируйтесь на описание из текущего набора инструментов:
plutil -lint ExportOptions.plist
xcodebuild -help > export-help.txt
grep -A 120 "Available keys for -exportOptionsPlist" export-help.txt
При использовании ручной подписи также проверьте, охватывают ли заданные в конфигурации сопоставления основное приложение и каждое расширение. Отсутствующее сопоставление, ошибочный идентификатор или смешение способов подписи часто обнаруживаются только на этапе экспорта.
Повторите экспорт отдельно и сохраните полный журнал
Убедившись, что архив действителен, запустите экспорт отдельно, используя фиксированные абсолютные пути. Не позволяйте скрипту немедленно удалять временный каталог после сбоя.
set +e
xcodebuild -exportArchive \
-archivePath "$ARCHIVE_PATH" \
-exportPath "$RUN_DIR/exported" \
-exportOptionsPlist "$RUN_DIR/ExportOptions.plist" \
2>&1 | tee "$RUN_DIR/export.log"
STATUS=${PIPESTATUS[0]}
set -e
printf '%s
' "$STATUS" > "$RUN_DIR/export-status.txt"
exit "$STATUS"
В журнале ищите первую конкретную ошибку, а не итоговую строку в конце. Для первичной фильтрации можно использовать упоминания подписи, профиля подготовки, прав и способа экспорта:
grep -Ein \
"error:|provision|entitlement|certificate|signing|export" \
"$RUN_DIR/export.log" > "$RUN_DIR/export-errors.txt" || true
Иногда xcodebuild указывает в выводе путь к пакету журналов распространения. Сохраните этот каталог целиком. Обычно он содержит решения, принятые на каждом этапе процесса IDEDistribution, и позволяет точнее определить первопричину, чем несколько строк ошибки, оставшихся в журнале конвейера.
Составьте контрольный список приёмки после исправления
После исправления не ориентируйтесь только на код завершения. Как минимум убедитесь, что в каталоге экспорта присутствует целевой файл, проверка подписи проходит успешно, идентификатор и версия приложения совпадают с архивом, а итоговый ExportOptions.plist сохранён вместе с версией Xcode.
Рекомендуется закрепить в конце задачи следующий контрольный список:
- Контрольная сумма сжатого архива не изменилась.
- Текущий результат
xcode-select -pсоответствует ожидаемому набору инструментов. - Основное приложение и все расширения успешно проходят проверку подписи.
- Права приложения не выходят за пределы разрешённых профилем подготовки.
- Конфигурация экспорта проходит проверку
plutil, а её ключи и значения поддерживаются текущей версией Xcode. - Журнал экспорта, пакет журналов распространения и итоговые артефакты сохранены вместе.
- Исправление меняет только одну переменную и воспроизводимо проверяется на исходном архиве.
При таком подходе экспорт архива перестаёт быть непрозрачной операцией, результат которой сложно объяснить. Команда сможет чётко различать смену набора инструментов, ошибки конфигурации, нарушения связей подписи и повреждение архива, а большинство исправлений можно будет проверять без повторной сборки.
Часто задаваемые вопросы
Нужно ли сразу пересобирать архив после ошибки экспорта?
Нет. Сначала сохраните исходный xcarchive и проверьте его метаданные, пакет приложения, встроенный профиль и права подписи. Пересборка может уничтожить важные признаки ошибки.
Можно ли использовать один ExportOptions.plist с разными версиями Xcode?
Только после проверки. Изучите справку xcodebuild выбранной версии Xcode, сверьте допустимые значения method и проверьте синтаксис файла перед экспортом.
Запускайте следующую сборку на выделенном физическом Mac
Выберите облачный Mac-узел под длительность задачи, а затем управляйте заказами, данными для подключения и обращениями в службу поддержки через консоль.