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

Как восстановить повреждённую базу сборки Xcode на облачном Mac

Как восстановить повреждённую базу сборки Xcode на облачном Mac

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

При устранении такой проблемы на облачном Mac от NUMACS надёжнее действовать по порядку: зафиксировать точку входа в проект, сохранить журналы, убедиться, что файлы не заняты процессами сборки, проверить целостность базы данных и только после этого пересоздать XCBuildData конкретного проекта.

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

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

set -o pipefail
mkdir -p "$HOME/build-evidence"

xcodebuild \
  -workspace App.xcworkspace \
  -scheme App \
  -configuration Debug \
  -destination 'generic/platform=iOS Simulator' \
  build 2>&1 | tee "$HOME/build-evidence/xcodebuild.log"

status=${PIPESTATUS[0]}
echo "$status" > "$HOME/build-evidence/exit-code.txt"
exit "$status"

В первую очередь ищите строки с build.db, database, malformed, disk image и XCBuildData. Если журнал явно указывает на ошибку компиляции исходного кода, сбой разрешения зависимостей или нехватку места на диске, сначала устраните соответствующую причину. Нельзя связывать с базой данных все ошибки сборки без исключения.

Перед восстановлением сохраните как минимум полный журнал, код завершения и время возникновения ошибки. После очистки исходное состояние базы данных обычно уже невозможно воспроизвести.

Найдите каталог DerivedData, который действительно использует проект

На одном облачном Mac могут оставаться несколько каталогов DerivedData для проектов с одинаковыми именами. Если ориентироваться только на время изменения каталогов, легко удалить не те данные. Вместо этого получите текущие параметры сборки через xcodebuild, а затем определите корневой каталог проекта по значению BUILD_DIR.

BUILD_DIR=$(
  xcodebuild \
    -workspace App.xcworkspace \
    -scheme App \
    -configuration Debug \
    -showBuildSettings |
  awk -F ' = ' '$1 ~ /^[[:space:]]*BUILD_DIR$/ {print $2; exit}'
)

DERIVED_ROOT=$(dirname "$(dirname "$BUILD_DIR")")
XCBD="$DERIVED_ROOT/Build/Intermediates.noindex/XCBuildData"

printf 'DerivedData: %s
XCBuildData: %s
' \
  "$DERIVED_ROOT" "$XCBD"
test -d "$XCBD"

Если в конвейере используется -derivedDataPath, следует напрямую применять этот явно заданный путь, а не сканировать пользовательский каталог. Тогда скрипт будет давать одинаковый результат и в интерактивном сеансе, и в автоматическом задании.

Зафиксируйте сведения о целевых файлах

Перед очисткой запишите размеры и время изменения базы данных и связанных с ней файлов:

find "$XCBD" -maxdepth 1 -type f \
  -exec stat -f '%Sm %z %N' -t '%Y-%m-%dT%H:%M:%S%z' {} \; \
  > "$HOME/build-evidence/xcbuilddata-files.txt"

По этому списку можно определить, была ли база данных перезаписана непосредственно перед сбоем. Он также поможет выяснить, затрагивают ли повторяющиеся сбои один и тот же путь.

Исключите занятость файла процессами и проверьте build.db только для чтения

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

pgrep -alf 'Xcode|xcodebuild|XCBBuildService' || true
lsof "$XCBD/build.db" || true

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

Убедившись, что файл больше никем не используется, проверьте базу SQLite в режиме только для чтения:

sqlite3 "file:$XCBD/build.db?mode=ro" \
  'PRAGMA quick_check;'

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

Результат проверки Следующее действие
lsof показывает активную сборку Штатно остановить задание и повторить проверку
quick_check возвращает ok Сохранить базу и искать другую причину ошибки
Файл не занят, но проверка целостности не пройдена Пересоздать целевой XCBuildData
Путь не существует Проверить workspace, scheme и параметры DerivedData

Пересоздайте XCBuildData с минимальным вмешательством

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

STAMP=$(date '+%Y%m%d-%H%M%S')
QUARANTINE="$HOME/build-evidence/XCBuildData-$STAMP"

mv "$XCBD" "$QUARANTINE"
mkdir -p "$(dirname "$XCBD")"

Затем повторите сборку с тем же набором параметров. Xcode создаст новый XCBuildData и build.db, а остальные каталоги в DerivedData сохранятся.

set -o pipefail

xcodebuild \
  -workspace App.xcworkspace \
  -scheme App \
  -configuration Debug \
  -destination 'generic/platform=iOS Simulator' \
  build 2>&1 | tee "$HOME/build-evidence/recovery-build.log"

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

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

Одной успешной сборки недостаточно, чтобы подтвердить стабильность состояния. Первая сборка создаёт новую базу данных, а вторая, инкрементальная, проверяет возможность повторного чтения и обновления базы. В обоих случаях должны использоваться одинаковые workspace, scheme, configuration, destination и путь DerivedData. Журналы и коды завершения нужно сохранять отдельно.

При приёмочной проверке убедитесь в следующем:

  1. Новая build.db создана, а выполняемый только для чтения quick_check возвращает ok.
  2. Чистая и инкрементальная сборки успешно завершились.
  3. В журналах больше нет ошибок формата базы данных или чтения XCBuildData.
  4. Артефакты сборки получены из ожидаемой конфигурации и находятся в целевом каталоге.
  5. Диагностические данные исходного сбоя не были удалены до подтверждения восстановления.

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

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

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

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

Нужно ли сразу удалять весь DerivedData при ошибке build.db?

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

Как отличить повреждение базы от занятого файла?

Проверьте build.db командой lsof. Если активного процесса нет, выполните SQLite quick_check в режиме только для чтения; ошибка проверки указывает на повреждение.

Как проверить устойчивость исправления?

Запустите чистую и затем инкрементальную сборку с одинаковыми workspace, scheme, configuration и путём DerivedData, сохранив коды завершения и результаты.

Numacs облачный Mac

Перенесите задачи сборки на выделенную физическую рабочую станцию

Две конфигурации Apple Silicon доступны на выделенных физических машинах, а не на виртуальных, с арендой на день, неделю, месяц или квартал в Сингапуре, Токио, Сеуле и Гонконге.

Выбрать устройство и оформить заказ