Locked History Actions

sbt/Getting-Started-Basic-Def

.sbtビルド定義

https://github.com/harrah/xsbt/wiki/Getting-Started-Basic-Defの翻訳(2011/10/27時点)

ここではsbtビルド定義について説明するが、いくつかの「理屈」とbuild.sbtの構文が含まれる。 sbtの使い方とこれまでのページを読んだことを前提とするよ。

.sbtと.scala定義の違い

sbtのビルド定義はベースディレクトリに置かれた.sbtファイルに記述してもよいし、projectサブディレクトリに置かれた.scalaファイルに記述してもよい。

どちらか一方をつかってもいいし、両方を使ってもいい。通常は.sbtファイルを利用し、.sbtでは実現できない場合に.scalaを使うというのが良いアプローチだろう。 後者を使うのは、

  • sbtをカスタマイズする。つまり、新しいsettingやtaskを作る。
  • ネストされたサブプロジェクトを定義する。

ここでは、.sbtファイルについて説明する。.scalaについては「.scala build definition」で説明する。

ビルド定義とは何か?

ここは絶対に読むべし

sbtはプロジェクトを検証してビルド定義ファイルを処理したあと、ビルドを記述したイミュータブルなマップ(キー・値ペアの集合)を固定する。

例えば、nameというキーは文字列値をマップするが、それは君のプロジェクトの名前を表す。

ビルド定義ファイルはsbtマップに直接影響するわけではない。

そうではなく、ビルド定義はSetting[T]型の巨大なオブジェクトリストを生成する。Tとはマップ中の値の型だ(ScalaでのSetting[T]とは、JavaでいうSetting<T>のこと)。 Settingはマップに対する変換を表現している。例えば、(マップに対して)新たなキー・値ペアを追加したり、既存の値に追加したりすることだ (関数プログラミングの精神においては、変換は新しいマップを返す。古いマップを更新したりはしない)。

.sbtファイルの中では、プロジェクト名を表すSetting[String]を次のように作成することができる。

name := "hello"

このSetting[String]がマップに新しいnameというキーを追加し(あるいは置換し)、それに"hello"という名前を与える。 変換されたマップはsbtの新たなマップになる。

マップを作成するにあたり、事前にsbtはsettingのリストをソートするので同一のキーに対する変更は束ねられる。 そして、値が他のキーに依存するのであれば、その依存先がまず処理される。 そうしておいて、sbtはSettingのソート済リストをたどり、それぞれをマップに適用する。

サマリ:ビルド定義とはSetting[T]のリストを定義することであり、Setting[T]はsbtのキー・値マップペアへの変換であり、 Tとは各値のタイプである。

build.sbtにおけるsettingの定義方法

以下に例をあげる

name := "hello"

version := "1.0"

scalaVersion := "2.9.1"

build.sbtファイルは空行で区切られたsettingのリストだ。 それぞれのsettingはScalaの式で記述される。

build.sbtにおける式は、他との依存を持たない。また、それらは「式」であって、完全なScalaの「文」ではない。 だから、build.sbtの中でトップレベルのval、オブジェクト、クラス、メソッドを定義することはできない。

左側のname、version、scalaVersionはキーだ。 キーはSettingKey[T]、TaskKey[T]、InputKey[T]のいずれかで、Tは値の型だ。 キーの種類については後で説明する。

キーは「:=」というメソッドを持つのだが、この返値はSetting[T]になる。 このメソッドを呼び出すには、Javaのように以下のような書き方でもいい。

name.:=("hello")

Scalaでは「name := "hello"」という書き方でもいい(Scalaでは、どんなメソッドもどちらの書き方も許される)。

nameキーの:=メソッドはSettingを返すのだが、ここでは当然Setting[String]になる。 Stringはキー自身のタイプに現れる、つまりSettingKey[String]だ。 この場合には、返されたSetting[String]はsbtマップにnameキーを追加するか置換するものになる。その値は"hello"だ。

もし値のタイプを間違えるとビルド定義はコンパイルされない。

name := 42  // コンパイルできない

キーはKeysオブジェクトの中で定義されている

ビルトインキーはKeysというオブジェクト中に定義されている単なるフィールドだ。 build.sbtでは暗黙的にsbt.Keys._がインポートされているので、sbt.Keys.nameを単にnameのみで参照することができる。

カスタムキーは.scalaファイルかプラグインで定義することができる。

settingを変換する他の方法

:=による置換は最も単純な変換であり、他にも方法はある。 例えば、リスト値に+=で追加する方法だ。

その他の変換については、スコープを理解しておく必要がある。 次のページではスコープについて学習し、その後でsettingの詳細を説明する。

タスクキー

キーには三種類ある。

  • SettingKey[T]: 一度だけ計算される値を持つキー(値は、プロジェクトのロード時にだけ計算され、その後保持され続ける)

  • TaskKey[T]: 必要なときに再計算される値を持つキー。多くの場合副作用を持つ。

  • InputKey[T]: 入力としてコマンドライン引数を受け取るTaskKey。「Getting Started Guide」では、これについては説明しないが、このガイドを終了したら「Input Tasks」を参照して欲しい。

TaskKey[T]はタスクを定義するものだ。 タスクとは、コンパイルやパッケージング等の操作を意味する。 それらは、Unit(Javaでいうvoid)か、あるいはタスクに関連する何らかの値を返す。 例えば、packageはTaskKey[File]であり、作成したjar fileを返す。

いつでもタスクの実行を指示することができる。例えば、sbtの対話プロンプトでcompileと入力すると、それに含まれるタスクをただ一度再実行する。

sbtのマップはプロジェクトを記述するものであるが、ここにはnameのような固定的な文字列が保持されるほか、 compileのようなタスクの実行コードも保持される。この実行コードが最終的には文字列を返すには、毎回再実行する必要がある。

あるキーは、タスクかあるいはプレーンなsettingのいずれかを参照する。 つまり、taskiness(毎回再実行が可能であるか)はキーの性質であって、値ではない。

訳注:要するに、SettingKeyの値として実行コードを代入しても、実行されないことに注意しろという意味?

:=を使ってタスクに計算を代入することができ、その計算は毎回実行することができる。

hello := { println("Hello!") }

タイプシステムの観点から言えば、タスクキーから作成されたSettingはSetting Keyから作成されたものとは少々異なる。

taskKey := 42」はSetting[Task[T]]を結果とし、「settingKey := 42」はSetting[T]を結果とする。 たいていの場合には、何の違いもない。タスクが実行されると、単にタイプTの結果を生成するだけだ。

TとTask[T]のタイプの違いは以下を意味している。 setting keyはtask keyに依存することはできない。なぜなら、setting keyはプロジェクトロード時に一度だけ評価され、再実行されないからだ。 settingの詳細については、また後で説明するよ。

対話モード中でのキー

sbtの対話モードでは、タスクの名称を入力することによって、それを実行することができる。 compileと入力すれば、コンパイルタスクが実行される。compileはタスクキーだ。

タスクキーではなくsettingキーを入力すると、その値が表示される。 タスクキーはタスクを実行するものの結果値は表示しない。これを見るには、ただ<タスク名>と入力するのではなくshow <タスク名>とする必要がある。

ビルド定義中では、scalaのコンベンションに習ってキーの名称はキャメルケースになっているが、コマンドライン中ではハイフンで分離されたワードになっている。ハイフン分離された文字列はキー定義の中にある。例えば、Keys.scalaには以下のようなキーがある。

val scalacOptions = TaskKey[Seq[String]]("scalac-options", "Options for the Scala compiler.")

sbt(の対話モード)ではscalac-optionsと入力するが、ビルド定義中ではscalacOptionsとしなければならない。

キーについて知りたい場合は、対話モードで「inspect <キー名>」と入力すること。 inspectの表示するいくつかの情報はまだ意味をなさないものもあるが、しかしトップにはsettingの値タイプとsettingの管理説明がある。

build.sbtにおけるインポート

build.sbtのトップにはインポート文を入れることができる。これらは空行で分割されていなくていい。 以下のインポートは暗黙的に行われている。

import sbt._
import Process._
import Keys._

(加えて、.scalaファイルがあるなら、その中のビルド定義やプラグインをインポートすることができる。 これについては「.scala build definitions」で説明する)。

ライブラリ依存を追加する

サードパーティ製のライブラリに依存する際には二つの選択肢がある。 一つはlib/ディレクトリ(管理されていない依存)にそれを格納すること。 もう一つは管理依存に追加することだ。 後者の場合、build.sbtに次のように記述する。

libraryDependencies += "org.apache.derby" % "derby" % "10.4.1.3"

こうすると、Apache Derby library, version 10.4.1.3への管理された依存を追加することができる。

libraryDependenciesキーは二つの複雑さを持つ。:=ではなく、+=であることと、%というメソッドだ。 +=は置換ではなく、キーの古い値に追加するということ。 これについては後述する。 %メソッドは、IvyモジュールIDを作成するものだが、これについても後述する。

ライブラリ依存についての詳細は、このガイドの後の方になるまでスキップする。 まるまる1ページ使って詳細を説明するつもりだ。

次はスコープ