sbt(Simple Build Tool)を試す

scalaで実装するのにこれまで、
Eclipseで手動で環境作っていて、sbtを使っていませんでした。恥ずかしい・・・。

実際そんなに困ってなかった状況で、
ScalaTestをJunitRunnerで実行していて、
どうも空振るテストクラスがあり、Scala-IDEの不具合なのか分からず、
だったらsbtで実行しようと思ったわけです。
また、jenkins導入も考えていて、ビルドツールはどのみち必要なので。

んで、sbtをインストールしようかと思ったら、
なにやらTypesafe Stackというものがあるらしく、
これを使ったほうが楽に環境構築出来そうなので、ちょっと一からやってみることにしました。
ScalaJPのGithubの環境構築のページにもTypesafe Stackインストールするよと書いてあるので。
scala develop environment · scalajp/scalajp.github.com Wiki · GitHub

ちょっとやる前に、自分の想定(やりたい事)をまとめておきます。
1.sbtで作ったプロジェクトのひな形を作る。eclipseにインポート。既存のソースを入れる。
2.sbtでビルド出来るようにする。テストも実行出来るようにする。実装は、Eclipse
3.jettyを使っているので、sbtのプロジェクト構成でも、Eclipseデバッグ出来るようにする。
4.pureなJettyにsbtから作ったWARをデプロイする。

・typesafe-stackのインストール
http://typesafe.com/stack/downloadからダウンロード。
typesafe-stack-2.0.1.exeをダウンロードしました。
Windows7です。
はじめにsbtのインストールが行われます。
次にGiter8のインストールが行われます。Giter8とは・・・2011-12-13
次にScalaのインストールが行われます。
これで終わるんですが、Giter8が見当たらない・・・。
予習してると、g8コマンドで、プロジェクトのテンプレート作るらしいのですが、g8が見つからない。
どこにインストールしたんだ・・・。
とおもったら、Program Filesにありました。sbtのインストール先を変更したので、
Giter8も変わってくれてると思ってました(変更出来無いので)。Giter8は、Program Files固定なんですね。
うーん・・・。固定でいいか。再インストールっと。

・プロジェクトの作成
プロジェクトを作成するディレクトリに移動して、

>g8 typesafehub/scala-sbt
 Scala Project Using sbt

 organization [org.example]: org.hoge
 package [org.example]: org.hoge
 name [Scala Project]: hoge_project
 scala_version [2.9.1]:
 version [0.1-SNAPSHOT]:

 Applied typesafehub/scala-sbt.g8 in hoge_project

プロジェクトのひな形が作成されます。初回なにやら色々ダウンロードされます。
Windows 7の場合、ダウンロードされたファイルは、
C:\Users\770032\.ivy2\cache
に保存されるようです。
作成されたプロジェクトに移動して、sbt runすると、ビルド、実行されます。
作成されたプロジェクトの中に、projectというディレクトリがあるので、
そこにplugins.sbtというファイルを作ります。

addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.0.0")

sbteclipseが、eclipseの.projectなどを作ってくれるようです。
GitHub - sbt/sbteclipse: Plugin for sbt to create Eclipse project definitions
sbt、eclipseEclipseにインポート可能なプロジェクトになります。
Eclipseから、既存プロジェクトをインポートで、さくっとインポート出来ます。
mavenでのresourcesが無いので、
src/main/resources
src/test/resources
を追加しました。

・Build.scala

import sbt._
import sbt.Keys._

object ProjectBuild extends Build {

  lazy val root = Project(
    id = "root",
    base = file("."),
    settings = Project.defaultSettings ++ Seq(
      name := "hoge_project",
      organization := "org.hoge",
      version := "0.1-SNAPSHOT",
      scalaVersion := "2.9.1",
      // add other settings here
      libraryDependencies ++= dependencies
    )
  )
  
  val junit = "junit" % "junit" % "4.10" % "test"
  val scalaTest = "org.scalatest" %% "scalatest" % "1.7.1" % "test"
  lazy val dependencies = Seq(
  		junit,
  		scalaTest
  )
}

これで、TestClassを作って、sbt testを実行すると、テストが実行されます。
ただ、sbtだけを意識するのであれば、これでいいのですが、
この状態だと当然の如く、jUnit、ScalaTestがビルドパスに通ってないので、Eclipse上では、エラーが出ます。
ここで再びsbt eclipseを実行すると、ビルドパスをBuild.scalaから生成してくれます。素敵すぎる・・・。
安全の為には、プロジェクトを一旦クローズするなりしたほうが良いのかもしれません。
と安心してたら、resourcesのソースフォルダが消えてる。
デフォルトだとresourcesは作ってくれないので、手動で作っても、再度sbt eclipse実行すると消えてしまいます。
ですので、resourcesも作ってもらいましょう。
Biuld.scalaにて、

import com.typesafe.sbteclipse.plugin.EclipsePlugin._

上のインポートを追加し、settingに

EclipseKeys.createSrc := EclipseCreateSrc.Default + EclipseCreateSrc.Resource

を含めます。
Using sbteclipse · sbt/sbteclipse Wiki · GitHub
ちゃんと読めって事ですね。
sbt eclipse実行すると、ちゃんとresourcesを作ってくれます。

参考:
This page has moved
http://blob.geishatokyo.com/archives/22401
[Scala]sbtを使ってScalaのプロジェクトをeclipseで編集する | GENDOSU@NET

