プラグインとは?
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までお願いします。