目的
sbtをずっと何となく使っていたので一度ちゃんと調べました。
mavenやgradle触ってた人がある日突然sbtを触らなければならなくなった時用のメモ。
バージョンはsbt 0.13.8
ディレクトリ構成
sbtプロジェクトの基本構成は以下の通りです。
maven等を触っていた人にとっては比較的見慣れた構成ですね。
.
├── build.sbt
├── project
│ ├── build.properties
│ ├── plugins.sbt
│ ├── project
│ │ └── target
│ └── target
├── src
│ ├── main
│ │ ├── java
│ │ ├── resources
│ │ ├── scala
│ └── test
│ ├── java
│ ├── resources
│ ├── scala
└── target
ディレクトリ名通り、ソースコードはsrc/main/scala or javaに、テストコードはsrc/test以下に追加していきます。
本記事の主旨であるビルド設定をいじる場合はbuild.sbtとproject/以下のファイルを触っていくことになります。
build.sbt基本
基本的にはbuild.sbtに設定を書いていくことでビルド定義を行います。
projectフォルダ以下には共通の処理や値を定義したり、プラグインを利用するときに設定したりするようです。
(Scalaの文法を活用するため、project/Build.scala にビルド定義をまとめて書いていくこともあるようです。)
よく見るbuild.sbtは次のようなものではないでしょうか。
name := "sbt-sample"
version := "1.0"
scalaVersion := "2.11.8"
libraryDependencies ++= Seq(
"org.apache.spark" %% "spark-core" % "2.0.0"
)
とりあえず依存関係を解決していくだけであれば、libraryDependenciesに追加していくだけで上手くいきます。
nameやversionって何なの?
nameやversionが表しているものが名前やバージョンであることは一目瞭然だと思いますが、これらの実体は何なのでしょうか。
sbtは最終的にプロジェクト単位で定義を生成しますが、この定義というのは1つの不変なMapなようです。
そして、build.sbt等に書いていたのはこのプロジェクト定義のMapをどう生成するかということを書いていたようです。
例えば次の一文、
name := "sbt-sample"
これは nameというSettingKey[String]型のインスタンスが、:=メソッドを実行してSetting[String]型をオブジェクトを返していて、プロジェクト定義を表す不変MapのnameをこのSetting[T]で置き換え(追加)することで新しいMapを作っているようです。
nameやversionはSettingKey[T]型ですが、他にも全部で3種類のkeyの型があります。
- SettingKey[T]: プロジェクトの読み込み時に一度だけ計算される。
- TaskKey[T]: 複数回実行されるタスクを示す。副作用を伴う可能性がある。
- InputKey[T]: コマンドラインの引数を入力として受け取るタスクのキー。
TaskKey[T]はcompileとかtestとかrunですね。
InputKey[T]はこのタスクの実行時に引数を渡す場合に使うものっぽいです。
libraryDependenciesについて
libraryDependenciesがSettingKey[Seq]型のオブジェクトなのは前項から理解できます。
:=がメソッドだったことからすると、++=や%もメソッドっぽいです。
++=は、Setting[T]のTがリスト系のときに、リストを渡して既存のものと結合したSetting[T]を返すメソッドです。
他にも+=というメソッドがありますが、これはリストの要素を渡して既存のものに追加したSetting[T]を返すものです。
libraryDependenciesに対しても:=メソッドを使うことはできますが、あまりその機会はなさそうですね。
libraryDependenciesはModuleIDのリストで、%は文字列からModuleIDオブジェクトを作るメソッドです。
これによって、ユーザは単にmaven等と同じ感覚でライブラリのアーティファクト名やバージョンを記述していけば良いだけになります。
また、%%はScalaのバイナリバージョンをアーティファクト名に追加する記法です。
例えば、次の2つは同じです。
libraryDependencies += "org.apache.spark" % "spark-core_2.11.8" % "2.0.0"
scalaVersion := "2.11.8"
libraryDependencies += "org.apache.spark" %% "spark-core" % "2.0.0"
オリジナルのタスクを作りたい
何かタスクを作って処理したいこと、あると思います。
そんな時はSettingKey[T]と同じように、TaskKey[T]を定義すれば良さそうです。
lazy val hoge = taskKey[String]("This is a hoge task")
hoge := {
println("hogehoge!")
"successfully hoge"
}
ここではTaskKey[String]として文字列を返していますが、特に何もなければTaskKey[Unit]でも良いし、何でもおkです。
また、TaskKeyがSettingKeyの値をvalueで参照して用いることも可能です。
hoge := {
println("hoge: " + name.value)
"successfully hoge"
}
当然ですが、SettingKeyは読み込み時に一度のみ計算されるものなので逆はできません。
複数のプロジェクトを管理したい
1つのプロジェクトで完結するということは少ないと思います。ここではsbtで複数プロジェクトをどう管理すれば良いのか見ていきます。
これまで、プロジェクトルートにあるbuild.sbtの一番上の階層に各種keyを記述していたと思いますが、これは次のような書き方もできます。
lazy val root = (project in file(".")).
settings(
name := "sbt-sample",
version := "1.0",
scalaVersion := "2.11.8"
libraryDependencies ++= ...以下略
)
このように、明示的にプロジェクトを作って、そのスコープで定義を記述しています。
であれば、複数のプロジェクトを管理するときは次のようにすれば良さそうです。
lazy val root = (project in file(".")).
settings(
name := "sbt-sample"
)
lazy val foo = (project in file("sbt-foo")).
settings(
name := "sbt-foo"
)
lazy val bar = (project in file("sbt-bar")).
settings(
name := "sbt-bar"
)
実際このようにルートのbuild.sbtに記述してリフレッシュすると、Intellij IDEAさんの場合はsbt-fooとsbt-barというモジュールを以下のように勝手に作成してくれました。
.
├── build.sbt
├── project
│ ├── build.properties
│ ├── plugins.sbt
│ ├── project
│ │ └── target
│ └── target
├── sbt-bar
│ ├── project
│ ├── src
│ │ ├── main
│ │ │ ├── java
│ │ │ ├── resources
│ │ │ ├── scala
│ │ └── test
│ │ ├── java
│ │ ├── resources
│ │ ├── scala
│ └── target
├── sbt-foo
│ ├── project
│ ├── src
│ │ ├── main
│ │ │ ├── java
│ │ │ ├── resources
│ │ │ ├── scala
│ │ └── test
│ │ ├── java
│ │ ├── resources
│ │ ├── scala
│ └── target
├── src
│ ├── main
│ │ ├── java
│ │ ├── resources
│ │ ├── scala
│ └── test
│ ├── java
│ ├── resources
│ ├── scala
└── target
複数のプロジェクトでversionなどを共通化したい場合、次のようにすることで実現できます。
lazy val commonSettings = Seq(
version := "1.0",
scalaVersion := "2.11.8"
)
lazy val root = (project in file(".")).
settings(commonSettings: _*).
settings(
name := "sbt-sample"
)
lazy val foo = (project in file("sbt-foo")).
settings(commonSettings: _*).
settings(
name := "sbt-foo"
)
lazy val bar = (project in file("sbt-bar")).
settings(commonSettings: _*).
settings(
name := "sbt-bar"
)
一つ注意点としては、サブプロジェクトではproject/以下の.sbtや.scalaを使うことができません。**
また、それぞれのプロジェクトの*.sbtを他のプロジェクトから見ることもできないので、複数のプロジェクト間で共有したい処理や値はルートのproject/以下に用意すると良さそうです。
複数プロジェクト間の依存関係はどうするの?
プロジェクト間の依存関係として、次の2つがあるようです。
- aggregate: 依存している側で実行するのと同じタスクを、それより前に依存される側で実行する。
// barはfooに依存している(aggregate)。aggregateには複数のプロジェクトを指定することができる。
lazy val foo = project in file("foo")
lazy val bar = (project in file("bar")).aggregate(foo)
- classpath: 別のプロジェクトのコードに依存させる。当然、compileの実行もこれらの間で順序付けられる。
// barはfooのコードを利用する。dependsOnには複数のプロジェクトを指定することができる。
lazy val foo = project in file("foo")
lazy val bar = (project in file("bar")).dependsOn(foo)
プラグインを利用したい
プラグインを利用する場合、ルートのproject/以下の.sbtにaddSbtPlugin()を記述すれば良いです。
例えば、sbt-assembly (fat-jarを生成するタスクを追加する) を利用する場合、
./project/assembly.sbt
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.3")
と追加すれば良いです。
複数プロジェクトを管理していて、特定のプロジェクトでプラグインを有効・無効にしたい場合は次のようにして切り替えられます。
lazy val foo = (project in file("sbt-foo")).
disablePlugins(sbtassembly.AssemblyPlugin)
lazy val bar = (project in file("sbt-bar")).
enablePlugins(sbtassembly.AssemblyPlugin:)
その他知っておくと便利なsbt
console
sbt consoleでScala REPLに入れる。何が良いって、クラスパスが通った状態で起動される。
変更監視 & 自動コンパイル
sbtで次のようにすると、自動で変更を検知してコンパイルしてくれる。
> ~ compile
Keyのチェック
SettingKeyやTaskKeyの具体的なプロパティを見たい時
> inspect [key name]
定義されているtaskを一覧で見たい時
> tasks -v
sbtでのプロジェクトの切り替え
別のプロジェクトでsbtを実行するとき、わざわざ抜けてcdする必要はない。
// projectの一覧
> projects
// 特定のprojectに切り替える
> project [project name]