ビルド定義の基本
このページは build.sbt のビルド定義を解説する。
ビルド定義とは何か
ビルド定義は、build.sbt にて定義され、プロジェクト (型は Project) の集合によって構成される。 プロジェクトという用語が曖昧であることがあるため、このガイドではこれらをサブプロジェクトと呼ぶことが多い。
例えば、カレントディレクトリにあるサブプロジェクトは build.sbt に以下のように定義できる:
scalaVersion := "3.3.3"
name := "Hello"
より明示的に書くと:
lazy val root = (project in file("."))
.settings(
scalaVersion := "3.3.3",
name := "Hello",
)
それぞれのサブプロジェクトは、キーと値のペアによって詳細が設定される。例えば、name というキーがあるが、それはサブプロジェクト名という文字列の値に関連付けられる。キーと値のペア列は .settings(...) メソッド内に列挙される。
build.sbt DSL
build.sbt は、Scala に基づいた build.sbt DSL と呼ばれるドメイン特定言語 (DSL) を用いてサブプロジェクトを定義する。まずは scalaVersion と libraryDependencies のみを宣言して、YAML ファイルのように build.sbt を使うことができるが、ビルドの成長に応じてその他の機能を使ってビルド定義を整理することができる。
型付けされたセッティング式
build.sbt DSL をより詳しくみていこう:
organization := "com.example"
^^^^^^^^^^^^ ^^^^^^^^ ^^^^^^^^^^^^^
key operator (setting/task) body
それぞれのエントリーはセッティング式と呼ばれる。セッティング式にはタスク式と呼ばれるものもある。この違いは後で説明する。
セッティング式は以下の 3部から構成される:
- 左辺項をキー (key) という。
- 演算子。この場合は
:=。 - 右辺項は本文 (body)、もしくはセッティング本文という。
左辺値の name、version、および scalaVersion はキーである。 キーは SettingKey[A]、 TaskKey[A]、もしくは InputKey[A] のインスタンスで、 A はその値の型である。キーの種類に関しては後述する。
name キーは SettingKey[String] に型付けされているため、 name の := 演算子も String に型付けされている。これにより、誤った型の値を使おうとするとビルド定義はコンパイルエラーになる:
name := 42 // コンパイルできない
val と lazy val
ライブラリのバージョン番号など同じ情報の繰り返しを避けるために、build.sbt 内の任意の行に val、lazy val、def を書くことができる。
val toolkitV = "0.2.0"
val toolkit = "org.scala-lang" %% "toolkit" % toolkitV
val toolkitTest = "org.scala-lang" %% "toolkit-test" % toolkitV
scalaVersion := "3.7.3"
libraryDependencies += toolkit
libraryDependencies += (toolkitTest % Test)
上の例で val は変数を定義し、上の行から順に初期化される。そのため、toolkitV が参照される前に定義される必要がある。
以下は悪い例:
// 悪い例
val toolkit = "org.scala-lang" %% "toolkit" % toolkitV // 未初期化の参照!
val toolkitTest = "org.scala-lang" %% "toolkit-test" % toolkitV // 未初期化の参照!
val toolkitV = "0.2.0"
build.sbt に未初期化の事前参照を含む場合、sbt は NullPointerException による java.lang.ExceptionInInitializerError を投げて起動に失敗する。これをコンパイラに直させる方法の 1つとして、変数に lazy を付けて遅延変数として定義するという方法がある:
lazy val toolkit = "org.scala-lang" %% "toolkit" % toolkitV
lazy val toolkitTest = "org.scala-lang" %% "toolkit-test" % toolkitV
lazy val toolkitV = "0.2.0"
何でも lazy val を付けるのに渋い顔をする人もいるかもしれないが、僕たちは Scala 3 の lazy val は効率が良く、ビルド定義をコピー・ペーストに対して堅牢にすると思っている。