酢豚が美味しかった。 by HIRAOKA,Yasunobu

初心者ぺちぱーがGitHubでScalaレッスンを始めたぞ。今回はScalaの標準的なビルドツールであるsbtの使い方の続き。クロスビルドとか、設定に使う演算子みたいなメソッドとか、継続ビルド(continuous build)とか、そのへんの話。

Cross-building and library dependencies setting

前回はほぼマニュアル通りの設定ファイルを書いてみた。これにもう少し設定を追加してみよう。

Getting-Started/Cross-buildingによると、crossScalaVersionsを指定しておくと、クロスビルドできるようだ。Scalaカンファレンスで聞いた話だと、バイナリーバージョンが違うと、ライブラリが使用できないらしいので、もしGitHubなんかでライブラリ開発をするような場合は、クロスビルドしておかないと対応可能なはずのバージョンで使用出来ない、なんてことになってしまうのかもしれない。

2.9.0以降のバージョンとScalaTestをbuild.sbtに追加してみよう。

build.sbt
name := "hello"
version := "1.0"
scalaVersion := "2.10.1"
crossScalaVersions := Seq(
"2.9.0",
"2.9.1",
"2.9.2",
"2.9.3",
//"2.10.0",
"2.10.1"
)
libraryDependencies <+= scalaVersion(v => v match {
case "2.10.1" => "org.scalatest" % "scalatest_2.10" % "1.9.1" % "test"
case _ => "org.scalatest" %% "scalatest" % "1.9.1" % "test"
})

2.10.0を外しているのは、コンパイル後のコードがtarget/scala-2.10というディレクトリに生成されるから、という理由。2.10以降でバイナリーバージョンが同じになったから? 2.9までは、target/以下にscala-2.9.2とかscala-2.9.1みたいなディレクトリに分かれている。

同じディレクトリに生成されるので、2.10.0と2.10.1を設定していると、+ compileしたときに、ファイル変更が無くても毎回コンパイルされてしまう。+ testでも同じく、毎回コンパイルされた後にテストが実行される。2.10.1だけにすると、ファイル変更がなければコンパイルは一度だけになった。

$ tree
.
├── build.properties
├── build.sbt
├── project
│   └── target
│   └── config-classes
├── src
│   ├── main
│   │   ├── java
│   │   ├── resources
│   │   └── scala
│   │   └── jp
│   │   └── satooshi
│   │   └── hello
│   │   └── Hello.scala
│   └── test
│   ├── java
│   ├── resources
│   └── scala
│   └── jp
│   └── satooshi
│   └── hello
│   └── HelloTest.scala
└── target
├── resolution-cache
│   ├── hello
│   │   ├── hello_2.10
│   │   │   └── 1.0
│   │   │   ├── resolved.xml.properties
│   │   │   └── resolved.xml.xml
│   │   ├── hello_2.9.0
│   │   │   └── 1.0
│   │   │   ├── resolved.xml.properties
│   │   │   └── resolved.xml.xml
│   │   ├── hello_2.9.1
│   │   │   └── 1.0
│   │   │   ...
│   │   ├── hello_2.9.2
│   │   │   └── 1.0
│   │   │   ...
│   │   └── hello_2.9.3
│   │   └── 1.0
│   │   ...
├── scala-2.10
│   ├── cache
│   │   └── default-ea94fa
│   │   ├── compile
│   │   │   ├── copy-resources
│   │   │   ├── for_doc
│   │   │   │   └── scala
│   │   │   │   ├── inputs
│   │   │   │   └── output
│   │   │   └── inc_compile
│   │   ├── global
│   │   │   └── update
│   │   │   ├── inputs
│   │   │   └── output
│   │   └── test
│   │   ├── copy-resources
│   │   ├── inc_compile
│   │   └── succeeded_tests
│   ├── classes
│   │   └── jp
│   │   └── satooshi
│   │   └── hello
│   │   ├── Hello$.class
│   │   └── Hello.class
│   └── test-classes
│   └── jp
│   └── satooshi
│   └── hello
│   ├── HelloTest$$anonfun$1.class
│   └── HelloTest.class
├── scala-2.9.0
│   ├── cache
│   │   └── default-ea94fa
│   │   ├── compile
│   │   │   ├── copy-resources
│   │   │   └── inc_compile
│   │   ├── global
│   │   │   └── update
│   │   │   ├── inputs
│   │   │   └── output
│   │   └── test
│   │   ├── copy-resources
│   │   ├── inc_compile
│   │   └── succeeded_tests
│   ├── classes
│   │   └── jp
│   │   └── satooshi
│   │   └── hello
│   │   ├── Hello$.class
│   │   └── Hello.class
│   └── test-classes
│   └── jp
│   └── satooshi
│   └── hello
│   ├── HelloTest$$anonfun$1.class
│   └── HelloTest.class
├── scala-2.9.1
...
├── scala-2.9.2
...

