Rush.js — как можно значительно ускорить сборку ваших проектов, используя кеширование

от автора

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

В Rush по умолчанию реализован механизм инкрементной (ускоренной) сборки. При повторном вызове команды rush build пропускаются уже обновлённые проекты. Инкрементный анализ Rush опирается на хеши зависимостей. Их можно найти в файле project>/.rush/temp/shrinkwrap-deps.json внутри каждого проекта. Но результаты сборок никуда не сохраняются и поэтому, как правило, при переключении на другую ветку потребуется сделать полный rebuild.

Проект считается обновлённым в следующих случаях:

  • проект создан локально;

  • с момента последней сборки не изменились исходные файлы и npm-зависимости;

  • проект зависит от других локальных проектов из монорепозитория и эти проекты обновлены;

  • параметры командной строки не изменялись. Например, если сначала была выполнена команда rush build, а затем rush build --production, то в таком случае проекты требуют пересборки.

Механизм выбора проектов для rebuild

Предположим, что у нас есть монорепозиторий, состоящий из семи проектов: A, B, C, D, E, F, G.

Эти кружки представляют локальные проекты монорепозитория, а не внешние npm-пакеты. Проект D зависит от C и G, и это означает, что С и G нужно собрать до сборки D.

Если мы внесём изменения в проект B, то на момент вызова последующего build произойдёт rebuild сначала проекта B, потом C, и в последнюю очередь D, так как они зависят друг от друга. Все остальные проекты останутся без изменений.

В основе кеширования сборок лежит похожий механизм определения проектов, которые необходимо пересобрать. Разница в том, что результат успешной сборки помещается в tar-архив. Имя файла с архивом содержит специальный хеш. Созданный архив кешируется и помещается в хранилище.

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

Есть два варианта хранения закешированных архивов:

  • В папке кеша на локальном диске. Расположение по умолчанию — common/temp/build-cache.

  • В контейнере облачного хранилища. По умолчанию система CI будет настроена на запись в облачное хранилище, а отдельным пользователям будет предоставлен доступ только для чтения. Например, каждый раз, когда PR объединяется с основной ветвью, система CI получает архивы сборок и загружает их в облачное хранилище. И поэтому даже самая первая сборка после команды git clone будет очень быстрой.

Хеш формируется следующим образом. Создаётся хэш SHA1, и в него в определённом порядке добавляются данные:

  • список названий output-папок в JSON-формате, которые должны быть закешированы;

  • название последней введённой команды;

  • хеш каждой зависимости проекта.

Сформированный хеш помещается в название файла с tar-архивом, который будет сохранён в хранилище. Исходный код получения хеша:

Функция _getCacheId
Функция _getCacheId

Включение функции кеширования сборок

Для того, чтобы кеширование заработало, необходимо добавить файл с конфигурацией build-cache.json. Он помещается в корень папки common/config/rush.

