Перейти к содержимому

Кэш модулей

Требования
  • Сгенерированный проект. Кэш модулей считывает граф проекта, который Tuist хранит за Project.swift, чтобы хешировать цели и заменять их бинарными файлами.

Tuist Module Cache предоставляет мощный способ оптимизации времени сборки за счет кэширования ваших модулей в виде бинарных файлов (.xcframework) и обмена ими между различными средами. Эта возможность позволяет использовать ранее сгенерированные бинарные файлы, сокращая необходимость повторной компиляции и ускоряя процесс разработки.

Комбинируйте с кэшем Xcode

Кэш модулей и кэш Xcode дополняют друг друга, поскольку работают на разных уровнях детализации. Кэш модулей заменяет целые модули предварительно собранными .xcframework до начала сборки, а кэш Xcode повторно использует результаты компиляции во время сборки.

Настройки кэша компиляции не входят в хеши кэша модулей, поэтому включение или отключение кэша Xcode сохраняет уже подготовленные бинарные файлы. В Xcode 27 и более поздних версиях хешируются также настройки сопоставления префиксов. См. хеши кэша модулей.

Прогрев#

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

Эта операция, известная как «прогрев», создает бинарные файлы для локального использования или для обмена с коллегами и средами CI через Tuist. Процесс прогрева кэша прост и может быть запущен одной командой:

bash
tuist cache

Команда повторно использует бинарные файлы, чтобы ускорить процесс.

Если загрузка одного бинарного файла не удается, команда все равно загружает остальные, затем завершается с ненулевым статусом и перечисляет цели, которые не были загружены. Бинарные файлы в локальном кэше машины считаются закэшированными, поэтому для их повторной загрузки с той же машины выполните tuist clean binaries перед прогревом. Передайте --no-upload, чтобы хранить бинарные файлы только в локальном кэше.

Выбор назначения#

Используйте --destination simulator или --destination device, чтобы кэшировать и генерировать для одного назначения:

bash
tuist cache --destination simulator
tuist generate --destination simulator
tuist cache --destination device
tuist generate --destination device

По умолчанию используется all, что сохраняет все назначения, поддерживаемые вашими целями, включая Mac Catalyst. Последующий вызов tuist cache повторно использует закэшированные фрагменты и собирает только недостающие SDK. Обычная генерация сохраняет исходные файлы модуля, если отсутствуют какие-либо требуемые фрагменты.

Ограниченная генерация ограничивает исходные цели выбранным назначением и сохраняет нативные цели macOS, включая макросы. Ни один из ограниченных режимов не включает Mac Catalyst. Выполните повторную генерацию с --destination all, чтобы восстановить все назначения.

Послойное кэширование поддерживает iOS и macOS со стандартными архитектурами. Другие платформы, пользовательские архитектуры и неподдерживаемые продукты используют кэширование полных артефактов. Устаревшее кэширование модулей требует --destination all.

Допуск удаленной загрузки#

Современное кэширование модулей использует REAPI. Когда перегруженный кэш временно отклоняет загрузку с ошибкой RESOURCE_EXHAUSTED, Tuist применяет стратегию отката с джиттером, соблюдает стандартные минимальные задержки google.rpc.RetryInfo и повторяет попытки в рамках ограниченного бюджета ожидания. Допуск загрузки разрешает не более шести повторных попыток на вызов. Каждая операция загрузки блоба разделяет один совокупный бюджет отката длительностью 60 секунд между проверками наличия, пакетными загрузками и потоковой записью; одновременные ожидания расходуют этот бюджет, поэтому повторные отказы не предоставляют каждому поставленному в очередь блобу еще одну минуту на повторные попытки. Успешные передачи не расходуют этот бюджет и не отменяются при его исчерпании. Публикация результатов действий имеет собственный ограниченный бюджет. Экспоненциальная задержка начинается с одной секунды и растет до 30 секунд перед применением пропорционального джиттера. Если требуемая сервером задержка превышает оставшийся бюджет, Tuist завершает работу с ошибкой, а не повторяет попытку раньше, чем предписано. Отмена прерывает откат, а чтения кэша сохраняют короткие повторные попытки, чтобы перегрузка не блокировала генерацию.

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

