Один пакет для всех тестов: радикальный подход к ускорению CI
Представьте CI-пайплайн, где каждый пул-реквест собирается 30 минут. Разработчики теряют фокус, контекст переключается, а поток работы прерывается. Теперь представьте тот же пайплайн, но работающий за 2.5 минуты. Это не фантастика, а , которая началась с трех Mac mini и закончилась архитектурным прорывом в организации сборок Xcode.
Когда рост становится проблемой:
Все начиналось невинно: добавили тесты для первого пакета, создали конфигурацию. Потом подключили второй пакет - добавили еще одну конфигурацию. К третьему пакету шаблон был очевиден. Через несколько месяцев в проекте оказалось 91 отдельная конфигурация для сборки и тестирования.
Казалось логичным: изменили код в пакете А - запустились только его тесты. Но реальность оказалась сложнее. Основное приложение собиралось 12 минут - это был физический предел скорости. Остальное время уходило на дублирование работы: двадцать разных процессов могли одновременно пересобирать одни и те же зависимости, потому что каждый начинал с нуля.
Миф о портативном кэше:
Первая мысль любого инженера при оптимизации сборок - кэширование. С Xcode эта идея разбивается о суровую реальность.
Оказывается, DerivedData - директория, где Xcode хранит промежуточные артефакты сборки - содержит абсолютные пути, вшитые прямо в файлы. Попробуйте скопировать эту папку в другое место и Xcode просто проигнорирует ее, как будто кэша не существует.
Попытка использовать стандартный механизм кэширования GitHub тоже провалилась: 20 гигабайт данных нужно было скачивать перед каждой сборкой. На обычном интернет-соединении это занимало больше времени, чем полная пересборка проекта.
Но самая глубокая проблема обнаружилась дальше: Xcode принципиально не делится артефактами между разными типами целей. Приложение и Swift Package, даже если они используют один и тот же код, компилируются независимо. Два разных пакета в одном рабочем процессе тоже не могут использовать общие промежуточные файлы. Получался абсурд: изменение в одной библиотеке запускало двадцать параллельных процессов, каждый из которых компилировал одни и те же исходники с нуля.
Архитектурное решение - два мира, два кэша:
Прорыв пришел с парадоксальной идеей: а что если на время CI временно объединить все 89 Swift-пакетов в один гигантский пакет?
Такой подход дает лучшее из двух миров: максимальное повторное использование скомпилированного кода для тестирования и строгий контроль зависимостей для продакшен-сборки.
Система слотов - стабильность через постоянство:
Ключевое открытие оказалось удивительно простым: единственный способ заставить Xcode надежно использовать кэш - никогда не перемещать его. Директория DerivedData должна оставаться на одном и том же месте всегда.
Так родилась концепция «слотов» - фиксированных рабочих каталогов, которые процессы CI не копируют, а используют напрямую.
Интеллектуальный отбор тестов:
Объединение 91 конфигурации в единую систему потребовало умного алгоритма определения того, что действительно нужно проверить. На практике это выглядит так: изменили одну строку в библиотеке - автоматически запустились 12 связанных тестовых наборов и 3 сборки приложений. 61 несвязанный тест был пропущен. В сочетании с предварительно подготовленными кэшами большинство проверок затрагивают менее 20% кодовой базы.
Вывод:
Настоящая ценность этой оптимизации измеряется не в минутах, а в восстановленном потоке работы. Когда обратная связь от системы непрерывной интеграции приходит за 2,5 минуты вместо 30, исчезает внутреннее сопротивление перед созданием пул-реквестов. Разработчики остаются погруженными в задачу, ошибки обнаруживаются и исправляются сразу, циклы итерации становятся короче и эффективнее.
Эта история - прекрасная иллюстрация важного принципа: иногда самые значительные улучшения достигаются не заменой инструментов, а глубоким пониманием их внутренней работы и творческой адаптацией под конкретные нужды.