Инженерное руководство

Оптимизация кеша Xcode на удалённом Cloud Mac

Оптимизация кеша Xcode на удалённом Cloud Mac

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

Сначала зафиксируйте сопоставимую базовую сборку

Перед оптимизацией кеша необходимо исключить влияние посторонних переменных. Запишите вывод xcodebuild -version, версию macOS, хеш коммита, Scheme, конфигурацию сборки и целевую платформу. Во время тестов не обновляйте зависимости, не переключайте версию Xcode и не меняйте параметры компиляции, иначе результаты двух запусков нельзя будет корректно сравнить.

Выполните как минимум три теста:

  1. Удалите относящийся к проекту каталог DerivedData и выполните холодную сборку.
  2. Не меняя код, повторите сборку той же командой, чтобы проверить прогретый кеш.
  3. Измените обычный файл Swift и выполните инкрементальную сборку.

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

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

Разделите кеши по назначению

В рабочем каталоге рекомендуется создать четыре группы путей: для исходного кода, DerivedData, загруженных пакетов Swift, а также результатов и журналов. Не используйте один каталог DerivedData для нескольких проектов и не позволяйте конфигурациям Debug и Release перезаписывать данные друг друга. Если проектов много, в структуру каталогов можно дополнительно включить основную версию Xcode и идентификатор ветки.

Каталог Содержимое Рекомендуемая стратегия
Source Рабочее дерево Git Отдельно для каждого проекта
DerivedData Индексы и промежуточные артефакты Разделять по версии, Scheme и конфигурации
SourcePackages Загруженные и извлечённые пакеты Swift Переиспользовать, пока не изменился lock-файл
Results Пакеты результатов и журналы Архивировать по времени сборки

Каталог пакетов Swift можно переиспользовать только при неизменном lock-файле зависимостей. Если он изменился, позвольте системе штатно завершить разрешение зависимостей и не копируйте старый каталог извлечённых пакетов в новый проект. DerivedData сильнее зависит от конкретного набора инструментов, поэтому после переключения версии Xcode следует создать новый каталог, а не продолжать работу с перезаписанным старым.

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

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

#!/bin/bash
set -euo pipefail

ROOT="${HOME}/BuildWorkspace"
SCHEME="Application"
CONFIGURATION="Release"
DERIVED="${ROOT}/DerivedData/${SCHEME}-${CONFIGURATION}"
PACKAGES="${ROOT}/SourcePackages"
RESULT="${ROOT}/Results/${SCHEME}.xcresult"
LOG="${ROOT}/Results/${SCHEME}.log"

mkdir -p "${DERIVED}" "${PACKAGES}" "${ROOT}/Results"
rm -rf "${RESULT}"

xcodebuild \
  -workspace "${ROOT}/Source/Application.xcworkspace" \
  -scheme "${SCHEME}" \
  -configuration "${CONFIGURATION}" \
  -destination "generic/platform=iOS" \
  -derivedDataPath "${DERIVED}" \
  -clonedSourcePackagesDirPath "${PACKAGES}" \
  -resultBundlePath "${RESULT}" \
  build | tee "${LOG}"

Сначала выполните в терминале xcodebuild -list и убедитесь, что рабочее пространство действительно предоставляет нужную Scheme. Если проект использует файл проекта, а не рабочее пространство, замените -workspace на -project. Не передавайте оба параметра одновременно.

Создайте стабильный идентификатор каталога кеша

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

XCODE_KEY="$(xcodebuild -version | shasum -a 256 | cut -c1-12)"
PACKAGE_KEY="$(shasum -a 256 Package.resolved | cut -c1-12)"
printf '%s-%s-%s-%s
' "${XCODE_KEY}" "${PACKAGE_KEY}" "${SCHEME}" "${CONFIGURATION}"

Если в рабочем пространстве несколько файлов Package.resolved, явно укажите тот, который действительно участвует в сборке. Если lock-файл не найден, не подставляйте постоянный пустой ключ, иначе разные состояния зависимостей попадут в один каталог.

Найдите лишнюю перекомпиляцию по этапам

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

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

Не превращайте «очистку всех кешей» в обязательный этап. Она позволяет выяснить, связан ли сбой с повреждённым кешем, но не объясняет источник проблемы. Сначала отдельно переместите каталог DerivedData проекта. Если проблема сохранится, переходите к каталогу пакетов Swift. Такой порядок сохраняет данные для диагностики и избавляет от повторной загрузки всех зависимостей при каждой проверке.

Сохраните действительно полезные данные до окончания аренды

Перед переносом остановите выполняющиеся сборки, убедитесь, что журналы полностью записаны, а затем упакуйте lock-файлы зависимостей, необходимые загруженные пакеты Swift, пакеты результатов и проектные скрипты. Эталонной копией исходного кода должны служить зафиксированные в репозитории изменения; незакоммиченные файлы в рабочем дереве следует проверить отдельно.

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

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

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

Можно ли использовать один DerivedData с разными версиями Xcode?

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

Какие данные кеша стоит экспортировать перед окончанием аренды?

Сначала сохраните файлы фиксации зависимостей, повторно используемые загрузки пакетов Swift, пакеты результатов и диагностические журналы. Объектные файлы и индексы надёжнее создать заново.

Поможет ли полное удаление кеша при медленной сборке?

Так можно подтвердить повреждение кеша, но это не постоянное решение. Отдельно измерьте разрешение зависимостей, компиляцию, компоновку и тесты, затем очистите только связанный каталог.

ArmMacs Cloud Mac

Используйте выделенный физический узел в течение срока проекта

Выберите фиксированные чип, объём памяти, хранилище, срок аренды и доступный узел — при наличии на складе заказ перейдёт к оформлению.

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