Инженерные практики

Проверка воспроизводимости iOS-архива двумя сборками

Проверка воспроизводимости iOS-архива двумя сборками

Один и тот же коммит успешно архивируется на компьютере разработчика, а затем так же успешно собирается на облачном Mac или в ночном конвейере, но хеши двух артефактов не совпадают. Не спешите списывать это на кеш Xcode. В итоговый пакет могут попасть данные подписи, время сжатия, сгенерированный исходный код, порядок вывода скриптов и абсолютные пути. Более эффективный подход — создать на одном узле ArmVMS два полностью изолированных каталога сборки, дважды подряд выполнить архивирование без подписи, а затем сравнивать различия от внешних уровней к внутренним.

Сначала определите границы проверки воспроизводимости

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

  1. Уровень содержимого: один и тот же коммит, набор инструментов, файлы блокировки зависимостей и параметры сборки должны создавать одинаковые ресурсы приложения и исполняемые файлы.
  2. Уровень поставки: способ экспорта, параметры подписи, объявления разрешений и файлы внутри пакета должны соответствовать требованиям публикации, однако совпадение хеша всего IPA не обязательно.

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

Перед началом зафиксируйте хеш коммита, путь к Xcode, SDK, Scheme, Configuration, а также файлы блокировки, включая Package.resolved и Podfile.lock. Рабочая копия должна быть чистой; несохранённые в репозитории сгенерированные файлы также необходимо включить в проверку.

Выполните два архивирования в изолированных каталогах

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

#!/bin/zsh
set -euo pipefail

export LC_ALL=C
export LANG=C
export TZ=UTC

ROOT="$PWD"
SCHEME="ExampleApp"

for RUN in A B; do
  rm -rf "$ROOT/.repro/$RUN"
  mkdir -p "$ROOT/.repro/$RUN"

  xcodebuild archive \
    -workspace "$ROOT/ExampleApp.xcworkspace" \
    -scheme "$SCHEME" \
    -configuration Release \
    -destination "generic/platform=iOS" \
    -derivedDataPath "$ROOT/.repro/$RUN/DerivedData" \
    -archivePath "$ROOT/.repro/$RUN/App.xcarchive" \
    CODE_SIGNING_ALLOWED=NO \
    COMPILER_INDEX_STORE_ENABLE=NO
done

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

Сузьте область поиска с помощью списка файлов

Не начинайте с шестнадцатеричного сравнения двоичных файлов. Сначала убедитесь, что оба пакета .app содержат одинаковые пути:

APP_A=".repro/A/App.xcarchive/Products/Applications/ExampleApp.app"
APP_B=".repro/B/App.xcarchive/Products/Applications/ExampleApp.app"

find "$APP_A" -type f | sed "s#^$APP_A/##" | LC_ALL=C sort > /tmp/files-a.txt
find "$APP_B" -type f | sed "s#^$APP_B/##" | LC_ALL=C sort > /tmp/files-b.txt
diff -u /tmp/files-a.txt /tmp/files-b.txt

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

(
  cd "$APP_A"
  find . -type f ! -path "./_CodeSignature/*" -print0 |
    sort -z | xargs -0 shasum -a 256
) > /tmp/hash-a.txt

(
  cd "$APP_B"
  find . -type f ! -path "./_CodeSignature/*" -print0 |
    sort -z | xargs -0 shasum -a 256
) > /tmp/hash-b.txt

diff -u /tmp/hash-a.txt /tmp/hash-b.txt
Место первого различия Что проверить в первую очередь
Info.plist Автоматически добавленные дата, имя хоста, путь или номер конвейера
Assets.car Входные ресурсы, версию набора инструментов и порядок каталогов ресурсов
Исполняемый файл Параметры компиляции, сгенерированный исходный код, объектные файлы и порядок линковки
Framework Файлы блокировки зависимостей, версии двоичных зависимостей и скрипты встраивания
Текст или JSON Несортированные коллекции, случайные идентификаторы и региональное форматирование

Подробно проверьте конфигурацию и Mach-O

Побайтовое различие списков свойств может быть вызвано только разным порядком ключей. Скопируйте файлы, нормализуйте их с помощью plutil -convert xml1, а затем выполните текстовое сравнение. Не изменяйте исходные файлы архивов напрямую, иначе последующие доказательства будут искажены.

Если исполняемые файлы различаются, сначала проверьте UUID:

dwarfdump --uuid "$APP_A/ExampleApp"
dwarfdump --uuid "$APP_B/ExampleApp"

xcodebuild \
  -workspace ExampleApp.xcworkspace \
  -scheme ExampleApp \
  -configuration Release \
  -showBuildSettings > /tmp/build-settings.txt

Разные UUID означают лишь различие результатов линковки и сами по себе не доказывают, что бизнес-логика изменилась. Затем сопоставьте OTHER_SWIFT_FLAGS, OTHER_LDFLAGS, пути поиска, целевую версию платформы и фактически используемые пути к инструментам. Если генератор кода записывает текущую дату, временный каталог или результат обхода неупорядоченного словаря, все последующие этапы компиляции также будут нестабильными.

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

Превратите проверку в поддерживаемый контроль качества

Контроль не должен сводиться к простому требованию: «Хеши двух IPA обязаны совпадать». Надёжнее сохранять отчёт о различиях и оценивать изменения по уровням.

Изменения, которые должны блокировать сборку

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

Изменения, которые можно проверять отдельно

Каталог подписи, журналы экспорта и явно обозначенное время упаковки относятся к различиям уровня поставки и могут проходить отдельную проверку. При этом необходимо точно ограничить пути, в которых разрешены изменения; широкие правила вроде «игнорировать все plist» недопустимы.

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

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

Почему нельзя сравнить только хеши двух подписанных IPA?

Подпись, профиль и время упаковки могут различаться штатно. Сначала сравните неподписанное содержимое приложения, а экспорт проверяйте отдельно.

Разные UUID Mach-O всегда означают изменение исходного кода?

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

Нужно ли выполнять проверку для каждого коммита?

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

ArmVMS облачный Mac

Выберите выделенный физический узел Apple Silicon для следующей сборки

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

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