Сборка на облачном Mac завершается успешно, но после загрузки артефактов на машину доставки скрипты перестают запускаться, символические ссылки превращаются в обычные файлы или не проходит проверка пакета приложения. Причина таких проблем часто кроется не в сборке, а в последней операции упаковки и распаковки. Помимо содержимого и путей, файлы macOS могут хранить расширенные атрибуты, списки контроля доступа, ветки ресурсов и связи символических ссылок. Если в процессе доставки проверяется только то, что архив открывается, эти изменения обнаружатся уже на этапе публикации.
Сначала определите, что именно нужно сохранить
Не архивируйте всё рабочее пространство целиком. Сначала создайте промежуточный каталог, содержащий только материалы для доставки, и явно определите, какие метаданные входят в контракт артефакта.
Обычно в него включают пакеты приложений, фреймворки, динамические библиотеки, утилиты командной строки, шаблоны конфигурации и файлы символов. Каталоги кеша, временные тестовые файлы, каталоги системы контроля версий и локальные журналы включать не следует. Промежуточный каталог также предотвращает изменение исходных файлов заданиями сборки во время упаковки.
set -euo pipefail
ROOT="$PWD"
SOURCE="$ROOT/build/Release"
STAGE="$ROOT/artifacts/stage"
ARCHIVE="$ROOT/artifacts/release.zip"
rm -rf "$STAGE"
mkdir -p "$STAGE"
ditto "$SOURCE/MyProduct.app" "$STAGE/MyProduct.app"
ditto "$SOURCE/MyFramework.framework" "$STAGE/MyFramework.framework"
При первой проверке следует зафиксировать количество обычных файлов, каталогов и символических ссылок. Символические ссылки особенно часто разворачиваются при передаче между разными инструментами, из-за чего меняется размер архива или перестают работать относительные пути.
find "$STAGE" -type f | wc -l
find "$STAGE" -type d | wc -l
find "$STAGE" -type l -print
Проведите инвентаризацию метаданных macOS перед упаковкой
Используйте ls -le@ для просмотра разрешений, ACL и расширенных атрибутов, а xattr — для вывода атрибутов важных объектов. Не считайте все незнакомые атрибуты ненужными.
ls -le@ "$STAGE"
xattr -lr "$STAGE" > "$ROOT/artifacts/xattr-before.txt"
find "$STAGE" -type l -exec sh -c '
for item do
printf "%s -> %s\n" "$item" "$(readlink "$item")"
done
' sh {} + > "$ROOT/artifacts/symlinks-before.txt"
Не используйте
xattr -crкак универсальное средство исправления. Рекурсивная очистка может временно устранить некоторые внешние признаки ошибки, но вместе с ними удалит и данные, необходимые для расследования. Сначала проведите инвентаризацию и определите происхождение атрибутов, а затем точечно удаляйте только те отдельные атрибуты, которые точно не нужны.
Также необходимо проверить биты разрешений исполняемых файлов. Если скрипт был исполняемым в репозитории, это ещё не означает, что он останется таким после копирования в промежуточный каталог. Базовое состояние можно сохранить с помощью find "$STAGE" -type f -perm -111. Если определённый скрипт должен быть исполняемым, добавьте это требование в сценарий приёмки, а не полагайтесь на человеческую память.
Создайте архив для доставки с помощью ditto
ditto входит в состав macOS и хорошо подходит для работы с пакетами приложений и каталогами фреймворков. При создании ZIP включите сохранение ресурсов и расширенных атрибутов, а также оставьте уровень промежуточного каталога, чтобы получатель всегда получал стабильную структуру верхнего уровня.
rm -f "$ARCHIVE"
ditto -c -k \
--sequesterRsrc \
--keepParent \
"$STAGE" \
"$ARCHIVE"
shasum -a 256 "$ARCHIVE" > "$ARCHIVE.sha256"
du -h "$ARCHIVE"
Имя архива и имя внутреннего каталога верхнего уровня должны быть постоянными. Не включайте в имена каталогов временные метки, имена машин или абсолютные пути временных рабочих пространств, иначе последующим скриптам придётся угадывать точку входа. Если конвейеру нужен уникальный идентификатор, укажите хеш коммита во внешней записи об артефакте, не изменяя внутреннюю структуру архива.
Для файлов из неконтролируемых источников перед копированием в промежуточный каталог также следует проверять наличие переводов строк, невидимых символов и необычных комбинаций Unicode в именах. Обычные пробелы можно безопасно обрабатывать, но переменные в скриптах необходимо всегда заключать в двойные кавычки, чтобы Shell не разбивал пути на части.
Распакуйте архив в отдельный каталог и сравните результаты
При приёмке нельзя распаковывать архив поверх исходного каталога: старые файлы могут скрыть отсутствие новых. Каждый раз используйте совершенно новый каталог и распаковывайте архив с помощью ditto.
VERIFY="$ROOT/artifacts/verify"
rm -rf "$VERIFY"
mkdir -p "$VERIFY"
ditto -x -k "$ARCHIVE" "$VERIFY"
test -d "$VERIFY/stage/MyProduct.app"
test -d "$VERIFY/stage/MyFramework.framework"
find "$VERIFY/stage" -type l -exec sh -c '
for item do
printf "%s -> %s\n" "$item" "$(readlink "$item")"
done
' sh {} + > "$ROOT/artifacts/symlinks-after.txt"
diff -u \
"$ROOT/artifacts/symlinks-before.txt" \
"$ROOT/artifacts/symlinks-after.txt"
Для проверки содержимого файлов сформируйте отдельные списки для каталогов до упаковки и после распаковки. Пути могут содержать пробелы, поэтому не используйте циклы с переменными без кавычек. Следующий фрагмент Python сортирует объекты по относительному пути и вычисляет SHA-256 для обычных файлов.
from hashlib import sha256
from pathlib import Path
import sys
root = Path(sys.argv[1])
for path in sorted(root.rglob("*"), key=lambda p: str(p.relative_to(root))):
if path.is_file() and not path.is_symlink():
digest = sha256(path.read_bytes()).hexdigest()
print(digest, path.relative_to(root))
Запустите его отдельно для stage и verify/stage, а затем сравните вывод с помощью diff. Это позволит одновременно обнаружить изменение содержимого, отсутствие файлов и появление неожиданных объектов.
Превратите проверку структуры и подписи в обязательный шлюз
Если артефакт содержит подписанный пакет приложения или фреймворк, повторно проверьте подпись после распаковки. Одной проверки перед упаковкой недостаточно, поскольку проблема может быть внесена инструментом архивирования.
codesign --verify --deep --strict \
"$VERIFY/stage/MyProduct.app"
codesign -d --verbose=4 \
"$VERIFY/stage/MyProduct.app" 2>&1 |
sed -n '1,20p'
Параметр --deep подходит для окончательной проверки целостности, но не заменяет поуровневую диагностику. При ошибке сначала определите, где именно она возникла: в основном приложении, встроенном фреймворке или вспомогательном исполняемом файле. Затем выполните codesign --verify --strict --verbose=4 для конкретного пути. Одновременно сравните вывод xattr до и после упаковки, чтобы установить, связаны ли изменения с этапом архивирования.
Рекомендуется заранее определить следующие условия отказа при приёмке: несовпадение каталога верхнего уровня, различия в списке файлов, изменение целей символических ссылок, потеря обязательных битов исполнения, аномалии ключевых расширенных атрибутов или ошибка проверки подписи. Любое из этих событий должно останавливать публикацию — не следует просто повторно создавать архив в надежде на удачный результат.
Типичные ошибки и контрольный список доставки
Самая распространённая ошибка — использование нескольких инструментов в одной цепочке: сначала скрипт создаёт обычный ZIP, а затем программа загрузки автоматически распаковывает и повторно упаковывает его. Чем длиннее цепочка, тем сложнее отслеживать семантику метаданных. Следует выбрать единственный инструмент упаковки и передавать исходный архив непосредственно в хранилище артефактов.
Вторая распространённая ошибка — упаковка одновременно со сборкой. Пока компилятор продолжает записывать файлы, список и архив могут отражать разные моменты времени. Правильный порядок действий: дождаться успешного завершения процесса сборки, скопировать результат в доступный только для чтения промежуточный каталог и лишь затем начать архивирование.
Третья ошибка — тестирование только на той же машине. Как минимум удалите старый каталог приёмки, заново распакуйте архив и запустите проверки по путям внутри него. Если условия позволяют, поручите отдельному заданию выполнить тот же набор скриптов. В долгосрочных конвейерах на BerryVM файлы инвентаризации, хеш архива и журналы приёмки можно хранить как записи одной поставки, но журналы не должны содержать учётные данные или переменные окружения без маскирования.
Итоговый список должен оставаться кратким: промежуточный каталог заморожен, символические ссылки зафиксированы, расширенные атрибуты инвентаризированы, хеш архива создан, независимая распаковка завершена, списки файлов совпадают, биты исполнения соответствуют ожиданиям, а применимые проверки подписи успешно пройдены. При таком подходе упаковка перестаёт быть необязательным действием после сборки и становится воспроизводимым инженерным шлюзом, который точно фиксирует сбои.
Часто задаваемые вопросы
Почему для артефактов macOS недостаточно команды zip -r?
Обычная упаковка ZIP может не сохранить ресурсные ветви, расширенные атрибуты, ACL или семантику символических ссылок. Для приложений и фреймворков надежнее использовать ditto.
Стоит ли перед упаковкой запускать xattr -cr для всего каталога?
Нет. Сначала следует вывести список атрибутов и определить их назначение. Рекурсивное удаление без проверки может уничтожить данные, нужные для доставки или диагностики.
Достаточно ли проверить только SHA-256 архива?
Нет. Хеш подтверждает неизменность архива, но не корректность содержимого. Нужны отдельная распаковка, сверка структуры и ссылок, а для подписанных пакетов также codesign.
Перенесите следующую задачу разработки в облачный Mac
Выберите Berry M4 или Berry M4 Pro, проверьте пять узлов, срок аренды и дополнительные опции, затем оформите заказ.