.scala ビルド定義
このページは、このガイドのこれまでのページ、特に .sbt ビルド定義 と 他の種類のセッティングを読んでいることを前提とする。
sbt は再帰的だ
build.sbt は単純化しすぎていて、sbt の実際の動作を隠蔽している。
sbt のビルドは、Scala コードにより定義されている。そのコード自身もビルドされなければいけない。
当然これも sbt でビルドされる。
project ディレクトリは君のプロジェクトのビルド方法を記述したプロジェクトの中のプロジェクトだ。
project 内のプロジェクトは、他のプロジェクトができる全てのことを(理論的には)こなすことができる。
つまり、ビルド定義は sbt プロジェクトであるということだ_。
この入れ子構造は永遠に続く。project/project ディレクトリを作ることで
ビルド定義のビルド定義プロジェクトをカスタム化することができる。
以下に具体例で説明する:
hello/ # プロジェクトのベースディレクトリ
Hello.scala # プロジェクトのソースファイル
# (src/main/scala に入れることもできる)
build.sbt # build.sbt は、project/ 内のビルド定義プロジェクトの
# 一部となる
project/ # ビルド定義プロジェクトのベースディレクトリ
Build.scala # project/ プロジェクトのソースファイル、
# つまり、ビルド定義のソースファイル
build.sbt # これは、project/project 内のビルド定義プロジェクトの
# 一部となり、ビルド定義のビルド定義となる
project/ # ビルド定義プロジェクトのためのビルド定義プロジェクトの
# ベースディレクトリ
Build.scala # project/project/ プロジェクトのソースファイル
普通はこういうことをする必要は全く無いので、安心してほしい! だけど、原理を理解すると役立つことがある。
ちなみに、.scala や .sbt で終わる全てのファイルが用いられ、
build.sbt や Build.scala と命名するのは慣例にすぎない。
これは複数のファイルを使っていいということも意味する。
ビルド定義プロジェクトにおける .scala ソースファイル
.sbt ファイルは、その兄弟の project ディレクトリにマージされる。
プロジェクトの構造をもう一度見てみる:
hello/ # プロジェクトのベースディレクトリ
build.sbt # build.sbt は、project/ 内のビルド定義プロジェクトの
# 一部となる
project/ # ビルド定義プロジェクトのベースディレクトリ
Build.scala # project/ プロジェクトのソースファイル、
# つまり、ビルド定義のソースファイル
build.sbt 内の Scala 式は別々にコンパイルされ、
Build.scala(もしくは、project/ ディレクトリ内の他の .scala ファイル)
に編入される。
ベースディレクトリの .sbt ファイルは、
ベースディレクトリ直下の project 内のビルド定義プロジェクトの一部となる。
つまり、.sbt ファイルは、ビルド定義プロジェクトにセッティングを追加するための、
便利な略記法ということだ。
build.sbt と Build.scala の関係
ビルド定義の中で、.sbt と .scala を混ぜて使うには、両者の関係を理解する必要がある。
以下に、具体例で説明する。プロジェクトが hello にあるとすると、
hello/project/Build.scala を以下のように作る:
import sbt._
import Keys._
object HelloBuild extends Build {
val sampleKeyA = SettingKey[String]("sample-a", "demo key A")
val sampleKeyB = SettingKey[String]("sample-b", "demo key B")
val sampleKeyC = SettingKey[String]("sample-c", "demo key C")
val sampleKeyD = SettingKey[String]("sample-d", "demo key D")
override lazy val settings = super.settings ++
Seq(sampleKeyA := "A: in Build.settings in Build.scala", resolvers := Seq())
lazy val root = Project(id = "hello",
base = file("."),
settings = Project.defaultSettings ++ Seq(sampleKeyB := "B: in the root project settings in Build.scala"))
}
次に、hello/build.sbt を以下のように書く:
sampleKeyC in ThisBuild := "C: in build.sbt scoped to ThisBuild" sampleKeyD := "D: in build.sbt"
sbt のインタラクティブプロンプトを起動する。inspect sample-a と打ち込むと、以下のように表示されるはず(一部抜粋):
[info] Setting: java.lang.String = A: in Build.settings in Build.scala
[info] Provided by:
[info] {file:/home/hp/checkout/hello/}/*:sample-a
次に、inspect sample-c と打ち込むと、以下のように表示される:
[info] Setting: java.lang.String = C: in build.sbt scoped to ThisBuild
[info] Provided by:
[info] {file:/home/hp/checkout/hello/}/*:sample-c
二つの値とも、"Provided by" は同じスコープを表示していることに注意してほしい。
つまり、.sbt ファイルの sampleKeyC in ThisBuild は、
.scala ファイルの Build.settings リストにセッティングを追加するのと等価とういうことだ。
sbt は、ビルド全体にスコープ付けされたセッティングを両者から取り込んでビルド定義を作成する。
次は、inspect sample-b:
[info] Setting: java.lang.String = B: in the root project settings in Build.scala
[info] Provided by:
[info] {file:/home/hp/checkout/hello/}hello/*:sample-b
sample-b は、
ビルド全体({file:/home/hp/checkout/hello/})ではなく、
特定のプロジェクト({file:/home/hp/checkout/hello/}hello)
にスコープ付けされいることに注意してほしい。
もうお分かりだと思うが、inspect sample-d は sample-b に対応する:
[info] Setting: java.lang.String = D: in build.sbt
[info] Provided by:
[info] {file:/home/hp/checkout/hello/}hello/*:sample-d
sbt は .sbt ファイルからのセッティングを
Build.settings と Project.settings に追加するため、
これは .sbt 内のセッティングの優先順位が高いことを意味する。
Build.scala を変更して、build.sbt でも設定されている
sample-c か sample-d キーを設定してみよう。
build.sbt 内のセッティングが、Build.scala 内のそれに「勝つ」はずだ。
もう一つ気づいたかもしれないが、sampleC と sampleD は build.sbt でそのまま使うことができる。
これは、sbt が Build オブジェクトのコンテンツを自動的に .sbt ファイルにインポートすることにより実現されている。
具体的には、build.sbt ファイル内で import HelloBuild._ が暗黙に呼ばれている。
まとめてみると:
.scalaファイル内で、Build.settingsにセッティングを追加すると、 自動的にビルド全体にスコープ付けされる。.scalaファイル内で、Project.settingsにセッティングを追加すると、 自動的にプロジェクトにスコープ付けされる。.scalaファイルに書いた全てのBuildオブジェクトのコンテンツは.sbtファイルにインポートされる。.sbtファイル内のセッティングは.scalaファイルのセッティングに追加される。.sbtファイル内のセッティングは、明示的に指定されない限り プロジェクトにスコープ付けされる。
いつ .scala ファイルを使うか
.scala ファイルでは、セッティング式の羅列に限定されない。
val、object やメソッド定義など、Scala コードを自由に書ける。
推奨される方法の一つとしては、.scala ファイルは val や object やメソッド定義を
くくり出すのに使用して、セッティングの定義は .sbt で行うことだ。
.sbt 形式は、単一の式のみが許されているので、式の間でコードを共有する方法を持たない。
コードを共有したければ、共通の変数やメソッドの定義ができるように .scala ファイルが必要になる。
.sbt ファイルと .scala ファイルの両方がコンパイルされ、一つのビルド定義が作られる。
.scala ファイルは、単一のビルド内で複数のプロジェクトを定義する場合にも必須だ。
これに関しては、マルチプロジェクトで後ほど説明する。
(マルチプロジェクトで .sbt ファイルを使うことの欠点は、
.sbt ファイルが異なるディレクトリに散らばってしまうことだ。
そのため、サブプロジェクトがある場合は、セッティングを .scala に置くことを好む人もいる。
これは、マルチプロジェクトのふるまいを理解すると、すぐ分かるようになる。)
インタラクティブモードにおけるビルド定義
sbt のインタラクティブプロンプトの現プロジェクトを
project/ 内のビルド定義プロジェクトに切り替えることができる。
reload plugins と打ち込むことで切り替わる:
> reload plugins [info] Set current project to default-a0e8e4 (in build file:/home/hp/checkout/hello/project/) > show sources [info] ArrayBuffer(/home/hp/checkout/hello/project/Build.scala) > reload return [info] Loading project definition from /home/hp/checkout/hello/project [info] Set current project to hello (in build file:/home/hp/checkout/hello/) > show sources [info] ArrayBuffer(/home/hp/checkout/hello/hw.scala) >
上記にあるとおり、realod return を使ってビルド定義プロジェクトから普通のプロジェクトに戻る。
注意: 全て immutable だ
build.sbt 内のセッティングが、Build や Project オブジェクトの settings フィールドに
追加されると考えるのは間違っている。
そうじゃなくて、Build や Project のセッティングリストと build.sbt のセッティングが
連結されて別の不変リストになって、それが sbt に使われるというのが正しい。
Build と Project オブジェクトは、immutable なコンフィギュレーションであり、
ビルド定義の全体からすると、たった一部にすぎない。
事実、セッティングには他にも出どころがある。具体的には、以下の順で追加される:
.scalaファイル内のBuild.settingsとProject.settings。- ユーザ定義のグローバルセッティング。例えば、
~/.sbt/build.sbtに全てのプロジェクトに影響するセッティングを定義できる。 - プラグインによって注入されるセッティング、次のプラグインの使用参照。
- プロジェクトの
.sbtファイル内のセッティング。 - (
project内のプロジェクトである)ビルド定義プロジェクトの場合は、グローバルプラグイン(~/.sbt/plugins)が追加される。 プラグインの使用で詳細が説明される。
後続のセッティングは古いものをオーバーライドする。このリスト全体でビルド定義が構成される。
続いては
プラグインの使用に進む。