Как проверять пакеты ресурсов SwiftPM в CI на облачном Mac

Как проверять пакеты ресурсов SwiftPM в CI на облачном Mac

Пакет Swift может корректно работать в локальном предпросмотре, но после подключения к задаче архивирования на облачном Mac в приложении появляются пустые значки и стандартные строки либо перестаёт читаться JSON. Самая неприятная особенность таких сбоев обычно не ошибка компиляции, а успешный статус конвейера. SwiftPM обрабатывает Bundle.module, однако не проверяет за команду, полностью ли объявлены ресурсы, совпадают ли имена при обращении к ним и действительно ли файлы попали в итоговый пакет приложения. Поэтому ресурсы нужно проверять как часть результата сборки, а не ждать, пока тестировщик обнаружит пропажу после открытия экрана.

Сначала определите, на каком из трёх этапов пропал ресурс

Перед диагностикой необходимо понять, на каком уровне возникла проблема. На первом уровне файл присутствует в каталоге исходного кода, но соответствующий ресурс не объявлен в target файла Package.swift. На втором ресурс уже собран в .bundle, однако код обращается к bundle основного приложения. На третьем файл попал в пакет приложения, но регистр имени, подкаталог или каталог локализации не совпадает с запросом во время выполнения.

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

«Успешная компиляция» доказывает лишь то, что компилятор принял текущие входные данные. Она не подтверждает, что необходимые во время выполнения файлы попали в поставляемый артефакт.

В Package.swift следует явно выбрать .process или .copy. Для изображений, каталогов ресурсов и локализованного содержимого обычно используется .process. Вариант .copy нужен только для файлов, у которых необходимо сохранить исходную структуру каталогов или точное содержимое на уровне байтов. Не копируйте в пакет ресурсов весь каталог репозитория целиком: вместе с ним в сборку могут попасть тестовые данные, временные результаты и даже конфигурации, которые не должны поставляться.

Зафиксируйте контракт ресурсов с помощью списка

При проверке ресурсов недостаточно сравнить количество файлов. После добавления нового ресурса, удаления старого или переименования общее число файлов может остаться прежним. Надёжнее хранить в репозитории файл ci/expected-resources.txt, где каждая строка содержит путь относительно корневого каталога bundle ресурсов:

Assets.car
Defaults/config.json
en.lproj/Localizable.strings
zh-Hans.lproj/Localizable.strings

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

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

Проверяйте пакет приложения сразу после сборки

Каждая задача должна использовать отдельный каталог DerivedData. Иначе старый bundle, оставшийся после другой параллельной задачи, может привести к ложному успешному результату. В следующем сценарии показана сборка для симулятора. В реальном конвейере достаточно заменить scheme и адаптировать правило поиска имени bundle к фактическому имени модуля проекта.

set -euo pipefail

DERIVED="${RUNNER_TEMP:-/tmp}/resource-check-${BUILD_ID:-local}"
rm -rf "$DERIVED"

xcodebuild \
  -scheme ExampleApp \
  -destination 'generic/platform=iOS Simulator' \
  -derivedDataPath "$DERIVED" \
  build

APP=$(find "$DERIVED/Build/Products" -type d -name 'ExampleApp.app' -print -quit)
test -n "$APP"

COUNT=$(find "$APP" -type d -name '*FeatureKit*.bundle' | wc -l | tr -d ' ')
test "$COUNT" = "1"

BUNDLE=$(find "$APP" -type d -name '*FeatureKit*.bundle' -print -quit)

while IFS= read -r item; do
  test -z "$item" && continue
  test -e "$BUNDLE/$item" || {
    printf 'missing resource: %s\n' "$item" >&2
    exit 1
  }
done < ci/expected-resources.txt

Сценарий намеренно сначала проверяет, что найден ровно один bundle. Если сразу взять первое совпадение, проблему могут скрыть старый артефакт, тестовый bundle или модуль с таким же именем. Проверять необходимо именно bundle внутри .app, а не промежуточный каталог DerivedData: только содержимое .app отражает окончательный результат встраивания ресурсов.

Превратите частые ошибки в обязательные проверки

Для разных типов ресурсов нужны разные способы приёмки. Ограничиваться командой test -e во всех случаях нельзя.

Тип ресурса Минимальная проверка Типичный сбой
JSON Файл существует и успешно разбирается Скопирован пустой файл или файл с неверным форматом
Локализованные строки Каталог нужного языка существует, а ключ доступен для чтения Неверно назван каталог языка, используется стандартный текст
Изображения и цвета Скомпилированные ресурсы существуют и загружаются на странице дымового теста Не совпадает регистр имени или ошибочно используется основной bundle
Файлы шаблонов Проверяется хеш или ключевые поля Этап генерации перезаписал версию из репозитория

Рабочие тома macOS часто не учитывают регистр символов, поэтому ошибка в паре IconDark и icondark может остаться незаметной на машине разработчика. Не полагайтесь на случайную терпимость файловой системы. Извлеките пути из списка и посимвольно сравните их с фактическими путями с учётом регистра. JSON следует дополнительно проверить парсером, списки свойств — командой plutil -lint, а для каталогов строк нужно как минимум убедиться в наличии обязательных языков и ключевых записей.

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

Сохраняйте доказательства и обеспечьте повторяемость задач

При сбое не загружайте весь каталог DerivedData. Он занимает много места, содержит слишком много лишних данных и может включать сведения о путях, непригодные для длительного хранения. Более полезный набор диагностических материалов включает список ресурсов, фактическое дерево файлов bundle, заключительную часть журнала сборки, ошибки парсеров и идентификатор текущего коммита. Дерево файлов можно создать так:

find "$BUNDLE" -print \
  | sed "s#^$BUNDLE/##" \
  | LC_ALL=C sort \
  > resource-bundle-tree.txt

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

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

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

Почему пакет SwiftPM собирается, но приложение не находит ресурс во время выполнения?

Успешная сборка не гарантирует правильный регистр имени, путь обращения и конечное расположение файла. Необходимо проверить созданные .app и .bundle.

Нужно проверять исходные файлы или только результат сборки?

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

Облачный Mac

Размещайте проверенные рабочие процессы на выделенных физических узлах

Выберите M4 или M4 Pro, целевой узел и расчётный период, а перед заказом проверьте конфигурацию и дополнительные опции.

Выбрать конфигурацию и заказать