なので、2.10系は最新版だけいれておけば良さそうな気がする。

libraryDependenciesには見慣れない演算子みたいなメソッドが使われている。これは後で説明。この設定の意味は、scalaVersionに応じて、使用するライブラリ(ここではScalaTest)を変更する、というものだ。実は、ScalaTestの場合、デフォルトケースだけで設定しても問題なかったが、設定方法は2.10かどうかで分けて書いてあったので、これにならって設定した。

では、クロスビルドしてみる。タスクの前に+を付けるとcrossScalaVersionsで設定した各バージョンでタスクが実行されるようだ。

+ compileタスク実行
> + compile
Setting version to 2.9.0
[info] Set current project to hello (in build file:/Users/satoshi/prj/work/scala/hello/)
[info] Updating {file:/Users/satoshi/prj/work/scala/hello/}default-ea94fa...
[info] Resolving org.sonatype.oss#oss-parent;7 ...
[info] downloading http://repo1.maven.org/maven2/org/scalatest/scalatest_2.9.0/1.9.1/scalatest_2.9.0-1.9.1.jar ...
[info] [SUCCESSFUL ] org.scalatest#scalatest_2.9.0;1.9.1!scalatest_2.9.0.jar (10447ms)
[info] Done updating.
[success] Total time: 14 s, completed 2013/03/28 16:17:14
Setting version to 2.9.1
[info] Set current project to hello (in build file:/Users/satoshi/prj/work/scala/hello/)
[info] Updating {file:/Users/satoshi/prj/work/scala/hello/}default-ea94fa...
[info] Resolving org.sonatype.oss#oss-parent;7 ...
[info] downloading http://repo1.maven.org/maven2/org/scalatest/scalatest_2.9.1/1.9.1/scalatest_2.9.1-1.9.1.jar ...
[info] [SUCCESSFUL ] org.scalatest#scalatest_2.9.1;1.9.1!scalatest_2.9.1.jar (6783ms)
[info] Done updating.
[success] Total time: 10 s, completed 2013/03/28 16:17:24
Setting version to 2.9.2
[info] Set current project to hello (in build file:/Users/satoshi/prj/work/scala/hello/)
[info] Updating {file:/Users/satoshi/prj/work/scala/hello/}default-ea94fa...
[info] Resolving org.sonatype.oss#oss-parent;7 ...
[info] downloading http://repo1.maven.org/maven2/org/scalatest/scalatest_2.9.2/1.9.1/scalatest_2.9.2-1.9.1.jar ...
[info] [SUCCESSFUL ] org.scalatest#scalatest_2.9.2;1.9.1!scalatest_2.9.2.jar (6780ms)
[info] Done updating.
[success] Total time: 9 s, completed 2013/03/28 16:17:33
Setting version to 2.9.3
[info] Set current project to hello (in build file:/Users/satoshi/prj/work/scala/hello/)
[info] Updating {file:/Users/satoshi/prj/work/scala/hello/}default-ea94fa...
[info] Resolving org.sonatype.oss#oss-parent;7 ...
[info] downloading http://repo1.maven.org/maven2/org/scalatest/scalatest_2.9.3/1.9.1/scalatest_2.9.3-1.9.1.jar ...
[info] [SUCCESSFUL ] org.scalatest#scalatest_2.9.3;1.9.1!scalatest_2.9.3.jar (11996ms)
[info] Done updating.
[success] Total time: 17 s, completed 2013/03/28 16:17:50
Setting version to 2.10.1
[info] Set current project to hello (in build file:/Users/satoshi/prj/work/scala/hello/)
[success] Total time: 0 s, completed 2013/03/28 16:17:50
Setting version to 2.10.1
[info] Set current project to hello (in build file:/Users/satoshi/prj/work/scala/hello/)
+ runタスク実行
> + run
Setting version to 2.9.0
[info] Set current project to hello (in build file:/Users/satoshi/prj/work/scala/hello/)
[info] Running jp.satooshi.hello.Hello
へろー
[success] Total time: 0 s, completed 2013/03/28 16:17:59
Setting version to 2.9.1
[info] Set current project to hello (in build file:/Users/satoshi/prj/work/scala/hello/)
[info] Running jp.satooshi.hello.Hello
へろー
[success] Total time: 0 s, completed 2013/03/28 16:18:00
Setting version to 2.9.2
[info] Set current project to hello (in build file:/Users/satoshi/prj/work/scala/hello/)
[info] Running jp.satooshi.hello.Hello
へろー
[success] Total time: 0 s, completed 2013/03/28 16:18:00
Setting version to 2.9.3
[info] Set current project to hello (in build file:/Users/satoshi/prj/work/scala/hello/)
[info] Running jp.satooshi.hello.Hello
へろー
[success] Total time: 0 s, completed 2013/03/28 16:18:00
Setting version to 2.10.1
[info] Set current project to hello (in build file:/Users/satoshi/prj/work/scala/hello/)
[info] Running jp.satooshi.hello.Hello
へろー
[success] Total time: 0 s, completed 2013/03/28 16:18:00
Setting version to 2.10.1
[info] Set current project to hello (in build file:/Users/satoshi/prj/work/scala/hello/)
+ testタスク実行
> + test
Setting version to 2.9.0
[info] Set current project to hello (in build file:/Users/satoshi/prj/work/scala/hello/)
へろー
[info] HelloTest:
[info] - Hello should run main
[info] Passed: : Total 1, Failed 0, Errors 0, Passed 1, Skipped 0
[success] Total time: 0 s, completed 2013/03/28 16:18:04
Setting version to 2.9.1
[info] Set current project to hello (in build file:/Users/satoshi/prj/work/scala/hello/)
へろー
[info] HelloTest:
[info] - Hello should run main
[info] Passed: : Total 1, Failed 0, Errors 0, Passed 1, Skipped 0
[success] Total time: 0 s, completed 2013/03/28 16:18:04
Setting version to 2.9.2
[info] Set current project to hello (in build file:/Users/satoshi/prj/work/scala/hello/)
へろー
[info] HelloTest:
[info] - Hello should run main
[info] Passed: : Total 1, Failed 0, Errors 0, Passed 1, Skipped 0
[success] Total time: 0 s, completed 2013/03/28 16:18:04
Setting version to 2.9.3
[info] Set current project to hello (in build file:/Users/satoshi/prj/work/scala/hello/)
へろー
[info] HelloTest:
[info] - Hello should run main
[info] Passed: : Total 1, Failed 0, Errors 0, Passed 1, Skipped 0
[success] Total time: 0 s, completed 2013/03/28 16:18:05
Setting version to 2.10.1
[info] Set current project to hello (in build file:/Users/satoshi/prj/work/scala/hello/)
へろー
[info] HelloTest:
[info] - Hello should run main
[info] Passed: : Total 1, Failed 0, Errors 0, Passed 1, Skipped 0
[success] Total time: 0 s, completed 2013/03/28 16:18:05
Setting version to 2.10.1
[info] Set current project to hello (in build file:/Users/satoshi/prj/work/scala/hello/)
+ docタスク実行
> + doc
Setting version to 2.9.0
[info] Set current project to hello (in build file:/Users/satoshi/prj/work/scala/hello/)
[info] Generating Scala API documentation for main sources to /Users/satoshi/prj/work/scala/hello/target/scala-2.9.0/api...
model contains 2 documentable templates
[info] Scala API documentation generation successful.
[success] Total time: 2 s, completed 2013/03/28 16:29:08
Setting version to 2.9.1
[info] Set current project to hello (in build file:/Users/satoshi/prj/work/scala/hello/)
[info] Generating Scala API documentation for main sources to /Users/satoshi/prj/work/scala/hello/target/scala-2.9.1/api...
model contains 2 documentable templates
[info] Scala API documentation generation successful.
[success] Total time: 1 s, completed 2013/03/28 16:29:09
Setting version to 2.9.2
[info] Set current project to hello (in build file:/Users/satoshi/prj/work/scala/hello/)
[info] Generating Scala API documentation for main sources to /Users/satoshi/prj/work/scala/hello/target/scala-2.9.2/api...
model contains 2 documentable templates
[info] Scala API documentation generation successful.
[success] Total time: 1 s, completed 2013/03/28 16:29:10
Setting version to 2.9.3
[info] Set current project to hello (in build file:/Users/satoshi/prj/work/scala/hello/)
[info] Generating Scala API documentation for main sources to /Users/satoshi/prj/work/scala/hello/target/scala-2.9.3/api...
model contains 2 documentable templates
[info] Scala API documentation generation successful.
[success] Total time: 1 s, completed 2013/03/28 16:29:11
Setting version to 2.10.1
[info] Set current project to hello (in build file:/Users/satoshi/prj/work/scala/hello/)
[info] Generating Scala API documentation for main sources to /Users/satoshi/prj/work/scala/hello/target/scala-2.10/api...
model contains 2 documentable templates
[info] Scala API documentation generation successful.
[success] Total time: 1 s, completed 2013/03/28 16:29:11
Setting version to 2.10.1
[info] Set current project to hello (in build file:/Users/satoshi/prj/work/scala/hello/)

