LoginSignup
4
4

More than 3 years have passed since last update.

sbtのAutoPluginを理解する(requires, trigger, autoImport)

Last updated at Posted at 2019-11-24

プラグインとは?

sbtのプラグインは、簡単に言うと「ビルド定義の外付け」です。
複数のプロジェクト間にまたがる設定を共通化できます。
また外部ツールやフレームワークを使うときに、それ用のプラグインを読み込まないとそもそも動かなかったりします。
そういったときはproject/plugins.sbtにプラグインを追加することで、必要としている設定を読み込ませることが出来ます(こっちのほうがメインかも)。

例えば、フレームワークとしてPlayFrameworkを使いたいなら、以下のようにaddSbtPluginでPlayFramework用のプラグインを追加してあげる必要があります。

project/plugins.sbt
addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.7.3")

auto pluginとは?

プラグインを使ったビルド定義の拡張を簡便にするための仕組みです。

sbt 1.0より前は、auto plugin以外の方法でプラグインを制御することも可能でした(そもそもauto plugin自体がsbt 0.13.5からの新機能)。
1.0からはauto pluginのみサポートされるようになり、実質「プラグイン = auto plugin」となっています。

プラグイン、auto pluginについては、以下の公式ドキュメントが参考になると思います。
始めるsbt l.プラグインの使用

AutoPluginとは?

ここからが本題です。
AutoPluginとは、上記のauto pluginを適用するためのScalaの抽象クラスです。
AutoPlugin

プラグインを作成する際は、以下のようにAutoPluginを継承したサブクラスを作ります。

FooPlugin.scala
object FooPlugin extends AutoPlugin {

AutoPluginで押さえておきたい2つのポイント

AutoPluginを継承する際は、以下の2つのポイントを意識します。

  • 他のプラグインへの依存関係はあるか(requires)
  • このプラグインで拡張したビルド定義を、自動適用するか、使う場合のみユーザーに明示的に宣言させるか(trigger)

他にも、
「プラグインで定義したキーをbuild.sbt側でimportなしに指定できるようにする機能(autoImport)」や
「各定義の設定単位(projectSettings, buildSettings, globalSettings)」
などがありますが、とにもかくにも、先に挙げた2つを押さえなければ始まりません。

requires - 他のプラグインへの依存関係を記述する

プラグインが自身を追加される際に他のプラグインの定義が必要であれば、requiresメソッドをoverrideし、必要なプラグインを書き連ねます。

FooPlugin.scala
object FooPlugin extends AutoPlugin {
  override def requires: Plugins = BarPlugin

こう書くことで、ユーザー側がFooPluginを使用したい場合は、同時にBarPluginも読み込むことを強制します。
依存するプラグインは複数指定可能で、その場合は&&で繋ぎます。1

FooPlugin.scala
object FooPlugin extends AutoPlugin {
  override def requires: Plugins = BarPlugin && BazPlugin

なおrequiresをoverrideしなかった場合、AutoPluginは規定でJvmPluginというプラグインを要求します。

Plugins.scala
abstract class AutoPlugin extends Plugins.Basic with PluginsFunctions {
  def requires: Plugins = plugins.JvmPlugin

(※Plugins.scalaはsbtのソース)
そのため本来利用側はJvmPluginの使用を記述する必要があるのですが、JvmPluginは後述するtriggerにより、明示的な記述が不要なプラグインとなっています。

またemptyを指定することで、依存関係が一切不要であると明記することも可能です。

FooPlugin.scala
object FooPlugin extends AutoPlugin {
  override def requires: Plugins = empty

emptyの実体はPlugins.Emptyというオブジェクトであり、AutoPluginと同じくPluginsトレイトのサブクラスです。2

trigger - プラグインを自動適用するか、明示的に記述させるかの選択

triggerrequiresと同様にAutoPluginクラスで定義されているメソッドであり、PluginTrigger型の値を返します。

Plugins.scala
abstract class AutoPlugin extends Plugins.Basic with PluginsFunctions {
  def trigger: PluginTrigger = noTrigger

PluginTriggersealedクラスであり、AllRequirementsNoTriggerのどちらかの値を取ります。

PluginTrigger.scala
sealed abstract class PluginTrigger extends Serializable
object PluginTrigger {
  case object AllRequirements extends PluginTrigger
  case object NoTrigger extends PluginTrigger
}

なおAllRequirementsNoTriggerも(そして上述のPlugins.Emptyも)、オブジェクト名を直接指定するのではなく、対象のオブジェクトを返すメソッドを記述することが慣例となっています。3

Plugins.scala
// AutoPluginはPluginsFunctionsを継承しているため、下記の関数が使える
sealed trait PluginsFunctions {
  def empty: Plugins = Plugins.Empty
  def allRequirements: PluginTrigger = AllRequirements
  def noTrigger: PluginTrigger = NoTrigger
}

AutoPluginクラスは、デフォルトではNoTriggerを指定しています。

Plugins.scala
abstract class AutoPlugin extends Plugins.Basic with PluginsFunctions {
  def trigger: PluginTrigger = noTrigger

必要であれば定義したプラグイン側でtriggerメソッドをoverrideし、AllRequirementsを指定します。

FooPlugin.scala
object FooPlugin extends AutoPlugin {
  override def trigger: PluginTrigger = allRequirements

AllRequirementsとNoTriggerの違い

AllRequirementsNoTriggerの違いは、以下のとおりです。

AllRequirements

requiresに記述された依存関係が全て満たされれば、自動的に読み込まれる。(enablePlugins不要)
逆に言えば、requiresに記述された依存関係を全て解決しなければ使用不可。

NoTrigger

プラグインを利用するユーザーは、build.sbtにプラグインの利用を明記する必要がある(enablePlugins(プラグイン名))。
明記されれば、requiresに記述されたプラグインを自動的に追加する。
requiresに記述されたプラグインが、また別のプラグインに依存していれば、それも追加する(以下、追加するプラグインがなくなるまで繰り返し)。

例題

例として、以下のプラグイン群を考えます。

SamplePlugins.scala
object Foo extends AutoPlugin {
  override def requires: Plugins = Bar
}

object Bar extends AutoPlugin {
  override def requires: Plugins = Baz
  override def trigger: PluginTrigger = allRequirements
}

object Baz extends AutoPlugin

object Hoge extends AutoPlugin {
  override def requires: Plugins = Foo
  override def trigger: PluginTrigger = allRequirements
} 

問1. build.sbtに指定がない場合、上記のプラグインの内、追加されるプラグインはどれか。

答. どのプラグインも追加されない。

BarプラグインとHogeプラグインはtriggerallRequirementsになっていますが、依存しているプラグイン(Baz, Foo)が追加されてないため、これらが自動的に追加されることもありません。

問2. 以下のようにBazプラグインを明示的に追加した場合、追加されるプラグインはどれか。

build.sbt
lazy val root = (project in file("."))
  .enablePlugins(Baz)

答. BazBarが追加される。

enablePlugins(Baz)とした時点で、Bar側の依存関係が解決されたため、Barも自動的に追加されます。
Barプラグインが追加されたことでFooの依存関係も解決されましたが、FootriggerNoTrigger(既定値)のため、それで自動的に追加されることはありません。

問3. 以下のようにFooプラグインを明示的に追加した場合、追加されるプラグインはどれか。

build.sbt
lazy val root = (project in file("."))
  .enablePlugins(Foo)

答. Foo, Bar, Baz, Hogeが追加される。

Fooを有効にするにはBarが必要で、Barを有効にするにはBazが必要ですが、それら必要となるプラグインが全て追加されます。
そして、Fooが有効になったことで、Hogeが自動的に追加されます。

追加したプラグインを無効化する

上記の例で、「Foo, Bar, Bazの3つは有効にしたいが、Hogeは無効化したい」というケースはどうすればいいでしょうか。
Fooが有効になった時点で、Hogeも自動的に追加されてしまいます。
このようなケースは、build.sbtdisablePlugins(プラグイン名)と記述することで、対象のプラグインを除外することが可能です。

build.sbt
lazy val root = (project in file("."))
  .enablePlugins(Foo)
  .disablePlugins(Hoge)

このように、使いたいプラグインは自由に選択・除外できます。


autoImport

また、AutoPluginにはautoImportと呼ばれる機能があります。
こちらはプラグイン内でautoImportという名前で定義したobject(またはval, lazy val)内のフィールドに、build.sbt側からimportなしでアクセスできる機能です。

例えば、以下のようなプラグインがあるとします。

Piyo.scala
object Piyo extends AutoPlugin {
  override def trigger = allRequirements
  override def requires: Plugins = empty