Выбор конфигурации#

При прогреве кэша без передачи --configuration Tuist выбирает конфигурацию сборки в следующем порядке:

  1. defaultConfiguration, установленный в Project.Options.generationOptions вашего манифеста, если он задан.
  2. В противном случае — первая конфигурация сборки варианта debug, отсортированная по имени в алфавитном порядке.

Чтобы прогреть кэш для конкретной конфигурации, передайте ее явно:

bash
tuist cache --configuration Release

Использование#

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

bash
tuist generate # Only dependencies
tuist generate Search # Dependencies + Search dependencies
tuist generate Search Settings # Dependencies, and Search and Settings dependencies
tuist generate --cache-profile none # No cache at all
bash
tuist test
Warning

Бинарное кэширование — это функция, предназначенная для рабочих процессов разработки, таких как запуск приложения на симуляторе или устройстве, а также запуск тестов. Она не предназначена для сборок релизов. При архивации приложения сгенерируйте проект с исходными файлами, используя --cache-profile none.

Профили кэша#

Tuist поддерживает профили кэша для контроля степени агрессивности замены целей закэшированными бинарными файлами при генерации проектов.

  • Встроенные:
    • only-external: заменять только внешние зависимости (системное значение по умолчанию)
    • all-possible: заменять как можно больше целей (включая внутренние цели)
    • none: никогда не заменять закэшированными бинарными файлами

Выберите профиль с помощью --cache-profile в tuist generate:

bash
# Built-in profiles
tuist generate --cache-profile all-possible
# Custom profiles (defined in Tuist Config)
tuist generate --cache-profile development
# Use config default (no flag)
tuist generate
# Focus on specific targets (implies all-possible)
tuist generate MyModule AnotherTarget
# Disable binary replacement entirely
tuist generate --cache-profile none
Устаревший флаг

Флаг --no-binary-cache устарел. Вместо него используйте --cache-profile none. Устаревший флаг все еще работает для обратной совместимости.

Приоритет при определении эффективного поведения (от высшего к низшему):

  1. --cache-profile none
  2. Фокус на цели (передача целей в generate) → профиль all-possible
  3. --cache-profile <value>
  4. Значение по умолчанию из конфигурации (если задано)
  5. Системное значение по умолчанию (only-external)

Поддерживаемые продукты#

Tuist может кэшировать только следующие типы целей:

  • Фреймворки (статические и динамические), включая фреймворки поддержки тестов, зависящие от XCTest или Swift Testing
  • Библиотеки (статические и динамические), включая библиотеки поддержки тестов, зависящие от XCTest или Swift Testing
  • Бандлы
  • Макросы Swift

Тестовые бандлы остаются исключенными из модульного кэша. Это означает, что цели unitTests и uiTests не кэшируются как бинарные файлы, но обычные цели фреймворков и библиотек, от которых зависят тесты, могут быть закэшированы, даже если они линкуются с XCTest или Swift Testing.

Кэшированные библиотеки в формате .xcframework сохраняют метаданные, необходимые сгенерированным проектам для их импорта, включая модули Swift и публичные заголовки C/Objective-C, если исходная цель объявляет публичные заголовки.

Восходящие зависимости

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

Аналитика#

Откройте раздел Module Cache → Modules в панели управления вашего проекта, чтобы найти модули с частыми промахами. Выберите среду, которую хотите улучшить, например CI, и диапазон дат. В обзоре отображаются попадания в кэш, промахи и модули с промахами; при открытии модуля показывается его история и причина, назначенная каждому наблюдению.

Выпадающий список Misses позволяет выбрать количество и объяснение для одной причины. Диаграмма показывает распределение всех четырех причин. На странице модуля используйте фильтр Reason в истории, чтобы изучить отдельные случаи. Наведите курсор на значок причины или выделите его с помощью клавиатуры, чтобы увидеть определение.

