Статьи

SBT AutoPlugins Tutorial

Этот учебник проведет вас через процесс написания вашего собственного плагина sbt. Есть несколько причин сделать это, и это действительно просто:

  1. Добавьте настраиваемые этапы сборки в процесс непрерывной интеграции
  2. Предоставить настройки по умолчанию для разных сред для разных проектов

Прежде чем начать, убедитесь, что на вашем компьютере установлен sbt и доступен через командную строку . Код доступен на github . Если вы начинаете с первого коммита, вы можете просто пройти учебник с каждым коммитом.

Настроить

Первым шагом является настройка нашего проекта плагина. В вашем build.sbt есть только один важный параметр, остальное зависит от вас.

1
sbtPlugin := true

Это пометит ваш проект как сборку sbt-плагина. Для этого урока я использую sbt 0.13.7-M3, который позволяет вам писать свой build.sbt как вам угодно. Нет необходимости в разделении линий. Полный build.sbt выглядит следующим образом.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
name := "awesome-os"
organization := "de.mukis"
 
scalaVersion in Global := "2.10.2"
 
sbtPlugin := true
 
// Settings to build a nice looking plugin site
site.settings
com.typesafe.sbt.SbtSite.SiteKeys.siteMappings <+= (baseDirectory) map { dir =>
  val nojekyll = dir / "src" / "site" / ".nojekyll"
  nojekyll -> ".nojekyll"
}
site.sphinxSupport()
site.includeScaladoc()
 
// enable github pages
ghpages.settings
git.remoteRepo := "[email protected]:muuki88/sbt-autoplugins-tutorial.git"
 
// Scripted - sbt plugin tests
scriptedSettings
scriptedLaunchOpts <+= version apply { v => "-Dproject.version="+v }

Плагины, используемые внутри этой сборки, настраиваются в проекте / plugins.sbt . Ничего особенного.

Плагин

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

Давайте взглянем на наш код плагина.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import sbt._
import sbt.Keys.{ streams }
 
/**
 * This plugin helps you which operating systems are awesome
 */
object AwesomeOSPlugin extends AutoPlugin {
 
  /**
   * Defines all settings/tasks that get automatically imported,
   * when the plugin is enabled
   */
  object autoImport {
    lazy val awesomeOsPrint = TaskKey[Unit]("awesome-os-print", "Prints all awesome operating systems")
    lazy val awesomeOsList = SettingKey[Seq[String]]("awesome-os-list", "A list of awesome operating systems")
  }
 
  import autoImport._
 
  /**
   * Provide default settings
   */
  override lazy val projectSettings = Seq(
    awesomeOsList := Seq(
      "Ubuntu 12.04 LTS","Ubuntu 14.04 LTS","Debian Squeeze",
      "Fedora 20","CentOS 6",
      "Android 4.x",
      "Windows 2000","Windows XP","Windows 7","Windows 8.1",
      "MacOS Maverick","MacOS Yosemite",
      "iOS 6","iOS 7"
    ),
    awesomeOsPrint := {
      awesomeOsList.value foreach (os => streams.value.log.info(os))
    }
  )
 
}

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

1
awesomeOsList += "Solaris"

awesomeOsPrint — это задача, то есть она выполняется каждый раз, когда вы ее вызываете.

Тестовый проект

Давайте попробуем плагин. Для этого мы создаем тестовый проект, который зависит от нашего плагина os. Мы создаем каталог test-project в корневом каталоге нашего проекта плагина. Внутри test-project мы добавляем build.sbt со следующим содержимым:

1
2
3
4
5
name := "test-project"
version := "1.0"
 
// enable our now plugin
enablePlugins(AwesomeOSPlugin)

Однако настоящий трюк делается внутри test-project / project / plugins.sbt . Мы создаем ссылку на проект в родительском каталоге:

1
2
3
4
5
// build root project
lazy val root = Project("plugins", file(".")) dependsOn(awesomeOS)
 
// depends on the awesomeOS project
lazy val awesomeOS = file("..").getAbsoluteFile.toURI

И это все. Запустите sbt внутри тест-проекта и распечатайте потрясающие операционные системы.

1
sbt awesomeOsPrint

Если вы что-то изменили в коде своего плагина, просто вызовите reload, и ваш тест-проект перекомпилирует изменения.

Добавьте новое задание и протестируйте его

Затем мы добавляем задачу, которая хранит awesomeOsList внутри файла. Это то, что мы можем автоматически проверить. Тестирование sbt-плагинов немного утомительно, но выполнимо с помощью скриптового плагина.

Сначала мы создаем папку внутри src / sbt-test. Каталоги внутри sbt-test можно рассматривать как категории, в которые вы помещаете свои тесты. Я создал глобальную папку, в которую я положил два тестовых проекта. Критическая конфигурация снова внутри проекта / plugins.sbt

