このページでは、sbtのビルド定義について説明します。これには、「理論」とbuild.sbtの構文が含まれます。sbt 1.9.8などの最新バージョンのsbtをインストール済みであり、sbtの使い方を理解し、入門ガイドの前のページを読んでいることを前提としています。
このページでは、build.sbtのビルド定義について説明します。
ビルド定義の一部として、ビルドで使用するsbtのバージョンを指定します。これにより、異なるバージョンのsbtランチャーを持つ人が、一貫した結果で同じプロジェクトをビルドできます。これを行うには、sbtバージョンを次のように指定するproject/build.propertiesという名前のファイルを作成します。
sbt.version=1.9.8
必要なバージョンがローカルで使用できない場合、sbtランチャーがダウンロードします。このファイルが存在しない場合、sbtランチャーは任意のバージョンを選択しますが、ビルドが移植できなくなるため推奨されません。
ビルド定義はbuild.sbtで定義され、(Project型の)一連のプロジェクトで構成されます。プロジェクトという用語はあいまいな場合があるため、このガイドではサブプロジェクトと呼ぶことがよくあります。
たとえば、build.sbtで、現在のディレクトリにあるサブプロジェクトを次のように定義します。
lazy val root = (project in file("."))
.settings(
name := "Hello",
scalaVersion := "2.12.7"
)
各サブプロジェクトは、キーと値のペアによって構成されます。
たとえば、1つのキーはnameで、サブプロジェクトの名前である文字列値にマッピングされます。キーと値のペアは、次のように.settings(...)メソッドの下にリストされます。
lazy val root = (project in file("."))
.settings(
name := "Hello",
scalaVersion := "2.12.7"
)
build.sbtはサブプロジェクトを定義します。サブプロジェクトは、build.sbtドメイン固有言語(DSL)を使用して、設定式と呼ばれるキーと値のペアのシーケンスを保持します。
ThisBuild / organization := "com.example"
ThisBuild / scalaVersion := "2.12.18"
ThisBuild / version := "0.1.0-SNAPSHOT"
lazy val root = (project in file("."))
.settings(
name := "hello"
)
build.sbt DSLを詳しく見てみましょう:
各エントリは設定式と呼ばれます。それらの中にはタスク式とも呼ばれるものもあります。このページでは、後でその違いについて詳しく説明します。
設定式は3つの部分で構成されます。
:=です。左側では、name、version、およびscalaVersionがキーです。キーは、SettingKey[T]、TaskKey[T]、またはInputKey[T]のインスタンスであり、Tは予期される値の型です。キーの種類については、以下で説明します。
キーnameはSettingKey[String]として型付けされているため、nameに対する:=演算子も特にStringとして型付けされます。間違った値の型を使用すると、ビルド定義はコンパイルされません。
lazy val root = (project in file("."))
.settings(
name := 42 // will not compile
)
build.sbtには、val、lazy val、およびdefを混在させることもできます。トップレベルのobjectとclassはbuild.sbtでは許可されていません。これらはScalaソースファイルとしてproject/ディレクトリに配置する必要があります。
キーには3つの種類があります。
SettingKey[T]:一度だけ評価される値のキー(値はサブプロジェクトのロード時に計算され、保持されます)。TaskKey[T]:タスクと呼ばれる値のキー。参照されるたびに(Scala関数と同様に)評価され、副作用が発生する可能性があります。InputKey[T]:コマンドライン引数を入力として持つタスクのキー。詳細については、入力タスクを確認してください。組み込みキーは、Keysというオブジェクトのフィールドにすぎません。build.sbtには暗黙的にimport sbt.Keys._があるため、sbt.Keys.nameはnameとして参照できます。
カスタムキーは、それぞれの作成メソッド(settingKey、taskKey、およびinputKey)で定義できます。各メソッドは、キーに関連付けられた値の型と説明を期待します。キーの名前は、キーが割り当てられているvalから取得されます。たとえば、helloという名前の新しいタスクのキーを定義するには、次のようにします。
lazy val hello = taskKey[Unit]("An example task")
ここでは、設定に加えて、.sbtファイルにvalとdefを含めることができるという事実を使用しました。このような定義はすべて、ファイル内の定義場所に関係なく、設定の前に評価されます。
注:通常、初期化順序の問題を回避するために、valの代わりにlazy valが使用されます。
TaskKey[T]はタスクを定義すると言われます。タスクは、compileやpackageなどの操作です。それらはUnit(UnitはScalaのvoid)を返すことも、タスクに関連する値を返すこともできます。たとえば、packageはTaskKey[File]であり、その値は作成されるjarファイルです。
インタラクティブなsbtプロンプトでcompileと入力するなどしてタスクの実行を開始するたびに、sbtは関係するタスクを正確に1回再実行します。
sbtのサブプロジェクトを記述するキーと値のペアは、名前などの設定に対して固定された文字列値を保持できますが、compileなどのタスクに対しては何らかの実行可能なコードを保持する必要があります。その実行可能なコードが最終的に文字列を返した場合でも、毎回再実行する必要があります。
指定されたキーは常に、タスクまたはプレーンな設定のいずれかを参照します。つまり、「タスク性」(毎回再実行するかどうか)は値ではなく、キーのプロパティです。
ビルド定義に現在存在する設定キーのリストは、sbtプロンプトでsettingsまたはsettings -vと入力することで取得できます。
同様に、現在定義されているタスクキーのリストは、tasksまたはtasks -vと入力することで取得できます。sbtプロンプトで一般的に使用される組み込みタスクの説明については、コマンドラインリファレンスも参照できます。
キーは、次の条件を満たす場合に結果のリストに出力されます。
nameやscalaVersionなど)である場合詳細については、sbtプロンプトでhelp <key>と入力することもできます。
:= を使うと、設定に値を、タスクに計算を割り当てることができます。設定の場合、値はプロジェクトのロード時に一度だけ計算されます。タスクの場合、計算はタスクが実行されるたびに再実行されます。
例えば、前のセクションの hello タスクを実装するには、
lazy val hello = taskKey[Unit]("An example task")
lazy val root = (project in file("."))
.settings(
hello := { println("Hello!") }
)
プロジェクトの名前を定義したときに、すでに設定を定義する例を見ました。
lazy val root = (project in file("."))
.settings(
name := "hello"
)
型システムの観点から見ると、タスクキーから作成された Setting は、設定キーから作成されたものとはわずかに異なります。taskKey := 42 は Setting[Task[T]] を生成しますが、settingKey := 42 は Setting[T] を生成します。ほとんどの場合、これは違いを生じません。タスクキーはタスクが実行されると、依然として型 T の値を生成します。
T と Task[T] の型の違いは、設定はタスクに依存できないということを意味します。なぜなら、設定はプロジェクトのロード時に一度だけ評価され、再実行されないからです。詳細については、タスクグラフを参照してください。
sbt シェルでは、任意のタスクの名前を入力してそのタスクを実行できます。これが、compile と入力すると compile タスクが実行される理由です。compile はタスクキーです。
タスクキーではなく設定キーの名前を入力すると、設定キーの値が表示されます。タスクキー名を入力するとタスクが実行されますが、結果の値は表示されません。タスクの結果を確認するには、単に <task name> と入力するのではなく、show <task name> を使用します。キー名の規約は、コマンドライン名と Scala の識別子が同じになるように camelCase を使用することです。
任意のキーの詳細については、sbt のインタラクティブプロンプトで inspect <keyname> と入力してください。inspect が表示する情報の中には、まだ理解できないものもありますが、上部には設定の値の型と設定の簡単な説明が表示されます。
build.sbt の先頭に import 文を記述できます。空白行で区切る必要はありません。
次のように、いくつかの暗黙的なデフォルトのインポートがあります。
import sbt._
import Keys._
(さらに、自動プラグインがある場合は、autoImport の下にマークされた名前がインポートされます。)
設定は、.settings(...) 呼び出しの中に配置する代わりに、build.sbt ファイルに直接記述できます。これを「ベアスタイル」と呼びます。
ThisBuild / version := "1.0"
ThisBuild / scalaVersion := "2.12.18"
この構文は、ThisBuild スコープの設定とプラグインの追加に推奨されます。スコープとプラグインについては、後のセクションを参照してください。
サードパーティのライブラリに依存するには、2つのオプションがあります。1つは lib/ に jar をドロップする(アンマネージド依存関係)、もう1つはマネージド依存関係を追加することです。build.sbt では次のようになります。
val derby = "org.apache.derby" % "derby" % "10.4.1.3"
ThisBuild / organization := "com.example"
ThisBuild / scalaVersion := "2.12.18"
ThisBuild / version := "0.1.0-SNAPSHOT"
lazy val root = (project in file("."))
.settings(
name := "Hello",
libraryDependencies += derby
)
これが、Apache Derby ライブラリのバージョン 10.4.1.3 にマネージド依存関係を追加する方法です。
libraryDependencies キーには、:= ではなく += を使用することと、% メソッドという2つの複雑な点があります。+= はキーの古い値を置き換えるのではなく追加します。これについては タスクグラフで説明します。% メソッドは、文字列から Ivy モジュール ID を構築するために使用されます。これについては ライブラリ依存関係で説明します。
ライブラリ依存関係の詳細については、この入門ガイドの後半までスキップします。後でそれについて説明する ページ全体があります。