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

Автоматизация экспорта Unity iOS и проверки Xcode на облачном Mac

Автоматизация экспорта Unity iOS и проверки Xcode на облачном Mac

Проект Unity может без проблем экспортироваться для iOS локально, но на облачном Mac останавливаться на импорте ресурсов, обработке нативных зависимостей или линковке. Главная сложность не в самом сбое, а в том, что все этапы объединены одной командой: в журнале остаётся лишь сообщение об ошибке сборки, по которому невозможно понять, следует ли заново запускать Unity, очищать кэш или проверять проект Xcode. Надёжнее разделить конвейер на два независимых рубежа: сначала создать пригодный для аудита проект Xcode, а затем проверить нативную компиляцию без выполнения кодовой подписи.

Сначала определите границы входных данных и артефактов

Для воспроизводимой сборки необходимо зафиксировать как минимум четыре вида входных данных: версию коммита, версию редактора Unity, файл блокировки пакетных зависимостей и список сцен. Скрипт не должен на лету читать настройки редактора с компьютера конкретного разработчика. Не следует также перезаписывать проект Xcode от предыдущего экспорта и сразу продолжать его компиляцию.

Для каждого задания рекомендуется создавать отдельный каталог:

Путь Назначение Можно кэшировать
Source/ Проект Unity из текущего коммита Нет
Library/ Результаты импорта ресурсов При определённых условиях
Build/iOS/ Проект Xcode, экспортированный в текущем задании Нет
.build/DerivedData/ Промежуточные файлы нативной компиляции При определённых условиях
Artifacts/ Журналы, сводки и результаты проверки Нет

Ключ кэша Library должен учитывать версию Unity, целевую платформу, а также хеши Packages/manifest.json и Packages/packages-lock.json. При изменении любого из этих значений ресурсы необходимо импортировать заново. DerivedData также нельзя напрямую использовать повторно между разными версиями Xcode или разными настройками проекта.

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

Экспорт проекта Xcode в пакетном режиме

Точку входа для экспорта следует разместить в Assets/Editor/IosExport.cs, а список сцен формировать из включённых элементов Build Settings. Это избавляет от необходимости поддерживать два отдельных списка — в скрипте и в интерфейсе редактора.

using System;
using System.Linq;
using UnityEditor;
using UnityEditor.Build.Reporting;

public static class IosExport
{
    public static void Run()
    {
        var scenes = EditorBuildSettings.scenes
            .Where(scene => scene.enabled)
            .Select(scene => scene.path)
            .ToArray();

        if (scenes.Length == 0)
            throw new InvalidOperationException("No enabled scenes.");

        var options = new BuildPlayerOptions
        {
            scenes = scenes,
            locationPathName = "Build/iOS",
            target = BuildTarget.iOS,
            options = BuildOptions.CleanBuildCache
        };

        var report = BuildPipeline.BuildPlayer(options);
        if (report.summary.result != BuildResult.Succeeded)
            throw new InvalidOperationException("Unity export failed.");
    }
}

При запуске явно передайте каталог проекта, путь к журналу и имя метода:

"$UNITY_EDITOR" \
  -batchmode \
  -nographics \
  -quit \
  -projectPath "$PWD" \
  -executeMethod IosExport.Run \
  -logFile "$PWD/Artifacts/unity-export.log"

Перед началом задания следует удалить Build/iOS, но не нужно безусловно удалять Library. После завершения проверьте одновременно код возврата, наличие файла журнала и Build/iOS/Unity-iPhone.xcodeproj. Само наличие каталога ещё не означает, что экспорт выполнен успешно: незавершённые артефакты могут остаться и после сбоя.

Разделите нативные зависимости и проверку компиляции

Успешный экспорт из Unity означает лишь, что генератор завершил работу. Он не гарантирует, что Objective-C, Swift, результаты IL2CPP и нативные библиотеки удастся скомпилировать вместе. Сначала выполните предусмотренную проектом установку нативных зависимостей, а затем определите, что нужно собирать — workspace или project. Если инструмент управления зависимостями создал .xcworkspace, дальнейшая сборка .xcodeproj часто приводит к ошибкам отсутствующих модулей или библиотек при линковке.

set -euo pipefail

cd Build/iOS
rm -rf ../../.build/DerivedData

if [ -d "Unity-iPhone.xcworkspace" ]; then
  xcodebuild \
    -workspace Unity-iPhone.xcworkspace \
    -scheme Unity-iPhone \
    -configuration Release \
    -sdk iphoneos \
    -derivedDataPath ../../.build/DerivedData \
    CODE_SIGNING_ALLOWED=NO \
    build
else
  xcodebuild \
    -project Unity-iPhone.xcodeproj \
    -scheme Unity-iPhone \
    -configuration Release \
    -sdk iphoneos \
    -derivedDataPath ../../.build/DerivedData \
    CODE_SIGNING_ALLOWED=NO \
    build
fi

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

Настройте явную инвалидацию кэша вместо догадок

Распространённая ошибка в проектах Unity — использовать в качестве ключа кэша только имя ветки. Два коммита в одной ветке вовсе не гарантируют одинаковые результаты импорта ресурсов. Сначала можно сформировать хеш входных данных:

{
  printf '%s
' "$UNITY_VERSION"
  printf '%s
' "$XCODE_VERSION"
  shasum -a 256 Packages/manifest.json
  shasum -a 256 Packages/packages-lock.json
  find Assets -name '*.asmdef' -o -name '*.rsp' | sort | xargs shasum -a 256
} | shasum -a 256 | awk '{print $1}' > Artifacts/cache-key.txt

Не кэшируйте весь рабочий каталог

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

Архивируйте минимальный набор данных для последующего анализа

После успешной сборки недостаточно сохранять только отметку об успехе. Как минимум нужно архивировать полный журнал Unity, вывод Xcode, хеш коммита, версии инструментов, ключ кэша и сводку экспортированного проекта. В сводке проекта можно зафиксировать scheme, configuration и параметры сборки, но из неё следует исключить учётные данные, токены и приватные локальные пути.

Типовые сбои можно быстро распределить по этапам:

В завершение задайте «успешный экспорт» и «успешную нативную компиляцию» как два независимых состояния. Если экспорт завершился ошибкой, запускать Xcode не требуется. Если не прошла нативная компиляция, не нужно повторно импортировать все ресурсы. Такой конвейер проще перезапускать, его журналы короче, а границы ответственности понятнее.

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

Зачем отделять экспорт Unity от финальной сборки Xcode?

Так сбой сценариев и импорта ресурсов не смешивается с ошибками нативных зависимостей, компилятора и линковщика. Сборка без подписи сначала подтверждает техническую целостность проекта.

Можно ли использовать один каталог Library для разных проектов?

Не следует. Ключ кэша должен учитывать версию Unity, файлы блокировки зависимостей и целевую платформу; при любом отличии Library нужно создать заново.

Когда требуется xcworkspace вместо xcodeproj?

Если после установки нативных зависимостей появился workspace, собирать нужно его. Project используют только при отсутствии workspace, поэтому проверку выполняют после подготовки зависимостей.

Выделенный физический Mac

Разместите следующую сборку iOS на MiniDebug M4.

В стандартную комплектацию входят M4, 16 ГБ ОЗУ и SSD на 256 ГБ. Доступна аренда на день, неделю, месяц или квартал, а выбрать можно один из пяти узлов: Сингапур, Токио, Южная Корея (Сеул), Гонконг или восток США. Фактическая доступность отображается в консоли в реальном времени.

Выбрать узел и оформить заказ