Xcode 26 build database locked: как исправить удалённую сборку в 2026?

Симптом: xcodebuild сообщает build database locked, пока Build, Test или Archive запущены на одном удалённом Mac.

Быстрое решение: не удаляйте весь кэш; сначала остановите конкурирующие задания, разделите DerivedData для каждого Job, дождитесь завершения остаточных процессов и только затем очистите каталог затронутой сборки и повторите одиночный Archive.

Эта инструкция предназначена для вас, если вы запускаете xcodebuild через SSH, сталкиваетесь с оставшимся процессом после разрыва соединения или разрешаете нескольким CI-задачам собирать один проект. Она также пригодится тому, кто обслуживает общий Mac для Build, Test и Archive и должен сохранить xcarchive, dSYM, логи и результаты тестов.

Что именно нужно зафиксировать до остановки процессов

Сообщение Xcode 26 build database locked само по себе не доказывает, что база повреждена. Оно может означать, что два процесса одновременно открыли одну область промежуточных данных, что старый xcodebuild продолжает работать после обрыва SSH или что скрипт внутри основной сборки запустил ещё одну сборку.

Начните не с очистки, а с доказательной записи. Сохраните полный вызов команды, а не только последнюю строку ошибки:

xcodebuild \
  -workspace "/work/redacted/App.xcworkspace" \
  -scheme "RedactedScheme" \
  -configuration Release \
  -derivedDataPath "/builds/job-redacted/DerivedData" \
  -resultBundlePath "/artifacts/job-redacted/Test.xcresult" \
  build

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

Зафиксируйте следующие сведения:

  • точное время запуска Build, Test или Archive;
  • текущий рабочий каталог и путь, который реально получил xcodebuild;
  • путь DerivedData, OBJROOT, SYMROOT и каталог Archive;
  • первую строку build database locked, а также несколько строк до неё;
  • идентификатор процесса и родительский процесс;
  • кто запустил задачу: терминал, CI Runner, shell-скрипт или другой xcodebuild;
  • были ли уже созданы xcresult, xcarchive, dSYM или файл загрузки.

Apple описывает Build System как часть управления зависимостями, целями и действиями сборки, поэтому источник конфликта нужно искать в границах задания, а не только в пользовательском кэше. Сверьте поведение с официальным описанием Build System в Xcode и с заметками к выпуску Xcode 26. Форумные сообщения разработчиков могут подсказать текст ошибки, но не заменяют проверку вашего журнала.

Не конкурируют ли процессы за одну и ту же сборку?

Первый слой диагностики — реальная конкуренция процессов. Проверьте процессы от имени нужного пользователя:

pgrep -af 'xcodebuild|XCBBuildService|xctest|simctl'
ps -axo pid,ppid,lstart,user,command | grep -E 'xcodebuild|XCBBuildService|xctest'

Сопоставьте PID, PPID и время запуска с вашей временной шкалой. Особенно часто проблема возникает в четырёх случаях:

  • вы дважды отправили Build из терминала или панели CI;
  • повторная попытка началась до отмены предыдущей;
  • SSH-соединение оборвалось, но удалённый процесс не завершился;
  • два пользователя используют один Mac и одинаковый путь сборки.

Не завершайте все процессы, в имени которых встречается Xcode. Сначала выберите задачу, которую нужно сохранить: обычно это полный Archive с исходным логом и артефактами. Если одновременно идёт Test, его остановка означает потерю текущего xcresult; остановка Archive может оставить неполный xcarchive; прекращение задачи загрузки может дать неопределённый статус передачи. Поэтому сначала попросите корректно завершиться второстепенный Job, затем отменяйте его принудительно только при отсутствии прогресса и понятной связи с блокировкой.

Проверяйте завершение по цепочке:

kill -TERM <pid>
sleep 5
ps -p <pid> -o pid,ppid,state,command

Если процесс завершился, сохраните его лог и статус. Если он остался, выясните, не удерживает ли дочерний xcodebuild, xctest или XCBBuildService тот же каталог. kill -KILL — крайняя мера: применяйте её к конкретному PID после фиксации команды и последствий, а не ко всей пользовательской сессии.

Почему SSH-разрыв не отменяет удалённую сборку

SSH — это канал управления, а не гарантия остановки уже запущенной команды. Если Job был передан оболочке, CI Runner или службе, закрытие терминала может оставить дочерний процесс работать. Поэтому после повторного подключения сначала проверяйте временную шкалу и PID, а не запускайте тот же xcodebuild поверх него.

Для последующих запусков используйте управляемый процесс выполнения: CI должен знать PID или идентификатор задания, а оболочка должна сохранять stdout и stderr в отдельный файл. Перед повтором задачу нужно отменить средствами CI, подтвердить отсутствие процессов и только затем запустить новый Job. Если вы не можете отличить старый процесс от нового, безопаснее сохранить логи, завершить конкретное дерево процессов и создать новый каталог задания.

Почему общая DerivedData ломает параллельные Job

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

Проверьте не только YAML или shell-файл CI, но и итоговые параметры:

xcodebuild -showBuildSettings \
  -workspace "/work/redacted/App.xcworkspace" \
  -scheme "RedactedScheme" \
  | grep -E 'DERIVED_DATA|OBJROOT|SYMROOT|TARGET_BUILD_DIR'

Также найдите переопределения в скриптах:

grep -R --line-number -E 'derivedDataPath|OBJROOT|SYMROOT|archivePath|resultBundlePath' .

Значения в графическом интерфейсе, переменные CI и аргументы команды могут отличаться. Например, Job A может явно получить /builds/a/DerivedData, а Job B — использовать путь по умолчанию, который фактически совпадает с каталогом другого запуска. Отдельно проверьте символические ссылки и переменные, разворачиваемые Runner.

Для каждого Job задайте уникальный и отслеживаемый корень:

JOB_ROOT="/builds/${CI_JOB_ID}"
DD_PATH="${JOB_ROOT}/DerivedData"
RESULT_PATH="${JOB_ROOT}/results.xcresult"
ARCHIVE_PATH="${JOB_ROOT}/archive.xcarchive"

mkdir -p "$JOB_ROOT"

xcodebuild \
  -workspace "/work/redacted/App.xcworkspace" \
  -scheme "RedactedScheme" \
  -configuration Release \
  -derivedDataPath "$DD_PATH" \
  -resultBundlePath "$RESULT_PATH" \
  archive \
  -archivePath "$ARCHIVE_PATH"

CI_JOB_ID здесь — пример переменной вашего Runner, а не универсальное имя. Подставьте реальный идентификатор, который гарантированно различается между параллельными заданиями. Apple документирует параметры сборки и их переопределение в справочнике Build Settings и в материале о настройке Build Settings цели.

Разделяйте промежуточные данные и конечные артефакты:

  • DerivedData, OBJROOT и SYMROOT принадлежат конкретному Job и могут быть удалены после сохранения результатов;
  • xcresult должен иметь уникальное имя и оставаться доступным для диагностики тестов;
  • xcarchive нужно хранить отдельно, если он используется для подписи, символикации или загрузки;
  • dSYM и логи нельзя удалять вместе с временным каталогом до проверки передачи в хранилище артефактов.

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

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

Как обнаружить вложенную сборку, которой нет в CI-конфигурации

Иногда CI запускает только одну команду, но Run Script, пакетный инструмент или зависимый проект вызывает второй xcodebuild. Внешне это выглядит как случайная блокировка, хотя конфликт создаётся внутри графа целей.

Ищите вызовы:

grep -R --line-number -E '(^|[[:space:]])xcodebuild([[:space:]]|$)|xcodebuild[[:space:]]' \
  .ci scripts Packages .

Затем просмотрите полный лог с PID и временем. Если во время основного Build появляется новая команда xcodebuild, установите её границы:

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

Apple рекомендует описывать входы и выходы пользовательских скриптов, чтобы Build System мог правильно учитывать зависимости. Сверьте сценарий с документацией о Run Script во время сборки. Проверьте также настройки схем и зависимостей: отключать параллельную сборку вслепую не следует, пока не установлено, что именно вызывает повторную работу.

Перед изменением скрипта опишите откат: сохраните прежний файл, зафиксируйте ожидаемый артефакт и добавьте диагностическую строку с PID, рабочим каталогом и путём вывода. После изменения повторите Build и убедитесь по журналу, что дочерний xcodebuild больше не запускается либо получает полностью отдельный Job-каталог.

Что очищать, если активных процессов уже нет

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

Порядок действий:

  1. Скопируйте журнал последней неудачной команды и список файлов в каталоге Job.
  2. Проверьте, что xcodebuild, тесты и связанные службы больше не используют этот путь.
  3. Переименуйте каталог DerivedData конкретного Job, вместо немедленного удаления.
  4. Создайте новый пустой путь и повторите ту же команду с теми же Scheme и параметрами.
  5. Если ошибка исчезла, сравните новый xcresult, Build Products и лог с предыдущим запуском.
  6. Если проблема осталась, временно создайте новый рабочий каталог для проекта, сохранив исходники и настройки.
  7. Только после повторной проверки рассматривайте более широкую очистку, документируя каждый удалённый объект.

Например:

OLD="/builds/job-redacted/DerivedData"
if [ -d "$OLD" ]; then
  mv "$OLD" "${OLD}.blocked-$(date +%Y%m%d-%H%M%S)"
fi

mkdir -p "$OLD"

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

Удаление DerivedData не удаляет исходный код, сертификаты или уже сохранённый xcarchive, если они находятся в других каталогах. Однако новый запуск может заново создать продукты, модули и индексы, поэтому не удаляйте Archive, dSYM и xcresult вместе с временными данными. Для проверки подписанного результата сверяйтесь с документацией Apple о создании distribution-signed code для Mac. Для тестового результата используйте рекомендации по запуску тестов и интерпретации результатов.

Как выбрать исправление по фактической причине