  object Fuga {
    lazy val helloCommand =
      Command.command("hello") { state =>
        println("hello world!")
        state
      }
  }
}

このFuga.helloCommandを使うためには、build.sbt側で下記のようにimport文を記述する必要があります。

build.sbt
import Piyo.Fuga // importしないと使えない
lazy val root = (project in file("."))
  .settings(commands += Fuga.helloCommand)

しかし、このFugaオブジェクトがFugaではなくautoImportという名前だった場合のみ、このimport文とクラス名の修飾子を省略することが可能となります。4

Piyo.scala
object Foo extends AutoPlugin {
  override def trigger = allRequirements
  override def requires: Plugins = empty

  // FugaではなくautoImportという名前で定義(中身は同じ)
  object autoImport {
    lazy val helloCommand =
      Command.command("hello") { state =>
        println("hello world!")
        state
      }
  }
}
build.sbt
lazy val root = (project in file("."))
  .settings(commands += helloCommand) // importおよび修飾子が不要になる
sbt shell
$ hello
hello world!

これがautoImport機能です。

まとめ

sbtのAutoPluginの機能(の一部)をまとめてみました。
プラグインを自作するとき、あるいは他人の書いたプラグインを読み解かなければいけない場合に参考になれば幸いです。

また、上記のサンプルをまとめ一部を変更したものを https://github.com/ka2kamaboko/plugins-sample に置いてあります。
実験などに使ってください。

最後までお読み頂きありがとうございました。
質問や不備についてはコメント欄かTwitterまでお願いします。


  1. この&&は条件演算子で使われる&&ではなく、Pluginsトレイトで宣言されているプラグインを繋ぐためのメソッドです。 

  2. 余談ですがEmptyオブジェクトはPluginsトレイトの直接の子クラスで、AutoPluginクラスは孫クラスです。AutoPluginクラスはBasicクラスを継承しており、BasicクラスがPluginsトレイトの直接の子クラスです。 

  3. メソッドを使わずオブジェクト名を直接記述することも可能です。 

  4. 省略できるというだけなので、別に省略せずに記述することも可能です。 

4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4