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

#プラグインとは？
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&emsp;l.プラグインの使用](https://www.scala-sbt.org/1.x/docs/ja/Using-Plugins.html)

#AutoPluginとは？
ここからが本題です。
`AutoPlugin`とは、上記のauto pluginを適用するためのScalaの抽象クラスです。
[AutoPlugin](https://www.scala-sbt.org/1.3.3/api/sbt/AutoPlugin.html)

プラグインを作成する際は、以下のように`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 - プラグインを自動適用するか、明示的に記述させるかの選択
`trigger`は`requires`と同様に`AutoPlugin`クラスで定義されているメソッドであり、`PluginTrigger`型の値を返します。

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

`PluginTrigger`は`sealed`クラスであり、`AllRequirements`か`NoTrigger`のどちらかの値を取ります。

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

なお`AllRequirements`も`NoTrigger`も(そして上述の`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の違い
`AllRequirements`と`NoTrigger`の違いは、以下のとおりです。
###AllRequirements
`requires`に記述された依存関係が全て満たされれば、自動的に読み込まれる。(`enablePlugins`不要)
逆に言えば、`requires`に記述された依存関係を全て解決しなければ使用不可。



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

```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`プラグインは`trigger`が`allRequirements`になっていますが、依存しているプラグイン(`Baz`, `Foo`)が追加されてないため、これらが自動的に追加されることもありません。

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

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

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

####問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.sbt`に`disablePlugins(プラグイン名)`と記述することで、対象のプラグインを除外することが可能です。

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

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

<br>
#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&nbsp;shell
$ hello
hello world!
```

これが`autoImport`機能です。

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

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

最後までお読み頂きありがとうございました｡
質問や不備についてはコメント欄か[Twitter](https://twitter.com/ka2_kamaboko)までお願いします。

[^1]: この`&&`は条件演算子で使われる`&&`ではなく、[Plugins](https://www.scala-sbt.org/1.3.3/api/sbt/Plugins.html)トレイトで宣言されているプラグインを繋ぐためのメソッドです。
[^2]: 余談ですが`Empty`オブジェクトは`Plugins`トレイトの直接の子クラスで、`AutoPlugin`クラスは孫クラスです。`AutoPlugin`クラスは`Basic`クラスを継承しており、`Basic`クラスが`Plugins`トレイトの直接の子クラスです。
[^3]: メソッドを使わずオブジェクト名を直接記述することも可能です。
[^4]: 省略できるというだけなので、別に省略せずに記述することも可能です。