Причины промахов#

ПричинаЧто это означает
ИзмененоИзменились собственные сравниваемые входные данные модуля. К ним относятся хеши файлов, настройки сборки, разрешенная конфигурация и идентификатор компилятора. Изменение исходного кода — лишь одна из возможных причин.
ВосходящиеСобственные сравниваемые входные данные модуля остались прежними, но изменился хеш зависимости или внешнего пакета. Исходные файлы модуля могли остаться нетронутыми.
Холодный стартНет предыдущего наблюдения за модулем для сравнения, либо reported входные данные не объясняют промах, и нет достаточных доказательств предыдущей доступности в удаленном кэше. Холодный старт не доказывает, что модуль никогда не кэшировался.
ВытесненоТочный ключ кэша ранее имел попадание в удаленном кэше в том же проекте на той же записанной конечной точке кэша, но теперь происходит промах.

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

Например:

СценарийКлассификация
Модуль дает промах без предыдущего наблюдения или подходящего удаленного попаданияХолодный старт
A изменяется; B зависит от A, а C зависит от B; все три ключа меняютсяA имеет статус Changed; B и C имеют статус Upstream
Версия компилятора меняется после прогрева, источники и настройки не меняютсяЗатронутые промахи имеют статус Changed, когда оба наблюдения сообщают входные данные компилятора
Точный ключ был загружен удаленно, затем произошел промах после вытеснения его артефактаEvicted, если предыдущее попадание qualifies как доказательство
Ключ неоднократно дает промах и никогда не был успешно прогретМожет оставаться Cold

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

Повышение процента попаданий в кэш#

  1. Выберите согласованную базовую линию. Начните с CI и репрезентативного диапазона дат. Отсортируйте модули по количеству промахов, затем рассмотрите их процент попаданий, зависимые модули и стоимость сборки. Исправление часто пропускаемой дорогой зависимости может сэкономить больше времени, чем улучшение процента попаданий для крошечного модуля. Сравнивайте одну и ту же среду и схожие нагрузки после внесения изменений.
  2. Исследуйте промахи со статусом Changed. Откройте соответствующие запуски и сравните их полные ключи и reported входные данные. Согласуйте задачи прогрева и потребления на целевой версии Xcode/компилятора, конфигурации и назначениях целей. Повторно прогрейте после преднамеренных изменений. Если изменчивые генерируемые файлы или зависящие от среды настройки делают недействительными иначе стабильные модули, исследуйте эти входные данные. Сохраняйте в хеше входные данные, влияющие на бинарную совместимость.
  3. Отслеживайте промахи со статусом Upstream до измененной зависимости. Изучите ее историю, чтобы найти прямое изменение. Прогрев результирующих ключей может восстановить повторное использование. Если часто изменяемая реализация делает недействительными многие дорогие зависимые модули, рассмотрите возможность использования меньших модулей или стабильных интерфейсов, как описано в разделе Эффективность.
  4. Проверьте охват прогрева для промахов со статусом Cold. Убедитесь, что прогрев выбирает необходимые модули, использует конфигурацию и среду задачи потребления и успешно загружает артефакты. Проверьте полные ключи, используемые фактическими задачами CI. Если прогрев и потребление запрашивают разные ключи, проверьте входные данные хеширования, прежде чем предполагать, что кэш был вытеснен. Повторяющиеся промахи без предыдущей успешной загрузки возможны, даже если исходный код модуля не изменился.
  5. Используйте доказательства для промахов со статусом Evicted. Подтвердите ключ и конечную точку запуска потребления, затем проверьте предупреждения кэша этого запуска и результат загрузки задачи прогрева. При необходимости проверьте сохранение или вытеснение. Повторно прогрейте необходимые артефакты и убедитесь в последующем попадании. Если промахи продолжаются, предоставите ссылки на соответствующие запуски и логи в службу поддержки.

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

bash
tuist hash cache --configuration Debug --verbose

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

