Help us understand the problem. What is going on with this article?

初めて触る人のためのsbtまとめ

More than 3 years have passed since last update.

目的

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は次のようなものではないでしょうか。

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をどう生成するかということを書いていたようです。
例えば次の一文、

sbt
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つは同じです。

sbt
libraryDependencies += "org.apache.spark" % "spark-core_2.11.8" % "2.0.0"
sbt
scalaVersion := "2.11.8"
libraryDependencies += "org.apache.spark" %% "spark-core" % "2.0.0"

オリジナルのタスクを作りたい

何かタスクを作って処理したいこと、あると思います。
そんな時はSettingKey[T]と同じように、TaskKey[T]を定義すれば良さそうです。

sbt
lazy val hoge = taskKey[String]("This is a hoge task")
hoge := {
  println("hogehoge!")
  "successfully hoge"
}

ここではTaskKey[String]として文字列を返していますが、特に何もなければTaskKey[Unit]でも良いし、何でもおkです。
また、TaskKeyがSettingKeyの値をvalueで参照して用いることも可能です。

sbt
hoge := {
  println("hoge: " + name.value)
  "successfully hoge"
}

当然ですが、SettingKeyは読み込み時に一度のみ計算されるものなので逆はできません。

複数のプロジェクトを管理したい

1つのプロジェクトで完結するということは少ないと思います。ここではsbtで複数プロジェクトをどう管理すれば良いのか見ていきます。

これまで、プロジェクトルートにあるbuild.sbtの一番上の階層に各種keyを記述していたと思いますが、これは次のような書き方もできます。

sbt
lazy val root = (project in file(".")).
  settings(
    name := "sbt-sample",
    version := "1.0",
    scalaVersion := "2.11.8"
    libraryDependencies ++= ...以下略
  )

このように、明示的にプロジェクトを作って、そのスコープで定義を記述しています。
であれば、複数のプロジェクトを管理するときは次のようにすれば良さそうです。

sbt
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などを共通化したい場合、次のようにすることで実現できます。

sbt
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: 依存している側で実行するのと同じタスクを、それより前に依存される側で実行する。
sbt
// barはfooに依存している(aggregate)。aggregateには複数のプロジェクトを指定することができる。
lazy val foo = project in file("foo")
lazy val bar = (project in file("bar")).aggregate(foo)
  • classpath: 別のプロジェクトのコードに依存させる。当然、compileの実行もこれらの間で順序付けられる。
sbt
// 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

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

と追加すれば良いです。
複数プロジェクトを管理していて、特定のプロジェクトでプラグインを有効・無効にしたい場合は次のようにして切り替えられます。

sbt
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]
prokosna
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away