sbt 0.13.2-M3の新機能AutoPluginを使ってみる 2014年03月12日

昨日 sbt 0.13.2-M3 が出たのですが、その新機能のAutoPluginに興味を持ったので、試しに使ってみることにしました。

AutoPluginの機能

AutoPluginには以下の機能があるようです。

  1. 設定の自動読み込み
  2. Pluginの依存関係を書くことができる
  3. プロジェクトごとにどのプラグインを使うか使わないかの設定を細かくおこなえるようになる

1は、たとえば sbt-assembly というプラグインで考えてみますと、このプラグインを使うには project/assembly.sbt に

addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.11.1")

と書いて、build.sbt に

import AssemblyKeys._

assemblySettings

みたいに書く必要があるのですが、AutoPluginにした場合には後半のbuild.sbtの記述が不要になります。

2は、たとえば Playの機能を拡張するようなsbtプラグインを作った場合、まずPlayのsbtプラグインが読み込まれることを期待すると思いますが、その依存関係を記述することができます。 それで今までの場合、ちゃんとプラグインを読み込まないと、Keyが見つかりませんみたいなわかりづらいスタックトレースが出ると思いますが、AutoPluginを使った場合はわかりやすいエラーメッセージを表示したりしてくれるらしいです。 設定も正しい順番で読み込んでくれるらしいです。

3は、設定が自動的に読み込まれるということはマルチプロジェクトなんかの場合、プロジェクトごとの設定はどうなるんだとなると思いますが、

Project.plugins(Web && Javascript && !MyPlugin)
// or
Project.addPlugins(Web, Javascript).disablePlugins(MyPlugin)

みたいにどのプラグインを使うか使わないかの設定ができるようになるらしいです。

詳しくは以下に書いてあります。

User Stories: AutoPlugins · sbt/sbt Wiki

sbt/main/src/main/scala/sbt/Plugins.scala at 0.13 · sbt/sbt

サンプルプラグイン

hexx/sbt-list-assets-plugin

sbt-webのAssetファイルを全部表示するだけのシンプルなプラグインです。既存のプラグインへの依存も書けるのかと思ってsbt-webを使ってみたのですが、それは書けないようです。

既存の書き方

// normal/src/main/scala
import sbt._
import sbt.Keys._

import com.typesafe.sbt.web.SbtWebPlugin.WebKeys

object SbtListAssetsPlugin extends Plugin {
  object ListAssetsKeys {
    val listAssets = taskKey[Unit]("List all assets.")
  }

  import WebKeys._
  import ListAssetsKeys._

  lazy val listAssetsSettings: Seq[Setting[_]] = Seq(
    listAssets := (unmanagedSources in Assets).value foreach (f => streams.value.log.info(f.getName()))
  )
}

こういう感じが今までの簡単なsbtプラグイン書き方だと思います。sbt.Pluginを継承して、プラグインの設定(listAssetsSettings)を書いて、他のプラグインに依存する場合はKeyを参照するだけという感じだと思います。

使い方はproject/plugins.sbtなどでプラグインを読み込んで、build.sbtに以下のような設定を書くと

// normal-tester/build.sbt
webSettings

listAssetsSettings

sbtのプロンプトでlistAssetsというコマンドが使えるようになります。

AutoPluginを使った書き方

// auto/src/main/scala
import sbt._
import sbt.Keys._

import com.typesafe.sbt.web.SbtWebPlugin.WebKeys

object SbtListAssetsPlugin extends AutoPlugin {
  // def select = Web
  def select = Plugins.empty

  object ListAssetsKeys {
    val listAssets = taskKey[Unit]("List all assets.")
  }

  import WebKeys._
  import ListAssetsKeys._

  override def projectSettings = SbtWebPlugin.webSettings ++ Seq(
    listAssets := (unmanagedSources in Assets).value foreach (f => streams.value.log.info(f.getName()))
  )
}

AutoPluginを使う場合でも、そんなには変わらないです。 def selectで他のプラグインへの依存関係を書くのと、projectSettingsというメソッドをオーバーライドして設定を記述するくらいです。

sbt-webプラグインがAutoPluginだったらdef select = Webみたいに書くと思うのですが、まだそうではないのでPlugins.emptyを指定します。

使い方はbuild.sbtに

// auto-test/build.sbt
webSettings

とAutoPluginではないsbt-webの設定を書くだけです。今回作ったプラグインの設定は書く必要ありません。

まとめ

AutoPluginは単独で使ってもそんなに嬉しくないと思うので、微妙なサンプルになった気はしますが、既存のsbtプラグインをAutoPlugin化するのは簡単だと思います。 sbtのソースを読む限りAutoPluginに全面的に以降するらしいので、AutoPlugin化は必須だと思われます。

問題は既存のsbtプラグインを依存関係に指定できないので、sbt 0.13.2 が正式リリースされて他のプラグインがAutoPluginに対応されるのを待って、自分のプラグインの依存関係を記述するということになることですね。

sbt 0.13.2はこの間のインクリメンタル・コンパイルの改善といい、マイナーバージョンアップとは思えない大規模な変更が入ってますね。 でも、これで一応0.13.2に対する機能の追加は終わりらしいので、あとはドキュメント変更とリリース待ちですね。