Эффективность#

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

  1. Избегайте сильно вложенных графов зависимостей. Чем мельче граф, тем лучше.
  2. Определяйте зависимости через цели протоколов/интерфейсов вместо целей реализации и внедряйте реализации через зависимости из самых верхних целей.
  3. Разделяйте часто изменяемые цели на более мелкие, вероятность изменения которых ниже.

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

Рекомендуемая настройка#

Мы рекомендуем иметь задачу CI, которая запускается при каждом коммите в основной ветке для прогрева кэша. Это гарантирует, что кэш всегда будет содержать бинарные файлы для изменений в main, поэтому локальные сборки и сборки веток в CI будут выполняться инкрементально на их основе.

Держите прогрев кэша изолированным

Запускайте tuist cache на отдельном шаге CI без последующих шагов, зависящих от сгенерированного рабочего пространства. Поскольку tuist cache изменяет рабочее пространство для целей сборки кэша, любые шаги CI, которым требуется рабочее пространство, должны сначала запустить tuist generate, чтобы получить свежее, пригодное для использования рабочее пространство.

Прогрев кэша использует бинарные файлы

Команда tuist cache также использует бинарный кэш для ускорения прогрева.

Ниже приведены примеры распространенных рабочих процессов:

Разработчик начинает работу над новой функцией#

  1. Он создает новую ветку от main.
  2. Он запускает tuist generate.
  3. Tuist загружает самые свежие бинарные файлы из main и генерирует проект с их использованием.

Разработчик отправляет изменения upstream#

  1. Конвейер CI запустит xcodebuild build или tuist test для сборки или тестирования проекта.
  2. Рабочий процесс загрузит самые свежие бинарные файлы из main и сгенерирует проект с их использованием.
  3. Затем он выполнит сборку или тестирование проекта инкрементально.

Конфигурация#

Лимит параллелизма кэша#

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

bash
TUIST_CACHE_CONCURRENCY_LIMIT=2 tuist cache

Если значение не установлено, выгрузки REAPI используют восемь задач передачи. Загрузки используют восемь, когда любой блоб требует ByteStream, и 32 для планов только с пакетной обработкой. Устаревший путь HTTP-кэша по умолчанию равен 100; этот параметр по умолчанию не применяется к REAPI. Недействительные значения, включая ранее документированное none, сохраняют значения по умолчанию, а не удаляют ограничения. Используйте unset TUIST_CACHE_CONCURRENCY_LIMIT, чтобы восстановить значения по умолчанию.

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

Временная директория прогрева кэша#

По умолчанию tuist cache создает временную директорию для промежуточных файлов сборки и собранных артефактов кэша, а затем удаляет ее после завершения прогрева кэша. Чтобы хранить эти файлы в управляемой вами директории, установите TUIST_CACHE_WARM_SCRATCH_DIRECTORY.

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

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

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

Покрытие кода#

EARLY ACCESS

Покрытие кода с кэшем модулей находится в раннем доступе. Команды читают и прогревают инструментированные бинарные файлы, описанные ниже, только если установлен TUIST_FEATURE_FLAG_COVERAGE=1.

Цель из кэша модулей — это предварительно собранный бинарный файл, и запуск тестов измеряет его покрытие только если этот бинарный файл был собран со счетчиками покрытия. Включите покрытие для вашего проекта в Tuist.swift:

swift
import ProjectDescription
let tuist = Tuist(
project: .tuist(
generationOptions: .options(
coverage: .coverage(enabled: true)
)
)
)

С enabled каждая команда и каждое окружение читает один инструментированный вариант кэша для целей вашего проекта, поэтому одного прогрева на CI достаточно для обслуживания как CI, так и локальных машин:

КомандаЦели вашего проектаВнешние пакеты
tuist cache (конфигурация Debug)Прогревает инструментированные бинарные файлыПрогревает обычные бинарные файлы
tuist cache (конфигурация Release)Прогревает обычные бинарные файлыПрогревает обычные бинарные файлы
tuist hash cacheВыводит инструментированные ключиВыводит обычные ключи
tuist generateЧитает инструментированные бинарные файлыЧитает обычные бинарные файлы
tuist testЧитает инструментированные бинарные файлыЧитает обычные бинарные файлы

