Создание пакета для Arch Linux

Есть замечательный инструмент для форматирования кода на скале ― scalafmt. Гибкий конфиг, интеграция с IDE, возможность добавить форматирование кода в пайплайны CI… Что ещё нужно пользователю? Нормальному пользователю, наверное, ничего. А вот мне захотелось программировать на скале в vim и форматировать текущий файл по нажатию шортката.

Интеграция форматирования кода в vim делается примерно одинаково для всех языков: плагин дёргает скрипт форматировщика и записывает результат в текущий буфер. Но тут-то и возникает нюанс. scalafmt написана на скале и собирается под JVM ― виртуальной машиной, самым известным недостатком которой является долгий запуск и прогрев. Как можно догадаться, попытки запускать scalafmt из консоли вызывают только раздражение пользователя:

./scalafmt --help  2,74s user 0,18s system 169% cpu 1,719 total

Больше двух секунд на отображение помощи! Само собой, функция, которая выводит на экран подсказки, отработала мгновенно. Всё это время занял запуск виртуальной машины.

Но как же это работает в IDE?

Intellij IDEA написана на джаве и исполняется в уже запущенной JVM. Поэтому при форматировании кода в IDE не надо запускать её по новой ― просто вызывается нужная функция. Аналогичным образом работает интеграция с sbt.

Зачем вообще это использовать, если оно медленно?

Не всем программам обязательно уметь быстро запускаться. Например, для серверных приложений скорость штатной работы важнее времени перезапуска. А со скоростью у прогретой JVM всё в порядке.

Заставляем Scala запускаться быстро

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

Scala Native ― это компилятор скалы, генерирующий представление для LLVM, которое, в свою очередь, компилируется в бинарник. Проект пока ещё сыроват, но я возлагаю огромные надежды на его будущее. Благодаря ему у скалы есть все шансы перестать быть «языком для бэкенда» и занять ниши низкоуровневых языков, например, в IoT. К сожалению, сейчас далеко не все библиотеки поддерживают компиляцию с помощью Scala Native, хотя это постепенно становится моветоном. На момент написания заметки часть зависимостей scalafmt не поддерживали нативную компиляцию, поэтому пока пришлось отложить этот вариант.

И тут на помощь приходит GraalVM ― относительно молодая виртуальная машина, которая может исполнять программы, написанные на разных языках, и, самое главное, умеет собирать бинарники. Native Image в Граале представляет из себя один исполняемый файл, содержащий в себе минималистичную виртуальную машину Substrate VM и саму скомпилированную программу. Осталось только установить GraalVM и правильным образом собрать scalafmt из исходников:

git clone https://github.com/scalameta/scalafmt
cd scalafmt
git checkout v2.0.0-RC4 # последняя версия на данный момент
sbt cli/assembly
native-image -jar scalafmt-cli/target/scala-2.12/scalafmt.jar

Теперь у нас есть CLI-утилита с нормальной скоростью запуска:

./scalafmt --help  0,01s user 0,01s system 96% cpu 0,016 total

Делаем КРАСИВО

Собрал сам ― поделись с другими. У меня на ноутбуке стоит Arch, поэтому пакет будем делать для него. На самом деле, я достаточно долго откладывал этот процесс, предвкушая типичные программистские сложности. Но оказалось, что это максимально лёгкая и доступная процедура, если хочется просто запаковать один бинарник.

Для начала надо создать пустую директорию и добавить туда файл PKGBUILD со следующим содержимым (комментариев, начинающихся с [help] в итоговом файле быть не должно!):

# Maintainer: Mikhail Chugunkov <poslegm@gmail.com> 
pkgname=scalafmt-native # [help] название пакета, по которому пользователи будут его находить в репозитории и устанавливать на свои машины  
pkgver=2.0.0.RC4 # [help] версия ПО, которое поставляется в этом пакете 
pkgrel=1 # [help] версия самого пакета; её нужно увеличивать, например, при исправлении ошибки в этом файле
pkgdesc='Code formatter for Scala built with GraalVM (for fast startup)' # [help] лаконичное описание пакета
arch=('x86_64') # [help] архитектуры, на которые этот пакет может быть установлен
url='https://scalameta.org/scalafmt/' # [help] ссылка на сайт ПО, которое поставляется в этом пакете
license=('Apache')
source=("https://chugunkov.dev/files/scalafmt-native-2.0.0.RC4.tar.gz") # [help] ссылка на tar.gz архив с содержимым пакета, который скачается и распакуется при установке
sha256sums=('deefaa75b5363872f1f8da5d2a881db3a8cb05df6692989b450963b79a7b6efd') # [help] хеш-сумма архива

# [help] действия, выполняющиеся при установке
package() {
  cd "$pkgname-$pkgver"
  mkdir -p $pkgdir/usr/bin
  install -D -m755 scalafmt "${pkgdir}/usr/bin/scalafmt"
}

Когда пользователь выполнит в терминале команду yaourt -S scalafmt-native, пакетный менеджер создаст окружение fakeroot, загрузит и распакует то, что указано в source, сверит хеш-суммы и выполнит действия из package(). Сейчас там просто нужный файл копируется в /usr/bin и делается исполняемым.

В принципе, можно прямо тут описать сборку свежей версии программы из исходников, но я решил так не делать, чтобы не заставлять пользователя выкачивать 600мб GraalVM и ждать, пока пройдёт компиляция.
Вместо этого я создал на своей VPS директорию scalafmt-native-2.0.0.RC4, положил туда заранее скомпилированный бинарник, заархивировал и пробросил наружу через nginx, а получившуюся ссылку скопировал в source. Когда выйдет новая версия scalafmt, я повторю эту процедуру и обновлю ссылку вместе с версией пакета.

Теперь осталось зарегистрироваться на AUR, указав там свой публичный ключ ssh, и выполнить несколько команд:

makepkg --printsrcinfo > .SRCINFO
git init
git add PKGBUILD .SRCINFO # остальные файлы можно кинуть в .gitignore
git commit -a
git remote add aur ssh://aur@aur.archlinux.org/scalafmt-native.git
git fetch aur
git push --set-upstream aur master

Через несколько секунд пакет становится доступным для установки! На сайте scalafmt теперь красуется инструкция по установке для арча. А чтобы держать пакет в актуальном состоянии, я подписался на релизы через гитхаб: при выходе новой версии утилиты мне придёт уведомление на почту.