Ускорение вычислений в рабочих процессах GitHub/CircleCI с помощью Affected Commands

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


Затронутые команды в двух словах

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

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

Если бы мы изменили app.ts, это повлияло бы только на приложение. Однако если мы изменим lib.ts, то будет затронута не только библиотека утилит, но и UI 2 и App. Команда affected содержит логику определения того, какие узлы проекта были затронуты изменением.

Команда имеет простую форму:

nx affected --target={your chosen target e.g. "test"} {...options}
Войти в полноэкранный режим Выйти из полноэкранного режима

Цель определяется в workspace.json (lint, test, build…). Affected имеет несколько удобных опций, таких как parallel (для последовательного или параллельного запуска задач), но две наиболее важные — base и head. Они необходимы для предоставления информации о масштабе изменений.

Вот наш учебник по Egghead, показывающий команды Nx affected в действии:

https://egghead.io/lessons/javascript-scale-ci-runs-with-nx-affected-commands

Логика обнаружения изменений

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

if (files) {
  return { files };
} else if (uncommitted) {
  return { files: getUncommittedFiles() };
} else if (untracked) {
  return { files: getUntrackedFiles() };
} else if (base && head) {
  return { files: getFilesUsingBaseAndHead(base, head) };
} else if (base) {
  return {
    files: Array.from(
      new Set([
        ...getFilesUsingBaseAndHead(base, 'HEAD'),
        ...getUncommittedFiles(),
        ...getUntrackedFiles(),
      ])
    ),
  }
Войти в полноэкранный режим Выход из полноэкранного режима

Если пользователь явно предоставил список files или запросил только uncommitted или untrackedfiles, то только эти файлы будут использоваться для обнаружения затронутых проектов. Мы также видим, что если head не указан, то будет использоваться значение по умолчанию HEAD. Побочным эффектом отсутствия head является то, что затронутая логика также будет проверять наличие неотслеживаемых и незафиксированных файлов. Очевидно, что это имеет смысл только на локальном уровне, где вы ожидаете некоторые неотслеживаемые или незафиксированные изменения.

Давайте посмотрим, что base делает по умолчанию:

base = nxJson.affected?.defaultBase || 'master';
Войти в полноэкранный режим Выйти из полноэкранного режима

Мы используем git merge-base, чтобы найти все файлы, измененные между base и head:

  • Если вы выполняете affected на ответвлении, он сравнит точку, в которой было создано ответвление, с последним коммитом в ответвлении вместе с любыми вашими незафиксированными изменениями.

На диаграмме выше, когда мы запускаем nx affected (или nx affected --base=main --head=HEAD), мы сравниваем все файлы, изменённые между коммитами #22 (оранжевый) и #2 (синий).

  • Если вы запускаете affected на главной ветке, то base и head указывают на один и тот же коммит. Наиболее очевидным выбором для основной ветви будет использование HEAD~1 в качестве базы:
nx affected --target={target} --base=HEAD~1 --head=HEAD
Войти в полноэкранный режим Выйти из полноэкранного режима

которая сравнивает последний коммит с предыдущим.

Проблема

Возьмем в качестве примера приведенный выше график коммитов. Наш CI запускает nx affected --target=build **на каждом новом коммите. Предположим, что **коммит #3 содержал код, который сломал сборку Lib, и теперь наша основная ветвь находится в сломанном состоянии. Другой разработчик поставил коммит #4, который изменяет только UI 2. Запущенное воздействие сравнит коммиты #3 и #4 и запустит цели только на App и UI 2. CI ложно сообщит, что основная ветвь теперь исправлена, потому что последняя проверка была успешной.

Поэтому коммиты не могут быть просто HEAD и HEAD~1. Если несколько рабочих процессов терпят неудачу один за другим, это означает, что мы накапливаем список затронутых проектов, которые потенциально все еще сломаны.

Решение

Мы хотим включить каждый коммит с момента последнего успешного запуска. Таким образом мы гарантируем, что случайно не пропустим проверку сломанного проекта. По сути, мы хотим использовать в качестве base последний успешный коммит на основной ветке:

nx affected --target={target} --base={last successful commit} --head=HEAD
Войти в полноэкранный режим Выход из полноэкранного режима

К счастью, большинство CI-платформ уже предоставляют API, необходимые для обнаружения последних успешных запусков, и, используя git, мы можем извлечь точные коммиты.


CircleCI Orb и GitHub Action

При использовании affected в наших CI платформах, обычные шаги в наших конвейерах таковы:

  1. Проверить код
  2. Установите зависимости
  3. Выполните команду(ы)

Скорее всего, у нас будет два отдельных рабочих процесса/трубопровода — один для основной ветки, использующей HEAD~1 в качестве базы, и один для PR-ветки, использующей основную ветку в качестве базы. Мы хотим сделать еще один шаг перед командой run, который будет находить последний успешный коммит и сохранять его в переменной окружения, чтобы соответствующие команды могли его использовать.

Для CircleCI мы создали nrwl/nx orb, который включает полезную команду set-shas для получения SHAs коммитов HEAD и последнего успешного коммита:

version: "2.1"
orbs:
    nx: nrwl/nx@1.0.0
jobs:
    build:
        docker:
            - image: cimg/node:14.17-browsers
        steps:
            # ...CI steps like checkout, install...

            - nx/set-shas
            - run:
                command: nx affected --target=build --base=$NX_BASE

            # ...other CI steps
Вход в полноэкранный режим Выход из полноэкранного режима

Аналогично, на GitHub мы предоставляем действие nx-set-shas, которое обеспечивает такую же функциональность для рабочих процессов GitHub:

jobs:
  check:
    runs-on: ubuntu-latest
    name: Check branch
    steps:
      # ...CI steps like checkout, install...

      - name: Derive appropriate SHAs for base and head for `nx affected` commands
        uses: nrwl/nx-set-shas@v2

      - run: nx affected --target=build --base=${{ env.NX_HEAD }}

      # ...other CI steps
Войти в полноэкранный режим Выход из полноэкранного режима

Резюме

Использование affected может ускорить время вычислений, но если вы не используете его правильно, он также может стать источником проблем с сопровождением.

В этом посте мы рассмотрели, как работает команда affected и как ее использовать, а также какие распространенные проблемы возникают при ее использовании в CI.

Наши CircleCI orb и Github Action помогут вам всегда сравнивать с последней успешной сборкой и убедиться, что вы защищены от поломок из зафиксированного кода.

Оцените статью
devanswers.ru
Добавить комментарий