Setting manner

More Kinds of Settingと、harrah / xsbtには、設定に使えるメソッドの説明が記載してあった。

Refresher

:= 上書き

新しい設定値を設定する。または、既存の設定値を上書きする。

name := "My Project"

Appending to previous values

+= 1つ追加(他の設定値は使用しない) ++= Seqを追加(他の設定値は使用しない)

既存の設定に設定値を追加する。上書きされない。

libraryDependencies += "junit" % "junit" % "4.8" % "test"
libraryDependencies ++= Seq(
"org.scala-tools.testing" %% "scalacheck" % "1.9" % "test",
"org.scala-tools.testing" %% "specs" % "1.6.8" % "test"
)

Appending with dependencies

<+= 1つ追加(他の設定値を使用) <++= Seqを追加(他の設定値を使用)

他の設定値を使用して、設定値を追加する。上書きされない。依存する設定値は1つでも複数でもいいらしい。

cleanFiles <+= (name) { n => file("coverage-report-" + n + ".txt") }
libraryDependencies <+= scalaVersion( "org.scala-lang" % "scala-compiler" % _ )
libraryDependencies <++= scalaVersion { sv =>
("org.scala-lang" % "scala-compiler" % sv) ::
("org.scala-lang" % "scala-swing" % sv) ::
Nil
}