Наблюдение Наиболее вероятный слой Действие Когда остановиться
Два активных xcodebuild пишут в один путь Конкурирующие процессы Сохранить нужный Job, корректно отменить второй, повторить с контролем PID Когда остался один владелец каталога
Job получают одинаковый DerivedData или OBJROOT Конфигурация путей Ввести уникальный корень на каждый Job и сохранить отдельные результаты Когда два параллельных запуска проходят независимо
В логе появляется дочерний xcodebuild Run Script или зависимость Уточнить входы, выходы и границы вызова, затем убрать ненужную вложенную сборку Когда повторный запуск не создаёт скрытый процесс
Процессов нет, ошибка остаётся после сбоя Остаточное или повреждённое состояние Переименовать каталог конкретного Job и повторить ту же команду Когда одиночный Build проходит
Одиночный Build проходит, но Archive ломается Путь Archive, подпись или этап публикации Проверить archivePath, артефакты и подпись отдельно Когда создан полный проверяемый xcarchive

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

Пошаговая повторная проверка удалённого Mac

После исправления не возвращайте сразу весь параллелизм. Пройдите уровни в таком порядке:

  1. Запустите одиночный Build с уникальным -derivedDataPath и отдельным логом.
  2. Проверьте наличие ожидаемых Build Products и отсутствие второго xcodebuild в журнале.
  3. Запустите Test с отдельным -resultBundlePath, чтобы результат не был перезаписан.
  4. Запустите два тестовых Job одновременно, каждому назначив собственный корень.
  5. Выполните настоящий Archive с отдельным -archivePath; не подменяйте его обычным Build.
  6. Сохраните xcarchive, dSYM, xcresult и логи как независимые артефакты.
  7. Повторите проверку после отмены Job, повторного подключения по SSH и перезапуска хоста.

Для CI-проекта полезно отдельно проверить сценарий, в котором задача отменяется во время Build. После этого новый Job не должен наследовать занятый путь и не должен считать старый Archive успешно созданным. В непрерывной интеграции Apple также рекомендует явно организовывать параметры сборки и артефакты; сопоставьте свою схему с руководством по сборке Swift-пакетов и приложений в CI.

Если ошибка повторяется только после SSH-разрыва, улучшайте управление жизненным циклом Job. Если она появляется только при параллельном запуске, ограничьте конкуренцию или разделите Runner. Если после перезагрузки хоста старые процессы не контролируются, постоянная сборочная среда требует более строгого процесса запуска и очистки.

Когда менять схему, а не удалять кэш

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

Для небольшого проекта разумно начать с одного постоянного Runner и строгой очереди, если параллельность не нужна. Если Build и Test должны идти одновременно, разделите рабочие каталоги и артефакты. Если разные команды используют один Mac, добавьте явный владелец Job и запрет записи в общий промежуточный путь.

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

  • параллельный запуск создаёт независимые результаты;
  • разрыв SSH не оставляет бесконтрольную задачу, которая блокирует следующий Job;
  • перезапуск хоста не уничтожает единственную копию Archive, dSYM и диагностических логов.

Если нынешний Mac не позволяет управлять процессами, создавать независимый рабочий каталог для каждого задания и постоянно оставаться доступным, проблема уже не сводится к build database locked. Локальный Mac удобнее, когда вам нужны физические устройства, USB-доступ или постоянная тяжёлая нагрузка, но отдельная покупка ради временного CI-Runner означает расходы на оборудование, обслуживание, дисковое пространство и резервное восстановление. Общий облачный или виртуализированный вариант может ограничивать права, нестабильно переживать SSH-сеансы и не давать полного контроля над macOS-инструментами.

В такой ситуации аренда Mac у KVMFLUX может оказаться более предсказуемой для временного удалённого Build, тестового Runner или миграции iOS-проекта: вы сначала проверяете изоляцию каталогов, управление процессами и восстановление после обрыва, а затем выбираете период использования без немедленной покупки отдельного компьютера. Условия и доступные варианты можно сверить на странице аренды Mac для удалённой разработки и в разделе тарифов KVMFLUX.

Начните с контрольного прогона «параллельные Job — разрыв SSH — перезапуск хоста». Если существующая среда проходит все три проверки и сохраняет независимые артефакты, её можно оставлять. Если нет — сначала исправьте изоляцию и управление процессами, а затем уже решайте, нужен ли вам отдельный постоянно доступный удалённый Mac.

Стабильная удалённая сборка с KVMFLUX

Арендуйте удалённый Mac в KVMFLUX для сборки и тестирования проектов в изолированной среде. Подключайтесь по SSH к выделенному Mac и запускайте Archive, CI-задачи и повседневную разработку без зависимости от локального оборудования. Выберите подходящую конфигурацию и срок аренды для предсказуемой работы Xcode в удалённой инфраструктуре. Начните работу с удалённым Mac KVMFLUX и сосредоточьтесь на исправлении сборки, а не на проблемах окружения.

Mac Mini M4 · 16GB / 256GB
Сутки$19.3 /сутки
Неделя$52.2 /нед.
Месяц$96.7 /мес.
Квартал$263 /квартал