sbtでtest-onlyのような「補完が効いて、なおかつ補完の候補がキャッシュされるInputTask」を自作する方法

みなさん、sbtのtest-onlyとか使ってますか?
http://www.scala-sbt.org/0.12.3/docs/Detailed-Topics/Testing#test-only
簡単にtest-onlyの動作を説明すると

  • あたえられた名前のclassのテストのみ実行*1
  • テストコードのコンパイルがされていれば、それらのテストのclassの名前が補完候補にでてくる
  • それらの補完候補は、*2projectをreloadしたりしてもずっと有効!

など、とても便利です。され、これらの

  • タブで補完がされる
  • 補完候補が動的に変わる
  • 補完候補がキャッシュされる

という仕組みは、test-onlyのためだけに特別に用意されているのではなく、すごく汎用的になっていて誰でも使えるようになっています。

というわけで、似たようなサンプルを作ってみました。今回はseratchさんのscalikejdbcのサンプルプロジェクトを利用しました。
コードはこれ↓

https://github.com/xuwei-k/devteam-app/commit/5bb642b306d5c4
https://github.com/xuwei-k/devteam-app/blob/5bb642b3/project/DB.scala

動作としては

  • "show-describe テーブル名"
  • "show-column-names テーブル名"
  • そして、そのテーブル名が補完が効く
  • テーブル名の補完候補は"table-names"というTaskで、取得、更新

です。

以下ひたすら実装の細かい説明

  • 公式にドキュメントはあるけど、このあたり全部載ってるわけではないので、こういう結構凝ったものを作る場合はソース読もう
  • なぜprojectをreloadしてもキャッシュできるか*3というと、sbtがsbinaryというscalaで作られたシンプルなシリアライゼーションライブラリに依存しているからです。
    • https://github.com/harrah/sbinary
    • sbinaryの作者はsbt作者と同じ
    • "tableNames <<= tableNames storeAs tableNames"とかやってるのがキャッシュ設定してる
    • sbtのKeyの仕組みと統合されていて使いやすい
  • 補完のための独自Parserについては以前書いた
  • project/project/plugins.sbt にscalikejdbcの依存を書いてもできるが、それはせずに本体側をリフレクション経由で呼び出ししてる
  • sbt側は現状の0.12系はScala2.9で、本体は2.10というようにScalaのversionが異なる場合注意が必要
    • 互換性が無いので戻り値にScalaのclass(例えばListとか)を使うべきではない
    • java.lang.StringやArrayなどJavaのclassのみを使う
  • 本当は、本体に作ったDatabase.scalaは(開発時のみで実際運用時に必要ないなら)専用プロジェクトに分けるべき
  • サンプルなのでDatabase.scalaの設定はベタ書きで色々雑です

最後に実行した画面を貼り付けておきます

*1:スペース区切りで複数指定などのオプションあり

*2:cleanしない限り

*3:つまりメモリ上に保持してるわけではない