・Build.scala(Jetty)
sbtで、WebApplicationを実装する場合は、xsbt-web-pluginを使うらしい。
GitHub - aolshevskiy/xsbt-web-plugin: Web app support plugin for XSbt using Jetty Web Server
plugins.sbtに追加します。

addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.0.0")

libraryDependencies <+= sbtVersion(v => "com.github.siasia" %% "xsbt-web-plugin" % (v+"-0.2.11"))

改行しないと、reloadでエラーになります。
[error] Error parsing expression. Ensure that settings are separated by blank lines.
ここでちょっと脱線しますが、
Webの情報見てると、Build.scala、build.sbt、plugins.sbtの役割ってなんなのと今更、疑問が湧きました。
Build.scalaとbuild.sbtの違い。sbt の Build.scala を書く - tototoshi の日記
Build.scalaの方が柔軟に記述が出来るようですね。Build.scalaで設定します。
plugins.sbtは、sbtのプラグインを記述する為の設定ファイルのようです。http://scalajp.github.com/sbt-getting-started-guide-ja/using-plugins/

jetty-webappのjar依存を追加します。

val jetty_webapp = "org.eclipse.jetty" % "jetty-webapp" % "8.1.0.v20120127" % "container"
val jetty_websocket = "org.eclipse.jetty" % "jetty-websocket" % "8.1.0.v20120127" % "compile"
val servlet_api = "javax.servlet" % "javax.servlet-api" % "3.0.1" % "provided"

Jettyのバージョンですが、8.1.2を使うとエラーが出ます。SBT, Jetty and Servlet 3.0 - Stack Overflow
ですので、8.1.0を使うとよいそうです。sbt reload, updateするとだーっと依存してるライブラリがダウンロードされます。
また、
jetty-webappは、scopeをcontainerにする必要があります。container以外を指定するとエラーが出ます。
servlet-apiは、jetty-webappの中で、org.mortbay.jetty#servlet-api;3.0がダウンロードされますが、
containerを指定すると、依存しているライブラリは、ビルドパスに含まれないので、providedで使用します。
web-socketは、コンパイル&デプロイで必要なので、compileとしています。

次に、webappディレクトリを作成します。(誰もが思う、これeclipseコマンドでやってくれないかと)
sbtは、mavenディレクトリ構造が同じなので、
src/main/webapp
を作成します。srcの直下にwebapp作ってる例とかありますが、mavenを踏襲したほうがいいですね。
WEB-INFも作ります。web.xmlも作ります。
container:startで起動するんですけど、jettyの設定をBuild.scalaに記述するのは、loggingとかsslとか正直面倒。
これまでの資産のRunJettyRunの方が楽そう。Eclipseからデバッグすぐ出来るし。
「xsbt-web-plugin使う必要無いんじゃないの?」・・・。

・WAR
ただwarを作るだけなら、packageすればいいんですが、
ちょっとWAR化するのに、excludeしたかったりするので、また今度にします。

最後に自分のBuild.scalaとplugins.sbtです。
Build.scala

import sbt._
import sbt.Keys._
import com.typesafe.sbteclipse.plugin.EclipsePlugin._
import com.github.siasia.WebPlugin.webSettings
import sbtassembly.Plugin._
import AssemblyKeys._

object ProjectBuild extends Build {

  lazy val root = Project(
    id = "root",
    base = file("."),
    settings = Project.defaultSettings ++ webSettings ++ assemblySettings ++ Seq(
      name := "hoge_project",
      organization := "org.hoge",
      version := "0.1-SNAPSHOT",
      scalaVersion := "2.9.1",
      // add other settings here
      libraryDependencies ++= dependencies,
      EclipseKeys.createSrc := EclipseCreateSrc.Default + EclipseCreateSrc.Resource
    )
  )
  
  // test
  val junit = "junit" % "junit" % "4.10" % "test"
  val scalaTest = "org.scalatest" %% "scalatest" % "1.7.1" % "test"
  
  // jetty
  val jetty_webapp = "org.eclipse.jetty" % "jetty-webapp" % "8.1.0.v20120127" % "container"
  val jetty_websocket = "org.eclipse.jetty" % "jetty-websocket" % "8.1.0.v20120127" % "compile"
  val servlet_api = "javax.servlet" % "javax.servlet-api" % "3.0.1" % "provided"
  
  // log
  val slf4j = "org.slf4j" % "slf4j-api" % "1.6.4" % "compile"
  val logback_core = "ch.qos.logback" % "logback-core" % "1.0.1" % "runtime"
  val logback_classic = "ch.qos.logback" % "logback-classic" % "1.0.1" % "runtime"
  
  // h2
  val h2 = "com.h2database" % "h2" % "1.3.166" % "runtime"
  
  // commons
  val commons_io = "commons-io" % "commons-io" % "1.4" % "compile"
  
  // ant
  val ant = "org.apache.ant" % "ant" % "1.8.3" % "compile"
  
  lazy val dependencies = Seq(
  		junit,
  		scalaTest,
  		jetty_webapp,
  		jetty_websocket,
  		servlet_api,
  		h2,
  		slf4j,
  		logback_core,
  		logback_classic,
  		commons_io,
  		ant
  )
  
}

plugins.sbt

addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.0.0")

libraryDependencies <+= sbtVersion(v => "com.github.siasia" %% "xsbt-web-plugin" % (v+"-0.2.11"))

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

resolvers += Resolver.url("sbt-plugin-releases",
  new URL("http://scalasbt.artifactoryonline.com/scalasbt/sbt-plugin-releases/"))(Resolver.ivyStylePatterns)

お疲れ様でした。