/**  * Пример конфигурации build-cache.json  */ {   "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/build-cache.schema.json",    /**    * Для в включения функции кеширования параметр buildCacheEnabled должен иметь значение true    */   "buildCacheEnabled": true,    /**    * (Обязательный параметр) определяется где будет храниться результат сборки проекта    *    * Доступные значения: "local-only", "azure-blob-storage", "amazon-s3"    */   "cacheProvider": "local-only",     /**    * Переопределение параметра cacheEntryNamePattern позволяет изменить формат названия файлов с кэшом проектов. По дефолту добавляется только hash      */   //"cacheEntryNamePattern": "[projectName:normalize]-[phaseName:normalize]-[hash]"     /**    *  azureBlobStorageConfiguration необходимо добавить, если "cacheProvider"="azure-blob-storage"    */   "azureBlobStorageConfiguration": {     /**      * (Required) The name of the the Azure storage account to use for build cache.      */     // "storageAccountName": "example",      /**      * (Required) The name of the container in the Azure storage account to use for build cache.      */     // "storageContainerName": "my-container",      /**      * The Azure environment the storage account exists in. Defaults to AzurePublicCloud.      *      * Possible values: "AzurePublicCloud", "AzureChina", "AzureGermany", "AzureGovernment"      */     // "azureEnvironment": "AzurePublicCloud",      /**      * An optional prefix for cache item blob names.      */     // "blobPrefix": "my-prefix",      /**      * If set to true, allow writing to the cache. Defaults to false.      */     // "isCacheWriteAllowed": true   },    /**    * amazonS3Configuration необходимо добавить, если "cacheProvider"="amazon-s3"    */   "amazonS3Configuration": {     /**      * (Required unless s3Endpoint is specified) The name of the bucket to use for build cache.      * Example: "my-bucket"      */     // "s3Bucket": "my-bucket",      /**      * (Required unless s3Bucket is specified) The Amazon S3 endpoint of the bucket to use for build cache.      * This should not include any path; use the s3Prefix to set the path.      * Examples: "my-bucket.s3.us-east-2.amazonaws.com" or "http://localhost:9000"      */     // "s3Endpoint": "https://my-bucket.s3.us-east-2.amazonaws.com",      /**      * (Required) The Amazon S3 region of the bucket to use for build cache.      * Example: "us-east-1"      */     // "s3Region": "us-east-1",      /**      * An optional prefix ("folder") for cache items. It should not start with "/".      */     // "s3Prefix": "my-prefix",      /**      * If set to true, allow writing to the cache. Defaults to false.      */     // "isCacheWriteAllowed": true   } }

Чтобы включить запись кеша на локальный диск, необходимо добавить в конфигурацию build-cache.json только два параметра: buildCacheEnabled со значением true и cacheProvider со значением local-only. При таком варианте tar-архивы для каждого проекта после запуска команды rush build помещаются в папку common/temp/build-cache.

На момент написания статьи, помимо сохранения кеша в локальном хранилище, доступны ещё два варианта хранения в облачных контейнерах: Microsoft Azure blob storage container и Amazon S3 bucket. Для них необходимо передать в параметр cacheProvider значения azure-blob-storage или amazon-s3 соответственно, а также настройки для конфигурации облачного контейнера azureBlobStorageConfiguration либо amazonS3Configuration (смотрите конфигурацию выше).

Настройка параметров кеширования отдельно для каждого проекта

Если на этом этапе запустить команду rush build --verbose (при добавлении флага --verbose в консоли отображаются логи, которые обычно скрыты во время сборки), то появится предупреждение:

Project does not have a rush-project.json configuration file, or one provided by a rig,
so it does not support caching.

Чтобы избавиться от этого предупреждения, дополнительно помимо конфигурации build-cache.json нужно внутри каждого проекта настроить, какие папки и при вызове каких команд нужно закешировать. Для этого в папку config внутри каждого проекта (важный момент, не в папке common, а в каждом проекте) добавляется файл rush-project.json.

<your-project>/config/rush-project.json

{   "incrementalBuildIgnoredGlobs": ["temp/**"],    "disableBuildCacheForProject": false,  // при необходимости можно выключить кеширование для отдельных проектов    "operationSettings": [       {       "operationName": "build", // operationName — это название команда или фаза, которая вызывается в проекте        // Название папок верхнего уровня, которые необходимо закешировать. Например: lib, dist        "outputFolderNames": ["output-folder-1", "output-folder-2"]     },     {       "operationName": "_phase:build", //фазы - это еще одна интересная эксперементальная функция, см. Enabling phased builds         "outputFolderNames": ["output-folder-a", "output-folder-b"]     },     {       "operationName": "test",       "disableBuildCacheForOperation": true     }   ] }

Также этот файл будет полезен при настройке ещё одной интересной экспериментальной функции — phased builds. Она позволяет определить некоторые отдельные операции как фазы, которые можно выполнять параллельно в один момент времени. Например одновременный запуск rush build и rush test. Скажем, если сборка проекта A завершилась, а он находится в зависимостях проектов B и С, то сначала соберётся проект A, затем одновременно со сборкой проекта C и B начнётся запуск unit-тестов в A. Подробнее про это можно прочитать здесь. И кстати, включение кеширования — одно из обязательных условий для этой фичи.

После добавления файлов rush-project.json в проекты при запуске команды rush build --verbose результат сборки запишется в хранилище, а в консоли появится запись:

This project was not found in the build cache.

Caching build output folders: dist
Successfully set cache entry.

Время сборки 31 проекта при настроенном локальном хранилище: 6 минут 10,7 секунды.

При повторном запуске rush build проверяется наличие проекта в кеше. Если запись кеша есть, то существующие выходные папки удаляются, tar-архив из предыдущей сборки извлекается в папку проекта, а сборка пропускается. В результате в терминале появится запись:

Build cache hit.
Clearing cached folders: dist
Successfully restored output from the build cache.

Время повторной сборки: 1,44 секунды.

Заключение

Если сравнивать инкрементную сборку по умолчанию при локальном запуске с кешированием сборок, то длительность повторных сборок примерно одинаковая. Но инкрементная сборка не учитывает, были ли изменения в output-папках, и в таком случае (либо после переключения на новую ветку) может потребоваться полный rebuild.

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


ссылка на оригинал статьи https://habr.com/ru/company/domclick/blog/709916/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *