Scala向けビルドツールsbtの使い方

kotohが2015/07/14 19:04:33に投稿

sbtの設定と使い方

はじめに

sbtはコンパイル、パッケージング、テスト実行、依存ライブラリの管理などをやってくれるScala向けのビルドツールです。
本記事では、インストールから簡単な使い方、設定方法を説明します。
後半では、プラグインの導入、マルチプロジェクトの管理などを説明します。
込み入ったことは省略しているので、詳細が知りたい場合は 本家を参照して下さい。

インストール

JDKが必要です、JDKはインストール済みとします。(1.7以降がインストールされていれば問題ありません。)

  • MacOS
$ brew install sbt

他のプラットフォームは、本家 を参考にして下さい。
以降、sbtのバージョンは0.13.8を前提として話しをすすめます。

最初の一歩

まずは、Hello world. から。
作業用のディレクトリを適当に作成して下さい。

$ mkdir sbt-sample
$ cd sbt-sample
$ sbt
...
>

ライブラリのダウンロードが終わるとSBTが起動し、プロンプトが > に変わります。

最初はsbtが必要とするライブラリをダウンロードするので時間がかかります。
途中、scala-2.10.x 系をダウンロードしているのが確認できると思います。これは、sbt自体が必要とするScalaをダウンロードしています。
sbtは依存ライブラリの解決にivyを使っています、~/.ivy に依存ライブラリがキャッシュされています。

sbt-sample/Hello.scala

object Hello extends App {
  println("Hello world.")
}

sbtからcompile, runを実行します。

> compile
[info] Compiling 1 Scala source to ...
[success] Total time: 6 s, completed 2015/07/14 11:03:52
> run
[info] Running Hello 
Hello world.

今、実行したcompile, runはsbtのtaskと呼ばれます。
実行可能なtaskはsbtからtasksで確認することができます。

> tasks
This is a list of tasks defined for the current project.
It does not list the scopes the tasks are defined in; use the 'inspect' command for that.
Tasks produce values.  Use the 'show' command to run the task and print the resulting value.

  clean            Deletes files produced by the build, such as generated sources, compiled classes, and task caches.
  compile          Compiles sources.
  console          Starts the Scala interpreter with the project classes on the classpath.
  consoleProject   Starts the Scala interpreter with the sbt and the build definition on the classpath and useful imports.
  consoleQuick     Starts the Scala interpreter with the project dependencies on the classpath.
  copyResources    Copies resources to the output directory.
  doc              Generates API documentation.
  package          Produces the main artifact, such as a binary jar.  This is typically an alias for the task that actually does the packaging.
  packageBin       Produces a main artifact, such as a binary jar.
  packageDoc       Produces a documentation artifact, such as a jar containing API documentation.
  packageSrc       Produces a source artifact, such as a jar containing sources and resources.
  publish          Publishes artifacts to a repository.
  publishLocal     Publishes artifacts to the local Ivy repository.
  publishM2        Publishes artifacts to the local Maven repository.
  run              Runs a main class, passing along arguments provided on the command line.
  runMain          Runs the main class selected by the first argument, passing the remaining arguments to the main method.
  test             Executes all tests.
  testOnly         Executes the tests provided as arguments or all tests if no arguments are provided.
  testQuick        Executes the tests that either failed before, were not run or whose transitive dependencies changed, among those provided as arguments.
  update           Resolves and optionally retrieves dependencies, producing a report.

More tasks may be viewed by increasing verbosity.  See 'help tasks'.

よく使うのは、run, test, reload, compileでしょうか。
reloadはbuild.sbtを再読み込みします。

今回は触れませんが、自分でtaskを作成し、追加することもできます。

構成

sbt-sampleのディレクトリを確認してみます。

$ tree -L 2
.
├── Hello.scala
└── target
    ├── resolution-cache
    ├── scala-2.10
    └── streams

targetというディレクトリが作成されています。
ここには、Scalaプログラムをコンパイルしてできたclassファイルやキャッシュが置かれています、開発者がここを意識することはあまりありません。

scala-2.10というディレクトリについて少し説明します。
sbt自体はscalaで動いていて、設定もscalaで記述します。
sbt-0.13.8はScala-2.10.4に依存しています、なのでデフォルトではScala-2.10.4でコンパイルします。

次にscalaのバージョンを変更したり、テストを作成したり、プロジェクトとして設定を行う方法を説明します。

プロジェクトの作成と設定

sbtはプロジェクトの雛型する機能を持っていません。
Typesafe Activatorgiter8 を使えばひな型を作成することができます。

今回はこれらのツールは使わず、自前でディレクトリ・ファイルを作成していくことにします。

ディレクトリ構成

先程作成した、sbt-sampleに次のようなディレクトリ構成に変更して下さい。

$ tree
.
└── src
    ├── main
    │   ├── resources
    │   └── scala
    │       └── Hello.scala
    └── test
        ├── resources
        └── scala
  • src/main

product codeを配置します。

  • src/test

test codeを配置します。

main, test 以下のscala, resourceはそれぞれ、scalaコード、リソースファイルを配置します。
また、scalaの下は、com/hoge/... とパッケージと同じ階層にすることも可能です。
デフォルトではsbtはsrc/main/scala の下にscalaコードを探してコンパイルします。これは設定によって変更することもできます。

$ sbt
> compile
[info] Compiling 1 Scala source to ...
[success] Total time: 5 s, completed 2015/07/14 14:29:54
> run
[info] Running Hello 
Hello world.

sbtで実行できました。
次は設定の変更方法を説明します。

設定ファイル

sbtでは2種類の設定ファイルを使うことができます。
ひとつ目は、build.sbt です。もうひとつは project/Build.scala です。
古いsbtでは、両者でできることに差があったのすが、0.13以降はほとんど差がないようです。

以前は以下のような差がありました。

build.sbtはsbtで用意された設定用のDSLを用いて設定を記述するので、簡単に設定が書けます。
ただし、val, def, class などscalaのコードが利用できませんでした。つまり、共通の設定をまとめたりできませんでした。
また、マルチプロジェクトの設定はBuild.scalaでしかできませんでした。
なので、小規模でシンプルなプロジェクトはbuild.sbt、大規模で複雑なプロジェクトではBuild.scalaが使われることが多かったのではないでしょうか。

0.13以降は、build.sbtでclass, object が使えませんが、マルチプロジェクトの設定ができるようになっています。
今回は、build.sbt を利用します。

Scalaのバージョン、アプリケーションの情報、依存ライブラリの定義、Scalaコンパイラオプションを設定するシンプルなものを次に記載します。
build.sbt

lazy val commonSettings = Seq(
  organization := "jp.co.linkode",
  version := "0.1.0",
  scalaVersion := "2.11.7"
)

lazy val root = (project in file(".")).
  settings(commonSettings: _*).
  settings(

    name := "sbt-sample project",

    scalacOptions += "-deprecation",

    libraryDependencies ++= Seq(
      "org.apache.commons" % "commons-lang3" % "3.1",
      "org.scalatest" %% "scalatest" % "2.2.4" % "test"
    )
  )

sbt0.13.7より前は各設定は空行で区切る必要がありましたが、0.13.7以降はこの空行が不要になりました。

何を設定しているかは直感的でわかりやすいと思います。
記号について、少し説明します。

記号 意味
:= 設定値の割り当て
+= 既にある設定値に追加
++= コレクションに設定値を追加

通常libraryDependenciesでは 依存ライブラリを

groupID % artifactID % revision

のように記述します。

libraryDependenciesの%と%%の違いですが、%%はscalaのバージョンを付与します。
scalaのライブラリにはscalaバージョンが付くのが一般的です。
つまり、

"org.scalatest" %% "scalatest" % "2.2.4" % "test"

"org.scalatest" % "scalatest_2.11" % "2.2.4" % "test"

と同じです。

また、行末の % test は test の時にか依存しないことを表しています。
他にも設定できる項目があります、こちらを参照して下さい。

ここまで設定すれば、Scalaで開発を始めることができます。

次は、もう少し複雑なことを設定してみます。

応用編

プラグイン

sbtにはプラグインという仕組みがあります。
プラグインにはプロジェクト個別のプラグインとグローバルプラグインの2種類があります。

プロジェクト別プラグイン

プロジェクト毎にプラグインを設定し、設定したプロジェクトでのみ利用するプラグインのことです。
project/plugins.sbt に設定を記述します。

sbtでサーブレットアプリケーションの開発を行うときに必要なプラグインの設定を例に説明します。

project/plugins.sbt

addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "0.9.0")

build.sbt

