sbt(simple-build-tool) のカスタムアクションでScalaのビルドを制御する

sbt の導入については
blog1.mammb.com
を参照してください。

カスタムアクション

sbt のアクション(compile, test, run など)は、sbt.DefaultProject.scala という Scala コードにて定義されています(実際には BasicScalaProject という抽象クラス)。この DefaultProject を継承したクラスを作ることで簡単に sbt のカスタマイズが可能です。
各プロジェクトディレクトリ中のproject/build ディレクトリに sbt.DefaultProject を継承したクラス(sbt.Project を実装したクラス)を作成して sbt コンソールからreload するだけです。

sbt のアクション実装

例えば compole アクションは sbt/DefaultProject.scala 内の BasicScalaProject 抽象クラスに以下の様に定義されています。

class DefaultProject(val info: ProjectInfo) extends BasicScalaProject
    with MavenStyleScalaPaths

abstract class BasicScalaProject extends ScalaProject
    with BasicDependencyProject with ScalaPaths {
  protected def compileAction = task { doCompile(mainCompileConditional) }
      describedAs MainCompileDescription
  lazy val compile = compileAction
}

sbt コンソールから compile とすると上記対応メソッドが実行されることになります。

カスタムアクションの追加

ここでは Hello!! と出力するだけの簡単なアクションを追加してみます。
project/build/ ディレクトリに MyProject.scala を以下の内容で作成します。

import sbt._

class MyProject(info: ProjectInfo) extends DefaultProject(info) {
  lazy val hello = task { println("Hello!!"); None }
}

hello というアクションを追加しました。

変更の反映

作成した hello アクションを有効にするため、sbt コンソールから reload を行います。

> reload
[info] Recompiling project definition...
[info] 	  Source analysis: 1 new/modified, 0 indirectly invalidated, 1 removed.
[info] Building project etc9 1.0 against Scala 2.8.0
[info]    using MyProject with sbt 0.7.4 and Scala 2.7.7

MyProject が反映されていることが分かります。

アクションの実行

作成した hello アクションを実行してみます。

> hello 
[info] 
[info] == hello ==
Hello!!
[info] == hello ==
[success] Successful.

作成したアクションが実行され、「Hello!!」 の出力が得られました。

アクションの作成

新しいアクションは lazy val とし、Task インスタンスを構築することで作成します。

lazy val print = task { log.info("This is a test."); None }

sbt は val として定義されたタスクをリフレクションにより識別します。アクション名は testCompile というキャメルケースの場合ハイフン区切りの test-compile として扱われます。
task の戻り値の None はタスクが正常終了した場合、エラーとなった場合は Some にエラーメッセージを入れて戻します。

タスクの依存関係

タスク間の依存関係は、dependsOn にて以下の様に記述します。

lazy val jars = task { ... } dependsOn(compile, doc) describedAs("Package classes and API docs.")
lazy val compile = ...
lazy val doc = ...

また、describedAs としてコメントを記述できます。

デフォルトアクションの拡張

BasicScalaProject でのアクションの定義はおおむね以下のように定義されています。(package は予約語なので「`」が必要)

lazy val `package` = packageAction
def packageAction = packageTask(packagePaths, jarPath, packageOptions).dependsOn(compile) ...
def packageTask(sources: PathFinder, outputDirectory: Path, jarName: => String, options: => Seq[PackageOption]): Task = task { ... }


サブクラスで以下のようにオーバーライドすることでアクションを拡張できます。

override def packageAction = task { createCustomFile() } dependsOn(compile) describedAs("Creates a war file.")

依存関係の変更だけであれば、

lazy val preprocess = task { ... }
override def compileAction = super.compileAction dependsOn(preprocess)

とできます。

カスタムアクションの作成

カスタムアクションとして必要となるファイル処理は FileTasks トレイトのメソッドなどが利用できます。また、sbt にはエラーハンドリング用に sbt.Control、ファイルのユーティリティとして sbt.FileUtilities など便利なクラスが提供されています。これらを使うことで簡単にアクションをカスタマイズすることができます。
project/build/lib に必要な外部ライブラリを置く事で、カスタムアクションから利用することもできます。