Опция применяется ко всем конфигурациям, которые Xcode считает Debug. Сборки Release никогда не инструментруются. Tuist также линкует рантайм профилирования в этих конфигурациях (CLANG_COVERAGE_MAPPING_LINKER_ARGS), поэтому сборки, не собирающие покрытие, всё равно линкуют инструментированные бинарные файлы, и включает покрытие в действии тестирования схем, которые он генерирует автоматически, и схем, которые вы определяете и оставляете coverage неустановленным.

Схема, которую вы определяете, следует опции, если сама не устанавливает покрытие: .options(coverage: false) оставляет его выключенным, а .options(coverage: true) оставляет включенным независимо от значения опции. Планы тестов сами решают вопрос покрытия, а -enableCodeCoverage всё ещё переопределяет запуск, как в Xcode. Запуск, который читает инструментированные бинарные файлы без сбора покрытия, собирается и тестируется нормально. Когда запуск собирает покрытие, но цели вашего проекта пришли из кэша как обычные бинарные файлы, Tuist предупреждает, что их покрытие не измеряется.

Переменная окружения TUIST_COVERAGE_ENABLED (1 или 0) имеет приоритет над coverage: .coverage(enabled:). Установите её для всего окружения, например, для рабочего процесса CI, а не для одной команды, чтобы команды, прогревающие кэш, и команды, читающие его, согласовывались.

prefixMapping (см. кэш Xcode) имеет собственное переопределение: переменная окружения TUIST_COVERAGE_PREFIX_MAPPING (1 или 0) имеет приоритет над coverage: .coverage(prefixMapping:).

Переопределение в состояние «выключено» читает обычный вариант, который кэш, прогретый с включенным покрытием, не заполняет, поэтому такие цели собираются из исходного кода. Прогрев Release всегда прогревает обычные бинарные файлы.

KEEP THE VALUES THE SAME EVERYWHERE

enabled и prefixMapping изменяют настройки сборки и ключи кэша целей вашего проекта, а также хэши выборочного тестирования его тестовых целей. Установите их в одинаковое значение на каждой машине, которая использует общий кэш, и не выводите их из окружения (например, из Environment.isCI): различие значений между CI и локальными машинами приведет к разным ключам. То же самое относится к переменным окружения: запуск с TUIST_COVERAGE_ENABLED=0 не переиспользует бинарные файлы или результаты выборочного тестирования запусков с включенным покрытием.

Имейте в виду:

  • Инструментированные бинарные файлы больше: примерно на 20–30% для статических архивов и динамических библиотек. Отладочные сборки работают с той же скоростью.
  • Отчет о покрытии Xcode никогда не показывает код, полученный из кэша, независимо от этой опции. Загрузка покрытия Tuist измеряет его. Чтобы увидеть покрытие цели в Xcode, сфокусируйтесь на ней с помощью tuist generate <target>, чтобы она собиралась из исходного кода.

Устранение неполадок#

Не используются бинарные файлы для моих целей#

Убедитесь, что хэши детерминированы в разных окружениях и запусках. Это может произойти, если проект ссылается на окружение, например, через абсолютные пути. Вы можете использовать команду diff для сравнения проектов, сгенерированных двумя последовательными вызовами tuist generate или в разных окружениях или запусках.

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

Отсутствующие символы#

При использовании исходного кода система сборки Xcode через Derived Data может разрешать зависимости, которые не объявлены явно. Однако при использовании бинарного кэша зависимости должны быть объявлены явно; иначе вы, скорее всего, увидите ошибки компиляции, когда символы не будут найдены. Для отладки мы рекомендуем использовать команду tuist inspect dependencies --only implicit и настроить её в CI, чтобы предотвратить регрессии в неявной линковке.