sbt plugin作成時のデバック、テスト方法

赤坂Scalaに初めて行って来ました。

http://atnd.org/events/32263

そこで話してきたことをちょっと簡単に。

参加したメンバーの人は、sbt pluginちょっとは作った経験はあるみたいですが、テストやデバック方法あまり知らないようなので、話してきました。


まず
「どうデバックするか?」
という話題になって、
「publish-localして試してる」
という話がでました。もちろんそれでもいいのですが、キャッシュとか関係して面倒*1だし、それよりもちょっとだけ便利かもしれない方法の紹介です。

勉強会中にtweetしましたが

sbtにはgithubを直接参照できる機能があります。上記のyoshioriさんの記事は、日本語での簡単な説明ですが、細かいことは書いてないので、正式な仕様知りたい人は sbtの公式のwiki をちゃんと見てください。 sbtのドキュメントはgithubのwikiからscala-sbt.orgに移りました!
そして、これの

uri("git://github.com/foo/bar.git")

の部分をuriではなく以下のようにfileとすることにより

file("/foo/bar")

ローカルのあるディレクトリにあるプロジェクトを参照することも可能になります。
自分は、sbt pluginをデバックする場合この機能を使っています。ライブラリとして他のprojectを参照する場合は上記のyoshioriさんの記事のように、project/Build.scala に書きますが、pluginを参照する場合はproject/Build.scla に書くのではなく、 project/project/build.scala に書きます。*2

今回のxsbt-scalag-pluginをデバックする場合なら、以下のようなディレクトリ構成になります。ここでは仮にplugin-debugというディレクトリを作成しています。このディレクトリ自体は、対象となるxsbt-scalag-pluginの中ではなく、全く関係ない別の場所に作成するべきだと思います

plugin-debug/
├── build.sbt
└── project
    ├── plugins.scala
    └── project
        └── build.scala

build.sbtとproject/plugins.scalaは、xsbt-scalag-pluginのREADMEのとおりのものを用意します。
そしてproject/plugins.sbtにaddSbtPluginの記述は書かずに
代わりに、project/projectの下にscalaファイルを用意します(ファイル名自体はなんでもよい)

import sbt._

object build extends Build {
  lazy val root = Project("plugins", file(".")) dependsOn(
    // ローカルのxsbt-scalag-pluginがあるディレクトリを絶対パスもしくは相対パスで指定する
    file("/foo/bar/xsbt-scalag-plugin") 
  )
}

こうしたあとに、plugin-debugディレクトリ直下でsbtを立ち上げると、「ローカルに置いてあるxsbt-scalag-plugin」が参照できて、addSbtPluginしたのと同じ状態になっているはずで、よってgコマンドなどが有効になっているはずです。さらに詳細な正式な仕様はsbtのwikiの以下のページなどを参考にしてください(説明しきれていない、もうちょっと細かい機能もあるので)
sbtのドキュメントはgithubのwikiからscala-sbt.orgに移りました!
https://github.com/harrah/xsbt/wiki/Plugins
https://github.com/harrah/xsbt/wiki/Full-Configuration

publish-localの方法だと、

  1. xsbt-scalag-pluginを修正
  2. publish-localする
  3. デバック用のprojectを立ち上げ直し(このあたりでキャッシュにハマる場合が?)

となりますが、上記の「ローカルのものを直接参照する」方法ならば

  1. xsbt-scalag-pluginを修正
  2. デバック用のprojectを立ち上げ直し

だけですむはずです。ローカルにpublishしないので、ローカルリポジトリが開発途中のpluginで汚れなくてすみますし、publish-localしない分だけちょっとだけ面倒が減ります(ほんとにちょっとだけですが)

自分はこうやってデバックすることがたまにあるので、上記の方法を勉強会で説明してきました。もっと工夫するべき点とか、いい方法あったら教えて下さい。



あと、そもそも上記のようにデバックするのではなく、scripted testを書きましょうという話があります。勉強会中に自分はテストを(一部のみですが)書きました。

https://github.com/seratch/xsbt-scalag-plugin/pull/4/files

コマンドを実行してファイルが生成されているかを確かめる程度しかしていません。あと、勉強会中にtweetしましたが

複数versionのsbtに対応させようとすると、scripted pluginへの依存をちょっと複雑に書くか、sbt-cross-buildingのpluginを使わないといけないので気をつけましょう

https://github.com/jrudolph/sbt-cross-building

scripted testの書き方については下記の eed3si9n さんの説明がとてもわかり易いので、それ読んでください。

http://eed3si9n.com/ja/testing-sbt-plugins

scripted testは外部DSLになっていて、最初はちょっとだけ覚えることあって面倒ですが、慣れて使いこなせばとても便利なので、sbt pluginを書いたときには、できるだけscripted testを書くようにしましょう。

*1:そういう場合のために、ivyのキャッシュを消すsbt pluginがある https://github.com/sbt/sbt-dirty-money

*2:ちなみにライブラリを参照する場合も、プラグインを参照する場合も .scala の拡張子になっていて、ファイルの置き場所さえ合っていれば、ファイル名はなんでもよいです