プラグインとは?
sbtのプラグインは、簡単に言うと「ビルド定義の外付け」です。
複数のプロジェクト間にまたがる設定を共通化できます。
また外部ツールやフレームワークを使うときに、それ用のプラグインを読み込まないとそもそも動かなかったりします。
そういったときはproject/plugins.sbt
にプラグインを追加することで、必要としている設定を読み込ませることが出来ます(こっちのほうがメインかも)。
例えば、フレームワークとしてPlayFrameworkを使いたいなら、以下のようにaddSbtPlugin
でPlayFramework用のプラグインを追加してあげる必要があります。
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
を継承したサブクラスを作ります。
object FooPlugin extends AutoPlugin {
AutoPluginで押さえておきたい2つのポイント
AutoPlugin
を継承する際は、以下の2つのポイントを意識します。
- 他のプラグインへの依存関係はあるか(
requires
) - このプラグインで拡張したビルド定義を、自動適用するか、使う場合のみユーザーに明示的に宣言させるか(
trigger
)
他にも、
「プラグインで定義したキーをbuild.sbt側でimportなしに指定できるようにする機能(autoImport
)」や
「各定義の設定単位(projectSettings
, buildSettings
, globalSettings
)」
などがありますが、とにもかくにも、先に挙げた2つを押さえなければ始まりません。
requires - 他のプラグインへの依存関係を記述する
プラグインが自身を追加される際に他のプラグインの定義が必要であれば、requires
メソッドをoverrideし、必要なプラグインを書き連ねます。
object FooPlugin extends AutoPlugin {
override def requires: Plugins = BarPlugin
こう書くことで、ユーザー側がFooPlugin
を使用したい場合は、同時にBarPlugin
も読み込むことを強制します。
依存するプラグインは複数指定可能で、その場合は&&
で繋ぎます。1
object FooPlugin extends AutoPlugin {
override def requires: Plugins = BarPlugin && BazPlugin
なおrequires
をoverrideしなかった場合、AutoPlugin
は規定でJvmPlugin
というプラグインを要求します。
abstract class AutoPlugin extends Plugins.Basic with PluginsFunctions {
def requires: Plugins = plugins.JvmPlugin
(※Plugins.scala
はsbtのソース)
そのため本来利用側はJvmPlugin
の使用を記述する必要があるのですが、JvmPlugin
は後述するtrigger
により、明示的な記述が不要なプラグインとなっています。
またempty
を指定することで、依存関係が一切不要であると明記することも可能です。
object FooPlugin extends AutoPlugin {
override def requires: Plugins = empty
empty
の実体はPlugins.Empty
というオブジェクトであり、AutoPlugin
と同じくPlugins
トレイトのサブクラスです。2
trigger - プラグインを自動適用するか、明示的に記述させるかの選択
trigger
はrequires
と同様にAutoPlugin
クラスで定義されているメソッドであり、PluginTrigger
型の値を返します。
abstract class AutoPlugin extends Plugins.Basic with PluginsFunctions {
def trigger: PluginTrigger = noTrigger
PluginTrigger
はsealed
クラスであり、AllRequirements
かNoTrigger
のどちらかの値を取ります。
sealed abstract class PluginTrigger extends Serializable
object PluginTrigger {
case object AllRequirements extends PluginTrigger
case object NoTrigger extends PluginTrigger
}
なおAllRequirements
もNoTrigger
も(そして上述のPlugins.Empty
も)、オブジェクト名を直接指定するのではなく、対象のオブジェクトを返すメソッドを記述することが慣例となっています。3
// AutoPluginはPluginsFunctionsを継承しているため、下記の関数が使える
sealed trait PluginsFunctions {
def empty: Plugins = Plugins.Empty
def allRequirements: PluginTrigger = AllRequirements
def noTrigger: PluginTrigger = NoTrigger
}
AutoPlugin
クラスは、デフォルトではNoTrigger
を指定しています。
abstract class AutoPlugin extends Plugins.Basic with PluginsFunctions {
def trigger: PluginTrigger = noTrigger
必要であれば定義したプラグイン側でtrigger
メソッドをoverrideし、AllRequirements
を指定します。
object FooPlugin extends AutoPlugin {
override def trigger: PluginTrigger = allRequirements
AllRequirementsとNoTriggerの違い
AllRequirements
とNoTrigger
の違いは、以下のとおりです。
AllRequirements
requires
に記述された依存関係が全て満たされれば、自動的に読み込まれる。(enablePlugins
不要)
逆に言えば、requires
に記述された依存関係を全て解決しなければ使用不可。
NoTrigger
プラグインを利用するユーザーは、build.sbt
にプラグインの利用を明記する必要がある(enablePlugins(プラグイン名)
)。
明記されれば、requires
に記述されたプラグインを自動的に追加する。
requires
に記述されたプラグインが、また別のプラグインに依存していれば、それも追加する(以下、追加するプラグインがなくなるまで繰り返し)。
例題
例として、以下のプラグイン群を考えます。
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
プラグインはtrigger
がallRequirements
になっていますが、依存しているプラグイン(Baz
, Foo
)が追加されてないため、これらが自動的に追加されることもありません。
問2. 以下のようにBaz
プラグインを明示的に追加した場合、追加されるプラグインはどれか。
lazy val root = (project in file("."))
.enablePlugins(Baz)
答. Baz
とBar
が追加される。
enablePlugins(Baz)
とした時点で、Bar
側の依存関係が解決されたため、Bar
も自動的に追加されます。
Bar
プラグインが追加されたことでFoo
の依存関係も解決されましたが、Foo
のtrigger
はNoTrigger
(既定値)のため、それで自動的に追加されることはありません。
問3. 以下のようにFoo
プラグインを明示的に追加した場合、追加されるプラグインはどれか。
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.sbt
にdisablePlugins(プラグイン名)
と記述することで、対象のプラグインを除外することが可能です。
lazy val root = (project in file("."))
.enablePlugins(Foo)
.disablePlugins(Hoge)
このように、使いたいプラグインは自由に選択・除外できます。
autoImport
また、AutoPlugin
にはautoImport
と呼ばれる機能があります。
こちらはプラグイン内でautoImport
という名前で定義したobject
(またはval
, lazy val
)内のフィールドに、build.sbt
側からimport
なしでアクセスできる機能です。
例えば、以下のようなプラグインがあるとします。
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
文を記述する必要があります。
import Piyo.Fuga // importしないと使えない
lazy val root = (project in file("."))
.settings(commands += Fuga.helloCommand)
しかし、このFuga
オブジェクトがFuga
ではなくautoImport
という名前だった場合のみ、このimport
文とクラス名の修飾子を省略することが可能となります。4
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
}
}
}
lazy val root = (project in file("."))
.settings(commands += helloCommand) // importおよび修飾子が不要になる
$ hello
hello world!
これがautoImport
機能です。
まとめ
sbtのAutoPlugin
の機能(の一部)をまとめてみました。
プラグインを自作するとき、あるいは他人の書いたプラグインを読み解かなければいけない場合に参考になれば幸いです。
また、上記のサンプルをまとめ一部を変更したものを https://github.com/ka2kamaboko/plugins-sample に置いてあります。
実験などに使ってください。
最後までお読み頂きありがとうございました。
質問や不備についてはコメント欄かTwitterまでお願いします。