成らぬは人の為さぬなりけり

エンジニアライフをエンジョイする為のブログ

Play2.0でsbtの独自タスクを定義する

sbtの独自タスク追加方法は、ググればちらほら出てくるのですが、
Playを使ったsbtの独自タスクの定義方法がよくわからず、
ちょっと苦労したので、メモします。
(苦労した、という程の事でもないのですが。。。)

今回の環境

OS : Mac OS X 10.7.5
Scala : 2.9.1
sbt : 0.11.3
Play : 2.0.4

今日の流れ

  • sbtでの独自タスク定義方法
  • playでの独自タスク定義方法

sbtで独自タスク

sbtで環境依存ファイルを変更するためのカスタムタスクを書いてみたよ - ブログなんだよもん
(こちらを参考にさせて頂きました)
まず、sbtオンリーの場合は、上記サイトにあるように、
こんな感じで書きます。

project/Build.scala

import sbt._

object ProjectBuild extends Build {
  val hogeKey = TaskKey[Unit]("hoge", "hoge task")

  val hoge = hogeKey := {
    println("hoge")
  }

  lazy val root = Project(
    id = "my task",
    base = file("."),
    settings = Defaults.defaultSettings ++ Seq(hoge)
  )
}
  1. TestKeyでkeyと説明を定義
  2. :=メソッドを使ってタスクを定義
  3. Projectのsettingsへ追加

という流れになります。

実行してみます。

% sbt hoge
[info] Loading global plugins from /Users/xxx/.sbt/plugins
[info] Loading project definition from /Users/xxx/workspace/scala/scala_practice/project
[info] Set current project to my task (in build file:/Users/xxx/workspace/scala/scala_practice/)
hoge
[success] Total time: 0 s, completed 2012/12/03 22:26:54

ちゃんとhogeと出力されました。

Playプロジェクトでsbt独自タスクを定義する

PlayのBuild.scalaはこんな感じです。


project/Build.scala

import sbt._
import Keys._
import PlayProject._

object ApplicationBuild extends Build {

    val appName         = "hoge"
    val appVersion      = "1.0"

    val appDependencies = Seq(
      "org.squeryl" %% "squeryl" % "0.9.5" withSources(),
      "mysql" % "mysql-connector-java" % "5.1.18"
    )

  lazy val main = PlayProject(appName, appVersion, appDependencies, mainLang = SCALA).settings(
    resolvers += "t2v.jp repo" at "http://www.t2v.jp/maven-repo/"
  )
}

PlayではProjectがPlayProjectになっています。


PlayProjectのapplyメソッドを見てみると。

package sbt
object PlayProject extends java.lang.Object with sbt.Plugin with sbt.PlayExceptions with sbt.PlayKeys with sbt.PlayReloader with sbt.PlayCommands with sbt.PlaySettings with scala.ScalaObject {
  def apply(name : scala.Predef.String, applicationVersion : scala.Predef.String = { /* compiled code */ }, dependencies : scala.Seq[sbt.ModuleID] = { /* compiled code */ }, path : sbt.File = { /* compiled code */ }, mainLang : scala.Predef.String = { /* compiled code */ }, settings : => scala.Seq[sbt.Setting[_]] = { /* compiled code */ }) : sbt.Project = { /* compiled code */ }
}

「settings」というパラメータがある!
というわけで、以下のようにしてみました。

import sbt._
import Keys._
import PlayProject._

object ApplicationBuild extends Build {

    val appName         = "ripple"
    val appVersion      = "1.0"

    val appDependencies = Seq(
      "org.squeryl" %% "squeryl" % "0.9.5" withSources(),
      "mysql" % "mysql-connector-java" % "5.1.18",
      "jp.t2v" % "play20.auth_2.9.1" % "0.3" withSources()
    )

  lazy val main = PlayProject(appName, appVersion, appDependencies, mainLang = SCALA, settings = Seq(Tasks.hogeTask)).settings(
    resolvers += "t2v.jp repo" at "http://www.t2v.jp/maven-repo/"
  )

  object Tasks {
    val hogeTaskKey = TaskKey[Unit]("hoge", "test task")
    lazy val hogeTask = hogeTaskKey := {
      println("hoge")
    }
  }
}

で、playコマンド実行。
エラー、、、

...
[error] References to undefined settings: 
...

うーむ。
エラーメッセージからなにがいけなかったのか、よくわかりません。
と、いうわけで、型をちゃんと調べることにしました。


[追記]
ここの原因は、id:xuweiさんからご指摘頂き、

Defaults.defaultSettings ++ Seq(Tasks.hogeTask)

Defaults.defaultSettingsを忘れている事だと分かりました。

そもそも:=の型は??
    def :=(value : => S) : sbt.Project.Setting[sbt.Task[S]] = { /* compiled code */ }
PlayProjectのsettingsの型は?
settings : => scala.Seq[sbt.Setting[_]]


なるほど、そもそも型が違うようです。
[訂正]id:xuweiさんからのご指摘により、同じ型である事がわかりました。

しかし、よくよく見ると、
Projectにはsettingsというメソッドが用意されている。

Project#settingsの型は?
  def settings(ss : sbt.Project.Setting[_]*) : sbt.Project = { /* compiled code */ }

「sbt.Project.Setting」これだ!

というわけで、結論、以下のようにして解決しました。

import sbt._
import Keys._
import PlayProject._

object ApplicationBuild extends Build {

    val appName         = "hoge"
    val appVersion      = "1.0"

    val appDependencies = Seq(
      "org.squeryl" %% "squeryl" % "0.9.5" withSources(),
      "mysql" % "mysql-connector-java" % "5.1.18",
      "jp.t2v" % "play20.auth_2.9.1" % "0.3" withSources()
    )

  lazy val main = PlayProject(appName, appVersion, appDependencies, mainLang = SCALA).settings(
    resolvers += "t2v.jp repo" at "http://www.t2v.jp/maven-repo/",
    Tasks.hogeTask
  )

  object Tasks {
    val hogeTaskKey = TaskKey[Unit]("hoge", "test task")
    lazy val hogeTask = hogeTaskKey := {
      println("hoge")
    }
  }
}

実行してみます。

% play 
[info] Loading global plugins from /Users/xxx/.sbt/plugins
[info] Loading project definition from /Users/xxx/workspace/ripple/project
[info] Set current project to ripple (in build file:/Users/xxx/workspace/ripple/)
        _               _ 
 _ __ | | __ _ _  _| |
| '_  \| |/ _' | || |_|
|  __/|_|\____|\__ (_)
|_|            |__/ 
             
play! 2.0.4, http://www.playframework.org

> Type "help play" or "license" for more information.
> Type "exit" or use Ctrl+D to leave this console.

[hoge] $ hoge
hoge
[success] Total time: 0 s, completed 2012/12/03 23:02:27

ちゃんと表示されました。

うーん、勉強不足だ、、、すっごい遠回りした。。orz

[追記]
一応、自分のメモとして、訂正前の状態も書き残しましたが、
ちゃんと調べずに、誤った事を記載してしまい申し訳ありませんでした。
今後も、じゃんじゃんご指摘頂けると助かります。