seq(webSettings :_*) // 追加

sbtのtaskにcontainer:startなどコンテナを起動・停止するコマンドが追加されています。

グローバルプラグイン

エディタやIDEをsbtを連携されるプラグインなど、プロジェクトとは本来関係のないプラグインを扱う仕組みです。
グローバルプラグインで設定したプラグインは全てのsbtプロジェクトで有効になります。

$HOME/.sbt/0.13/plugins/plugins.sbt に設定を記述します。

僕は、ensime-sbt というテキストエディタとsbtを連携させるプラグインをグローバルプラグインに設定しています。

$HOME/.sbt/0.13/plugins/plugins.sbt

resolvers += Resolver.sonatypeRepo("snapshots")

addSbtPlugin("org.ensime" % "ensime-sbt" % "0.1.8-SNAPSHOT")

マルチプロジェクト

次のようなプロジェクトを例に説明します。

├── build.sbt
└── src
    ├── main
    │   ├── resources
    │   └── scala
    │       └── Hello.scala
    └── test
        ├── resources
        └── scala
└── modules
    ├── core
    └── util

メイン(root)プロジェクトがmodulesの下のcoreとutilというsbtプロジェクトに依存しています。
このプロジェクト間の依存関係をbuild.sbtに定義します。

依存関係には集約と依存を定義することができます。

集約

集約する側のプロジェクトであるタスクを実行するとき、集約される側の複数のプロジェクトでも同じタスクを実行するという関係を意味する。

build.sbt

lazy val commonSettings = Seq(
  organization := "jp.co.linkode",
  version := "0.1.0",
  scalaVersion := "2.11.7"
)
lazy val root = (project in file(".")).
  settings(commonSettings: _*).
  settings(
    name := "sbt-sample project",
    scalacOptions += "-deprecation",
    libraryDependencies ++= Seq(
      "org.apache.commons" % "commons-lang3" % "3.1",
      "org.scalatest" % "scalatest_2.11" % "2.2.4" % "test"
    )
  ).aggregate(core, util) // 集約を定義


lazy val core = (project in file("modules/core")).
  settings(commonSettings: _*).
  settings(
    name := "core project",
    scalacOptions += "-deprecation",
    libraryDependencies ++= Seq(
      // ...
    )
  )

lazy val util = (project in file("modules/util")).
  settings(commonSettings: _*).
  settings(
    name := "util project",
    scalacOptions += "-deprecation",
    libraryDependencies ++= Seq(
      // ...
    )
  )

プロジェクトの切り替えは、

> project core

などで行います。
この場合rootプロジェクトでcompileすると、core, utilもcompileされます。
集約したプロジェクトではコマンドが実行してほしくない場合は、

lazy val root = (project in file(".")).
  aggregate(util, core).
  settings(
    aggregate in update := false
  )

このように設定することで回避できます。
この場合rootでupdateを実行しても、core, utilではupdateは実行されません。

runの場合は現在のプロジェクトのみで実行されます。

依存

プロジェクトが別のプロジェクトのコードに依存する状態を定義します。

build.sbt

lazy val commonSettings = Seq(
  organization := "jp.co.linkode",
  version := "0.1.0",
  scalaVersion := "2.11.7"
)
lazy val root = (project in file(".")).
  dependsOn(core, util).
  settings(commonSettings: _*).
  settings(
    name := "sbt-sample project",
    scalacOptions += "-deprecation",
    libraryDependencies ++= Seq(
      "org.apache.commons" % "commons-lang3" % "3.1",
      "org.scalatest" % "scalatest_2.11" % "2.2.4" % "test"
    )
  )


lazy val core = (project in file("modules/core")).
  settings(commonSettings: _*).
  settings(
    name := "core project",
    scalacOptions += "-deprecation",
    libraryDependencies ++= Seq(
      // ...
    )
  )

lazy val util = (project in file("modules/util")).
  settings(commonSettings: _*).
  settings(
    name := "util project",
    scalacOptions += "-deprecation",
    libraryDependencies ++= Seq(
      // ...
    )
  )

こうすると、rootプロジェクトからcore, utilのパッケージ、クラスをimportすることができます。

また、aggregateとdependsOnは同時に定義することもできます。

lazy val root = (project in file(".")).
  dependsOn(core, util).
  aggregate(core,util).
  settings(commonSettings: _*).