1
addSbtPlugin("de.mukis" % "awesome-os" % sys.props("project.version"))

Подключаемый модуль с сценариями сначала локально добавляет плагин, а затем передает номер версии каждой запущенной сборке sbt-теста через системное свойство project.version . Мы добавили это поведение в наш build.sbt ранее:

1
scriptedLaunchOpts <+= version apply { v => "-Dproject.version="+v }

Каждый тестовый проект содержит файл с именем test , который может содержать команды sbt и несколько простых команд проверки. Обычно вы вводите несколько простых проверок, таких как файл, и выполняете более сложные вещи внутри задачи, определенной в тестовом проекте.

Тестовый файл для нашего второго теста выглядит следующим образом.

1
2
3
4
# Create the another-os.txt file
> awesomeOsStore
$ exists target/another-os.txt
> check-os-list

Задача check-os-list определяется внутри build.sbt тестового проекта ( /src/sbt-test/global/store-custom-oslist/build.sbt .

01
02
03
04
05
06
07
08
09
10
11
12
13
enablePlugins(AwesomeOSPlugin)
 
name := "simple-test"
 
version := "0.1.0"
 
awesomeOsFileName := "another-os.txt"
 
// this is the scripted test
TaskKey[Unit]("check-os-list") := {
  val list = IO.read(target.value / awesomeOsFileName.value)
  assert(list contains "Ubuntu", "Ubuntu not present in awesome operating systems: " + list)
}

Отдельные операционные системы для каждого плагина

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

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

Первым шагом является создание трех новых автоматических плагинов: AwesomeWindowsPlugin , AwesomeMacPlugin и AwesomeLinuxPlugin . Все они будут работать одинаково:

  1. Задайте параметры проекта из AwesomeOSPlugin, чтобы задать пользовательскую область конфигурации и предоставить их в качестве параметров.
  2. Переопределить определенные параметры / задачи в пределах определенной пользователем области конфигурации

AwesomeLinuxPlugin выглядит так:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import sbt._
 
object AwesomeLinuxPlugin extends AutoPlugin{
 
  object autoImport {
    /** Custom configuration scope */
    lazy val Linux = config("awesomeLinux")
  }
 
  import AwesomeOSPlugin.autoImport._
  import autoImport._
 
  /** This plugin requires the AwesomeOSPlugin to be enabled */
  override def requires = AwesomeOSPlugin
 
  /** If all requirements are met, this plugin will automatically get enabled */
  override def trigger = allRequirements
 
  /**
   * 1. Use the AwesomeOSPlugin settings as default and scope them to Linux
   * 2. Override the default settings inside the Linux scope
   */
  override lazy val projectSettings = inConfig(Linux)(AwesomeOSPlugin.projectSettings) ++ settings
 
  /**
   * the linux specific settings
   */
  private lazy val settings: Seq[Setting[_]] = Seq(
    awesomeOsList in Linux := Seq(
      "Ubuntu 12.04 LTS",
      "Ubuntu 14.04 LTS",
      "Debian Squeeze",
      "Fedora 20",
      "CentOS 6",
      "Android 4.x"),
    // add awesome os to the general list
    awesomeOsList ++= (awesomeOsList in Linux).value
  )
}

Другие плагины определены таким же образом. Давайте попробуем. Запустите sbt в вашем тестовом проекте.

1
2
3
4
5
sbt
awesomeOsPrint # will print all operating systems
awesomeWindows:awesomeOsPrint # will only print awesome windows os
awesomeMac:awesomeOsPrint # only mac
awesomeLinux:awesomeOsPrint # only linux

SBT уже предоставляет некоторые области, такие как Compile , Test и т. Д. Таким образом, существует только небольшая потребность в создании ваших собственных областей. Большую часть времени вы будете использовать уже предоставленные и настраивать их в своих плагинах.

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

1
2
3
4
5
// what is required that this plugin can be enabled
<span class="k">override</span> <span class="k">def</span> <span class="nf">requires</span> <span class="o">=</span> <span class="n">AwesomeOSPlugin
</span>
// when should this plugin be enabled
<span class="k">override</span> <span class="k">def</span> <span class="nf">trigger</span> <span class="o">=</span> allRequirements

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

Вывод

SBT Autoplugins значительно облегчает жизнь пользователям и разработчикам плагинов. Это немного снижает крутую кривую обучения для sbt и создает более читаемые файлы сборки. Для разработчиков sbt-плагинов процесс миграции не очень сложен. Замена sbt.Plugin на sbt.AutoPlugin и создание поля автоимпорта .

Ссылка: Учебное пособие по SBT AutoPlugins от нашего партнера JCG Непомука Сайлера в блоге mukis.de .