Популярная викторина: как вы пишете и выполняете свои ежедневные задачи?

Если у вас есть умеренно большой проект React или проект на Go или любом другом языке, вам, вероятно, понадобятся собственные сборки, тесты или задачи CI.

Например, для npm или yarn в Node.js вы должны написать свою собственную задачу test:integration, которая запускает только ваши интеграционные тесты:

$ yarn test:integration

И test:integration может быть любой серией команд оболочки, которые сообщают вашему тестировщику, что фильтровать.

И иногда у этих заданий есть зависимости, поэтому вы упорядочиваете их в правильном порядке:

$ yarn clean && yarn build && yarn test:integration

Затем вы помещаете их в свой файл package.json:

"ci": "yarn clean && yarn build && yarn test:integration"

Которые в итоге становятся:

$ yarn ci

Для make или подобных инструментов, для всех видов задач, таких как сборка, анализ, тестирование, покрытие, CI, выпуск, это выглядит очень похоже.

Иногда вам нужно написать свои собственные сценарии оболочки «инструментария CI»:

$ ./ci/build-dev.sh

Как мы делаем это в проектах на Rust?

На самом деле невозможно выполнять специальные задачи с Cargo, как с yarn или npm, путем добавления некоторого сценария оболочки в файл манифеста (для Rust — cargo.toml).

Люди обычно решают это с помощью пользовательских сценариев оболочки, вызываемых для конкретной потребности, или используя (злоупотребляя?) make, подобно тому, как это делало сообщество Go.

Есть несколько причин, по которым мы идем таким простым путем. Они есть:

  • Это просто, просто назовите задачу и вперед
  • Мы все знаем команды оболочки
  • Порядок выполнения и режимы отказа также наследуются от оболочки.
  • Легко экспериментировать и повторять

Проблема рабочего процесса

Попробовать команду оболочки и вставить ее как задачу make отлично подходит для простых случаев.

Но для любого умеренно крупного проекта задайте себе следующие вопросы:

  • Как вы предоставляете пользовательские параметры для make? (или npm задач, если уж на то пошло)
  • Как насчет предоставления параметров сценарию оболочки? а флаги или опции? а по умолчанию?

Ответ таков: вы возвращаетесь к тому вуду сценариев оболочки, с которым вы вы знакомы в тот момент.

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

Проблема производительности

Как и в «обычном» программном обеспечении, для продуктивной работы вам необходимо:

  • Сделайте некоторую программную логику
  • Повторно используйте существующую кодовую базу
  • Импортируйте чужие библиотеки или задачи
  • Способность отлаживать, рассуждать или тестировать задачи

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

Какие есть решения?

Очевидно, что у этой задачи есть несколько решений.

Инструменты декларативной «линейной» сборки

Есть ваши типичные инструменты, которые были созданы для «запуска моей команды оболочки», такие как yarn, make и т. д.

Вы можете использовать все, что знаете о переменных окружения, порядке команд оболочки и логике (&& и т. д.), использовать позиционные параметры $1, $2, и т. д. и надеяться на лучшее.

Более умные инструменты «сделать», полудекларативные

Такие инструменты, как just или cargo-make, специально созданы для решения проблем, связанных с задачами, и это хорошо.

Например, построение задач с cargo-make выглядит так:

[tasks.A]
dependencies = ["B", "C"]
[tasks.B]
dependencies = ["D"]
[tasks.C]
dependencies = ["D2"]
[tasks.D]
script = "echo hello"
[tasks.D2]
alias="D"

Итак, мы получаем:

  • Инструменты, ориентированные на задачу, с зависимостями, разрешение заказа
  • Удобство для параметров и именования

Но в конечном итоге мы ограничены выполнением команд в оболочке или используемым инструментом.

Инструментарий языка программирования DSL

В этой категории у вас есть библиотека или инфраструктура, и вам остается создавать задачи самостоятельно.

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

Отличным примером для этого был rake в Ruby. Это выглядело как make, но вы строите все свои задачи на своем родном языке: Ruby.

В какой-то момент похожее решение (скорее ориентир) появилось для Rust: xtask.

xзадача

xtask — это условность, практика создания проекта.

Если вы выполните шаги по настройке, вы получите команду cargo xtask и новый проект Rust под названием xtask, в котором вы создадите все свои пользовательские материалы.

Обычно использование xtask означает, что вы используете груз так, как привыкли.

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

Настройка xtask

Мы пытаемся перейти от проекта с одним ящиком к рабочему пространству с несколькими ящиками, в котором у вас есть два участника:

[ your-project, xtask ]

Если ваш проект называется acme, переместите свой код в новую внутреннюю папку acme/.

Затем в вашем проекте:

$ cargo new --bin xtask

Настройте свое рабочее пространство (root cargo.toml):

[workspace]
members = [
  "acme",
  "xtask"
]

Добавьте псевдоним для cargo, чтобы знать xtask.cargo/config):

[alias]
xtask = "run --package xtask --"

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

Вот где вы хотели бы:

  • Легко принимать команды и параметры
  • Направляйте команды к задачам, которые вы написали, легко
  • Создавайте задачи легко

На данный момент не существует рекомендуемого способа легко создавать xtask задачи.

Вот почему xtaskops родился.

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

Используя xtaskops, вот полностью загруженный двоичный файл xtask с большим количеством задач, чем вам нужно. Он более или менее подходит для одного экрана:

Используя библиотеку xtaskops, вы бесплатно получаете все распространенные задачи в Rust, а также некоторые интересные задачи, такие как bloat-deps (показывает самые большие зависимости по размеру) и powerset (запускает тесты для комбинаций функций).

Просто подключите их к выбранной вами среде командной строки (здесь мы используем clap).

Вот некоторые задачи, доступные сегодня в xtaskops:

  • bloat_deps Показать самые большие ящики в релизной сборке
  • bloat_time Показать время сборки контейнера
  • dev Запускать проверку груза, а затем тест груза для каждого изменения файла.
  • ci Последовательно запускайте типичные задачи CI: fmt, clippy и тесты.
  • покрытие
  • docs Запуск грузовых документов в режиме просмотра
  • установить Instal грузовые инструменты
  • powerset Выполнение сборки CI с набором функций powerset

Для запуска собственных команд используйте макрос cmd!:

cmd!(your,command,here)

Использование xtask для каждого проекта

Вместо ручной настройки xtask снова и снова для каждого проекта вы можете использовать rust-starter, который управляется xtask.

Для существующих проектов вы можете скопировать файлы xtask или использовать Рюкзак, который может легко захватывать части репозиториев.

Конечно, вы можете создать свой собственный стартовый проект: один раз настроить xtask для себя и повторно использовать этот проект для всей своей работы. Затем используйте Рюкзак, чтобы запускать каждый новый проект с адреса вашего репозитория.

Заключение

Прежде чем добавить еще одну задачу make или написать еще один собственный скрипт сборки оболочки, помните:

  • Ваша сборка, ci, рабочий процесс, код поручений — все еще поддерживаемый код
  • Не стоит недооценивать накладные расходы на обслуживание задачи, использующей команды оболочки.
  • Ваша оболочка voodoo должна быть безошибочной, протестированной и обоснованной.
  • Командные инвестиции в знания, код, стандарты и практики лучше использовать повторно.
  • make может быть сложным и требовать забытых знаний (например, .PHONY)
  • Для задач package.json часто требуются дополнительные пакеты (rimraf, concurrently)

Вы можете использовать xtask, чтобы облегчить все эти боли, и написать больше кода на Rust.