После удалённого открытия крупного рабочего пространства Xcode вентиляторов не видно, однако график загрузки CPU в терминале продолжает расти. Автодополнение кода остаётся в состоянии «Индексирование», а переход к определению ничего не находит. Самое опасное действие в такой ситуации — не ожидание, а немедленное удаление всего каталога разработки. Сначала нужно выяснить, связана ли нагрузка с компиляцией или SourceKit, затем сохранить диагностические данные и только после этого перестроить затронутый индекс.
Как отличить обычное перестроение индекса от настоящего зависания
SourceKit перестраивает индекс при первом открытии проекта, после смены версии Xcode, изменения файла блокировки зависимостей или переключения между большим количеством веток. Кратковременная высокая загрузка CPU сама по себе не означает сбой. Сначала остановите активную сборку, не изменяйте файлы рабочего пространства и последовательно снимите два-три снимка процессов:
date
ps -axo pid,ppid,%cpu,%mem,etime,command | grep -E 'SourceKit|sourcekitd|XCBBuildService|swift-frontend' | grep -v grep
Обратите внимание на три момента: продолжают ли XCBBuildService или swift-frontend компиляцию; увеличивается ли время работы процессов SourceKit; остаётся ли после прекращения редактирования нагрузка CPU надолго сосредоточенной на одной и той же группе процессов. Если процессы сборки всё ещё работают, сначала дождитесь их завершения, чтобы не принять компиляционную нагрузку за зависание индексации.
Нельзя диагностировать зависание по одному значению CPU. Очистка оправданна только при одновременном выполнении четырёх условий: набор файлов стабилен, задачи сборки отсутствуют, автодополнение долго не продвигается, а журналы снова и снова указывают на один и тот же модуль.
Сохранение сопоставимых диагностических данных
Не перезапускайте Xcode сразу. Сначала создайте диагностический каталог со списком процессов, журналами и образцом стека вызовов. Без этих данных впоследствии нельзя будет достоверно определить, помогло ли исправление.
STAMP="$(date +%Y%m%d-%H%M%S)"
OUT="$HOME/sourcekit-diagnostics/$STAMP"
mkdir -p "$OUT"
ps -axo pid,ppid,%cpu,%mem,etime,command > "$OUT/processes.txt"
log show --last 10m --style compact --predicate 'process CONTAINS[c] "SourceKit" OR process == "Xcode"' > "$OUT/sourcekit.log"
PID="$(pgrep -n -f 'SourceKit|sourcekitd')"
test -n "$PID" && sample "$PID" 10 -file "$OUT/sourcekit.sample.txt"
Во время сбора образца не переключайте ветки и не изменяйте файлы массово. При проверке журналов в первую очередь ищите повторяющиеся имена модулей, недоступные для чтения пути, ошибки загрузки плагинов и циклы отмены запросов. Если стек вызовов многократно возвращается к разбору одного и того же файла Swift или сгенерированного файла, сначала проверьте, не перезаписывает ли его непрерывно какой-либо скрипт.
Исключение цикла обработки сгенерированных файлов
Распространённая причина — генератор кода, который при каждом запуске перезаписывает временную метку. Даже если содержимое не изменилось, индексатор вынужден обрабатывать файл заново. Скрипт генерации должен сначала сравнивать содержимое, а затем записывать целевой файл посредством атомарной замены. Кроме того, убедитесь, что каталог сгенерированных файлов не добавлен в проект одновременно как ссылка на папку и как каталог исходного кода.
Изоляция DerivedData для каждого рабочего пространства
Совместное использование одного каталога DerivedData приводит к взаимному влиянию разных веток, версий Xcode и параллельных задач. На облачном Mac надёжнее назначить каждому рабочему пространству или заданию CI отдельный явный путь:
ROOT="$PWD"
DERIVED="$ROOT/.derived-data"
xcodebuild \
-workspace App.xcworkspace \
-scheme App \
-derivedDataPath "$DERIVED" \
-showBuildSettings > "$ROOT/build-settings.txt"
Каталог .derived-data не следует добавлять в репозиторий. Для параллельных задач также нужен идентификатор задания, например .derived-data/job-42, чтобы две сборки не записывали данные в кэш модулей одновременно.
| Сценарий | Стратегия DerivedData | Стратегия индексации |
|---|---|---|
| Удалённая интерактивная разработка | Постоянный отдельный каталог рабочего пространства | Оставить включённой |
| Однократная проверочная сборка | Отдельный каталог для каждой задачи | Включать при необходимости |
| Только сборка и тестирование в CI | Отдельный каталог для каждой задачи | Запись индекса можно отключить |
| Смена версии Xcode | Использовать новый каталог версии | Создать заново |
Для заданий CI, которым не требуется автодополнение кода, в командную строку можно добавить COMPILER_INDEX_STORE_ENABLE=NO. Не задавайте этот параметр безусловно в общей конфигурации проекта, иначе разработчики лишатся переходов, диагностики и автодополнения.
Поэтапное восстановление с учётом области проблемы
Перед очисткой закройте рабочее пространство и убедитесь, что процессы xcodebuild, swift-frontend и SourceKit больше не используют целевой каталог. На первом этапе удалите только индекс и кэш модулей:
DERIVED="$PWD/.derived-data"
rm -rf "$DERIVED/Index.noindex"
rm -rf "$DERIVED/ModuleCache.noindex"
После повторного открытия проекта не меняйте ветку и зависимости, затем дождитесь завершения первичной индексации. Если проблема возникнет снова, удалите отдельный DerivedData текущего проекта, а не весь каталог ~/Library/Developer. Если зависание по-прежнему воспроизводится, вернитесь к диагностическим данным и проверьте, возникает ли оно только в определённой ветке, изменяется ли непрерывно какой-либо сгенерированный файл и совместим ли конкретный плагин компилятора с текущей версией Xcode.
Не считайте восстановление после перезапуска компьютера окончательным выводом. Перезапуск лишь завершает процессы и не позволяет определить, была ли причина в повреждённом кэше, нестабильных входных файлах или различиях цепочки инструментов.
Воспроизводимый контрольный список проверки
После исправления повторите проверку с тем же коммитом, тем же путём к Xcode и той же стратегией DerivedData. Рекомендуется зафиксировать следующие результаты:
xcodebuild -versionиxcode-select -pуказывают на ожидаемую цепочку инструментов.- После открытия рабочего пространства индексация завершается, а автодополнение и переход к определению снова работают.
- После прекращения редактирования и сборки загрузка CPU процессами SourceKit снижается.
- В журналах больше не повторяется по кругу один и тот же файл или модуль.
- После нескольких закрытий и повторных открытий рабочего пространства проблема не возникает снова.
- CI и интерактивная разработка используют разные каталоги DerivedData.
В удалённых сеансах NowMini диагностический каталог также следует сохранять вместе с журналами сборки, но в него нельзя включать содержимое исходного кода, секреты или переменные окружения без маскирования. Конечная цель состоит не в том, чтобы немедленно снизить загрузку CPU до нуля, а в том, чтобы доказать стабильность входных данных индекса, наличие прогресса процессов и ограничение очистки текущим проектом.
Часто задаваемые вопросы
Высокая загрузка CPU процессом SourceKit всегда означает зависание?
Нет. После первого открытия проекта, смены Xcode или обновления зависимостей индекс может законно перестраиваться. Признак проблемы — длительная повторная обработка одного модуля без прогресса автодополнения.
Нужно ли удалять весь каталог DerivedData?
Обычно нет. Остановите Xcode и сборки, затем удалите Index.noindex и ModuleCache.noindex только в каталоге конкретного проекта. Полностью пересоздавайте его DerivedData лишь при повторении ошибки.
Можно ли отключить запись индекса в CI?
Да. Для неинтерактивной сборки задайте COMPILER_INDEX_STORE_ENABLE=NO. Для рабочих сеансов разработчиков индекс следует оставить включённым.
Запустите следующую задачу на выделенном физическом узле
Облачный Mac с M4, 16 ГБ ОЗУ и SSD на 256 ГБ. Выберите узел в Сингапуре, Токио, Сеуле или Гонконге под срок задачи.