Проверка вызова App Clip и связи с основным приложением на облачном Mac

Проверка вызова App Clip и связи с основным приложением на облачном Mac

Если App Clip запускается из Scheme на компьютере разработчика, это ещё не означает, что результат готов к поставке. Чаще всего проверки пропускают не ошибки компиляции, а отсутствие Clip в архиве основного приложения, несогласованные идентификаторы родительского и дочернего приложений либо URL вызова, охватывающие только идеальный сценарий. Перенос этих проверок в задачи непрерывной интеграции на облачном Mac гарантирует, что для каждого коммита используются одинаковые версии Xcode, симуляторы и сценарии приёмки.

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

Не считайте сообщение «Build Succeeded» единственным критерием успеха. Полная проверка должна создавать как минимум .xcarchive основного приложения, отдельный журнал сборки App Clip, разобранные файлы прав и записи сценариев вызова. Тестовые URL рекомендуется хранить в конфигурации репозитория, а не в персональных настройках Scheme отдельных разработчиков.

Уровень проверки Входные данные Условие успешного прохождения
Сборка App Clip Scheme Цель успешно собирается для симулятора
Встраивание Архив основного приложения Clip присутствует в AppClips
Связь Идентификаторы и права двух приложений Родительская и дочерняя части согласованы
Вызов Базовый URL, URL с параметрами и ошибочный URL Маршрутизация и резервный переход соответствуют ожиданиям

Отдельный запуск Clip проверяет точку входа в пользовательский сценарий. Архивирование основного приложения с последующей проверкой встраивания подтверждает корректность структуры поставки. Эти проверки не заменяют друг друга.

Зафиксируйте каталог сборки

Облачные задачи должны использовать изолированный каталог DerivedData, чтобы результаты предыдущего запуска не скрывали ошибки этапа копирования. Сначала соберите версию App Clip для симулятора, а затем создайте архив основного приложения. Если передавать названия Scheme через переменные окружения, один и тот же сценарий можно использовать в разных ветках.

set -euo pipefail

ROOT="$PWD"
OUT="$ROOT/.ci-artifacts"
DERIVED="$OUT/DerivedData"
ARCHIVE="$OUT/HostApp.xcarchive"

rm -rf "$OUT"
mkdir -p "$OUT"

xcodebuild \
  -workspace "$WORKSPACE" \
  -scheme "$CLIP_SCHEME" \
  -sdk iphonesimulator \
  -destination "$SIMULATOR_DESTINATION" \
  -derivedDataPath "$DERIVED" \
  clean build | tee "$OUT/app-clip-build.log"

xcodebuild \
  -workspace "$WORKSPACE" \
  -scheme "$HOST_SCHEME" \
  -destination "generic/platform=iOS" \
  -archivePath "$ARCHIVE" \
  archive | tee "$OUT/host-archive.log"

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

Проверьте связь пакетов основного приложения и Clip

Найдите встроенный результат сборки

После архивирования найдите .app основного приложения в Products/Applications, а затем убедитесь, что его каталог AppClips содержит ровно ожидаемую цель. Не полагайтесь на фиксированное имя файла приложения: выполняйте поиск по расширению и проверяйте количество результатов.

HOST_APP="$(find "$ARCHIVE/Products/Applications" -maxdepth 1 -name '*.app' -print -quit)"
CLIP_APP="$(find "$HOST_APP/AppClips" -maxdepth 1 -name '*.app' -print -quit)"

test -n "$HOST_APP"
test -n "$CLIP_APP"

HOST_ID="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "$HOST_APP/Info.plist")"
CLIP_ID="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "$CLIP_APP/Info.plist")"

printf '%s
' "$HOST_ID" > "$OUT/host-bundle-id.txt"
printf '%s
' "$CLIP_ID" > "$OUT/clip-bundle-id.txt"

Если каталога AppClips нет, сначала проверьте этап встраивания в Target основного приложения, принадлежность Clip Target текущей архивной Scheme, а также не переопределяет ли конфигурация Release настройки, которые работают в Debug.

Сравните итоговые права

Объявления в файле проекта не всегда совпадают с итоговыми значениями, подписанными в артефактах. Экспортируйте права основного приложения и Clip по отдельности с помощью codesign -d --entitlements :-, а затем используйте plutil, чтобы привести их к стабильному формату. Проверка должна подтверждать, что значение связи с родительским приложением у Clip указывает на текущее основное приложение, а также ограничивать возможности, которые не должны наследоваться.

codesign -d --entitlements :- "$HOST_APP" > "$OUT/host-entitlements.plist"
codesign -d --entitlements :- "$CLIP_APP" > "$OUT/clip-entitlements.plist"

plutil -lint "$OUT/host-entitlements.plist"
plutil -lint "$OUT/clip-entitlements.plist"

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

Проверьте маршрутизацию с тремя типами URL

Тесты вызова должны охватывать как минимум базовую точку входа, корректные параметры и ошибочные входные данные. Например, базовая точка входа должна открывать стандартный облегчённый сценарий; URL с идентификатором ресурса — переходить к указанному содержимому; а при отсутствующем или недопустимом параметре приложение должно возвращаться на безопасную страницу, а не оставаться с пустым экраном.

Используйте URL как входные данные тестов

Применение _XCAppClipURL в App Clip Scheme подходит для ручной отладки, однако непрерывная интеграция не должна зависеть от локального состояния Scheme разработчика. Надёжнее передавать URL непосредственно анализатору маршрутов и проверять входные данные модульными тестами, сохранив при этом одну дымовую задачу на симуляторе для проверки жизненного цикла приложения.

func testInvocationRoutes() throws {
    let base = try XCTUnwrap(URL(string: invocationBaseURL))
    XCTAssertEqual(router.route(for: base), .home)

    let item = try XCTUnwrap(URL(string: invocationItemURL))
    XCTAssertEqual(router.route(for: item), .item(id: "42"))

    let malformed = try XCTUnwrap(URL(string: invocationMalformedURL))
    XCTAssertEqual(router.route(for: malformed), .fallback)
}

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

Ужесточите контроль и сохраняйте данные о сбоях

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

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

Задачи на GPUMini также должны явно записывать xcodebuild -version, выбранный SDK и среду выполнения симулятора. В долгосрочной перспективе поддерживать нужно не снимок экрана с единичным успешным запуском, а набор проверок с явно заданными входными данными, отслеживаемыми артефактами и возможностью быстро воспроизвести сбой.

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

Достаточно ли собрать только цель App Clip?

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

Нужно ли проверять несколько URL вызова?

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

Зачем анализировать архив, если локальный запуск успешен?

Схема Xcode может запускать клип напрямую. Анализ архива обнаруживает ошибки фазы копирования и расхождение настроек основного приложения.

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

Как выбрать облачный Mac для разработки и сборки

Сравните две конфигурации M4, четыре срока аренды и четыре доступных узла, а затем выполните развертывание под задачи проекта.

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