.sbt ビルド定義
このページでは、多少の「理論」も含めた sbt のビルド定義 (build definition) と build.sbt の構文を説明する。
君が、sbt の使い方を分かっていて、「始める sbt」の前のページも読んだことを前提とする。
.sbt vs. .scala 定義
sbt のビルド定義はベースディレクトリ内の .sbt で終わるファイルと、
project サブディレクトリ内の .scala で終わるファイルを含むことができる。
どちらか一つだけを使うこともできるし、両方使うこともできる。
普通の用途には .sbt を使って、.scala を使うのは以下のような .sbt で出来ないことに限定する、というのが良い方法だ:
- sbt をカスタマイズする(新しい設定値やタスクを加える)
- サブプロジェクトを定義する
このページでは .sbt ファイルの説明をする。.scala ファイルの詳細と、それがどう .sbt に絡んでくるかに関しては、
(このガイドの後ほどの).scala ビルド定義を参照。
ビルド定義って何?
ここは読んで下さい
プロジェクトを調べ、全てのビルド定義ファイルを処理した後、sbt は、ビルドを記述した不可変マップ(キーと値のペア)を最終的に作る。
例えば、name というキーがあり、それは文字列の値、つまり君のプロジェクト名に関連付けられる。
ビルド定義ファイルは直接には sbt のマップに影響を与えない。
その代わり、ビルド定義は、型が Setting[T] のオブジェクトを含んだ巨大なリストを作る。
T はマップ内の値の型だ。(Scala の Setting[T] は Java の Setting<T> と同様。)
Setting は、新しいキーと値のペアや、既存の値への追加など、マップの変換を記述する。
(関数型プログラミングの精神に則り、変換は新しいマップを返し、古いマップは更新されない。)
build.sbt では、プロジェクト名の Setting[String] を以下のように作る:
name := "hello"
この Setting[String] は name キーを追加(もしくは置換)して "hello" という値に設定することでマップを変換する。
変換されたマップは新しい sbt のマップとなる。
マップを作るために、sbt はまず、同じキーへの変更が一緒に起き、かつ他のキーに依存する値の処理が依存するキーの後にくるように Setting のリストをソートする。
次に、sbt はソートされた Setting のリストを順番にみていって、一つづつマップに適用する。
まとめ: ビルド定義は Setting[T] のリストを定義し、Setting[T] は sbt のキー・値ペアへの変換を表し、T は値の型を指す。
build.sbt はどう設定値を定義するか
以下に具体例で説明しよう:
name := "hello" version := "1.0" scalaVersion := "2.9.1"
build.sbt は、空行で分けられた Setting のリストだ。それぞれの Setting は Scala の式で表される。
build.sbt 内の式は、それぞれ独立しており、完全な Scala 文ではなく、式だ。
そのため、build.sbt 内ではトップレベルでの val、object、クラスやメソッドの定義は禁止されている。
左辺値の name、version、および scalaVersion は キーだ。
キーは SettingKey[T]、TaskKey[T]、もしくは InputKey[T] のインスタンスで、
T はその値の型だ。キーの種類に関しては後で説明しよう。
キーには := メソッドがあり、Setting[T] を返す。
Java 的な構文でこのメソッドを呼び出すこともできる:
name.:=("hello")
だけど、Scala では name := "hello" と書ける(Scala では全てのメソッドがどちらの構文でも書ける)。
name キーの := メソッドは Setting を返すが、特に Setting[String] を返す。
String は、name の型にもあらわれ、これは、SettingKey[String] となっている。
この場合、返された Setting[String] は、キーを追加(もしくは置換)して "hello" という値に設定するマップの変換だ。
間違った型の値を使うと、ビルド定義はコンパイルしない:
name := 42 // コンパイルしない
キーは Keys オブジェクトで定義される
組み込みのキーは Keys と呼ばれるオブジェクトのフィールドにすぎない。
build.sbt は、自動的に import sbt.Keys._ するため、
sbt.Keys.name は name として呼ぶことができる。
カスタムのキーは .scala ファイルかplugin で定義することができる。
設定を変換する他の方法
:= による置換は、最も単純な変換だけど、他にもいくつかある。
例えば、+= を用いて、リスト値に追加することができう。
他の変換はスコープの理解が必要なため、 次のページがスコープで、 次の次のページで設定の詳細に関して説明する。
タスクキー
キーには三種類ある:
SettingKey[T]: 値が一度だけ計算されるキー(値はプロジェクトの読み込み時に計算され、保存される)。TaskKey[T]: 毎回再計算され、副作用を伴う可能性のある値のキー。InputKey[T]: コマンドラインの引数を受け取るタスクキー。 「初めての sbt」ではInputKeyを説明しないので、このガイドを終えた後で、 [[Input Tasks]] を読んでみよう。
TaskKey[T] は、タスクを定義しているといわれる。タスクは、compile や packae のような作業だ。
タスクは Unit を返すかもしれないし(Unit は、Scala での void だ)、
タスクに関連した値を返すかもしれない。例えば、package は作成した jar ファイルを値として返す TaskKey[File] だ。
例えばインタラクティブモードの sbt プロンプトに compile と打ち込むなどして、
タスクを実行するたびに、sbt は関連したタスクを一回だけ再実行する。
プロジェクトを記述した sbt のマップは、name のようなセッティング (setting) ならば、その文字列の値をキャッシュすることができるけど、
compile のようなタスク(task)の場合は実行可能コードを保存する必要がある
(たとえその実行可能コードが最終的に同じ文字列を返したとしても、それは毎回再実行されなければいけない)。
あるキーがあるとき、それは常にタスクか素のセッティングかのどちらかを参照する。 つまり、キーの「タスク性」(毎回再実行するかどうか)はキーの特性であり、値にはよらない。
:= を使うことで、タスクに任意の演算を代入することができ、その演算は毎回再実行される:
hello := { println("Hello!") }
型システムの視点から考えると、タスクキー (task key) から作られた Setting は、セッティングキー (setting key) から作られたそれとは少し異なるものだ。
taskKey := 42 は Setting[Task[T]] の戻り値を返すが、settingKey := 42 は Setting[T] の戻り値を返す。
タスクが実行されるとタスクキーは型T の値を返すため、ほとんどの用途において、これによる影響は特にない。
T と Task[T] の型の違いによる影響が一つある。
それは、セッティングキーはキャッシュされていて、再実行されないため、タスキキーに依存できないということだ。
このことについては、後ほどの他の種類のセッティングにて詳しくみていく。
sbt インタラクティブモードにおけるキー
sbt のインタラクティブモードからタスクの名前を打ち込むことで、どのタスクでも実行することができる。
それが compile と打ち込むことでコンパイルタスクが起動する仕組みだ。つまり、compile はタスクキーなのだ。
タスクキーのかわりにセッティングキーの名前を入力すると、セッティングキーの値が表示される。
タスクキーの名前を入力すると、タスクを実行するが、その戻り値は表示されないため、
タスクの戻り値を表示するには素の <タスク名> ではなく、show <タスク名> と入力する。
Scala の慣例にのっとり、ビルド定義ファイル内ではキーはキャメルケース(camelCase)で命名されているけども、
sbt コマンドラインではハイフン分けされて(hyphen-separated-words)命名されている。
sbt で使われているハイフン分けされた文字列はキーの定義とともに宣言されている(Keys 参照)。
例えば、Keys.scala に以下のキーがある:
val scalacOptions = TaskKey[Seq[String]]("scalac-options", "Options for the Scala compiler.")
sbt では scalac-options と打ち込むけど、ビルド定義ファイルでは scalacOptions を使う。
あるキーについてより詳しい情報を得るためには、sbt インタラクティブモードで inspect <キー名> と打ち込む。
inspect が表示する情報の中にはまだ分からないこともあると思うけど、一番上にはセッティングの値の型と、セッテイングの簡単な説明がある。
build.sbt 内の import 文
build.sbt の一番上に import 文を書くことができ、それらは空行でわけなくてもいい。
自動的に以下のものがデフォルトでインポートされる:
import sbt._ import Process._ import Keys._
(さらに、.scala ファイルがある場合は、それらの全ての Build と Plugin の内容もインポートされる。
これに関しては、.scala ビルド定義でさらに詳しく。)
ライブラリへの依存性を加える
サードパーティのライブラリに依存するには二つの方法がある。
第一は lib/ に jar ファイルを入れてしまう方法で(アンマネージ依存性、unmanged dependency)、
第二はマネージ依存性(managed dependency)を加えることで、build.sbt ではこのようになる:
libraryDependencies += "org.apache.derby" % "derby" % "10.4.1.3"
これで Apache Derby ライブラリのバージョン 10.4.1.3 へのマネージ依存性を加えることができた。
libraryDependencies キーは二つの複雑な点がある:
:= ではなく += を使うことと、% メソッドだ。
後で他の種類のセッティングで説明するけど、+= はキーの古い値を上書きするかわりに新しい値を追加する。
% メソッドは文字列から Ivy モジュール ID を構築するのに使われ、これはライブラリ依存性で説明する。
ライブラリ依存性に関しては、このガイドの後ほどまで少しおいておくことにする。 後で、一ページ分をさいてちゃんと説明する。
続いては
スコープについて。