Transforming a value

~= 現在の設定値を変換

scalacOptions in Compile ~= { (options: Seq[String]) =>
options filterNot ( _ startsWith "-Y" )
}

Computing a value based on other keys' values

<<= 他の複数の設定値を使用して、設定を変更する

Getting-Started/More Kinds of Settingを読むと、依存する複数の設定値を使用して、設定値を決めることができるようだ。しかし、harrah / xsbtを読むと、他の全てのメソッドはこの<<=で書けるらしい。新規の設定値の場合はどうなるんだろうか。このへんがまだよく分かってない。

libraryDependencies <<= libraryDependencies apply { (deps: Seq[ModuleID]) =>
// Note that :+ is a method on Seq that appends a single value
deps :+ ("junit" % "junit" % "4.8" % "test")
}

Continuous build

Continuous build and testには~をタスクの前に付けると、ファイル変更を検知してタスクを実行できるという記載がある。

To speed up your edit-compile-test cycle, you can ask sbt to automatically recompile or run tests whenever you save a source file.

Make a command run when one or more source files change by prefixing the command with ~. For example, in interactive mode try:

> ~ compile

Press enter to stop watching for changes.

You can use the ~ prefix with either interactive mode or batch mode.

なので、ファイル変更の度にテストを実行する場合は、~ testとなる。クロスコンパイルする場合は、~ + compileでいけた。

> ~ test
> ~ + compile

Conclustion

これでsbtを使って複数バージョンでテストしたり、依存ライブラリを管理できるようになったんじゃないか!?やったね!まとめ!

  • クロスビルドの設定と実行方法
  • 依存ライブラリの設定
  • ファイル変更を監視して特定のタスクを実行する方法

Reference