このページでは、独自のsettingとtaskの作成方法について説明します。
このページを理解するには、入門ガイドの前のページ、特にbuild.sbtとタスクグラフを読んでください。
Keysには、キーの定義方法を示す多くの例が含まれています。ほとんどのキーはDefaultsに実装されています。
キーには3つの型のいずれかがあります。SettingKey
とTaskKey
については.sbtビルド定義で説明されています。入力タスクのページでInputKey
について説明します。
Keysからの例
val scalaVersion = settingKey[String]("The version of Scala used for building.")
val clean = taskKey[Unit]("Deletes files produced by the build, such as generated sources, compiled classes, and task caches.")
キーコンストラクタには、キーの名前("scalaVersion"
)とドキュメント文字列("ビルドに使用されるscalaのバージョン。"
)の2つの文字列パラメータがあります。
.sbtビルド定義から、SettingKey[T]
の型パラメータT
は設定の値の型を示し、TaskKey[T]
のT
はタスクの結果の型を示していることを思い出してください。また、.sbtビルド定義から、設定はプロジェクトのリロードまで固定された値を持ち、タスクは毎回「タスク実行」(sbtインタラクティブプロンプトまたはバッチモードでコマンドが入力されるたびに)再計算されることを思い出してください。
キーは.sbtファイル、.scalaファイル、または自動プラグインで定義できます。有効な自動プラグインのautoImport
オブジェクトの下にあるすべてのval
は、.sbt
ファイルに自動的にインポートされます。
タスクのキーを定義したら、タスク定義でそれを完成させる必要があります。独自のタスクを定義することも、既存のタスクを再定義することもできます。どちらの場合も見た目は同じです。:=
を使用して、タスクキーにコードを関連付けます。
val sampleStringTask = taskKey[String]("A sample string task.")
val sampleIntTask = taskKey[Int]("A sample int task.")
ThisBuild / organization := "com.example"
ThisBuild / version := "0.1.0-SNAPSHOT"
ThisBuild / scalaVersion := "2.12.18"
lazy val library = (project in file("library"))
.settings(
sampleStringTask := System.getProperty("user.home"),
sampleIntTask := {
val sum = 1 + 2
println("sum: " + sum)
sum
}
)
タスクに依存関係がある場合、タスクグラフで説明されているように、value
を使用してその値を参照します。
タスクの実装で最も難しい部分は、多くの場合、sbt固有のものではありません。タスクは単なるScalaコードです。難しい部分は、実行しようとしていることを行うタスクの「本体」を記述することです。たとえば、HTMLをフォーマットしようとしている場合、HTMLライブラリを使用したい場合があります(ビルド定義にライブラリ依存関係を追加し、おそらくHTMLライブラリに基づいてコードを記述します)。
sbtにはいくつかのユーティリティライブラリと便利な関数があり、特にIOの便利なAPIを使用してファイルやディレクトリを操作できます。
value
を使用してカスタムタスクから他のタスクに依存する場合、重要な詳細としてタスクの実行セマンティクスがあります。実行セマンティクスとは、これらのタスクが正確にいつ評価されるかということです。
例えばsampleIntTask
の場合、タスクの本体の各行は厳密に順番に評価される必要があります。つまり、シーケンシャルセマンティクスです。
sampleIntTask := {
val sum = 1 + 2 // first
println("sum: " + sum) // second
sum // third
}
実際には、JVMはsum
を3
にインライン化することがありますが、タスクの観測可能な効果は、各行が順番に実行された場合と同じままです。
今度は、さらに2つのカスタムタスクstartServer
とstopServer
を定義し、sampleIntTask
を次のように変更してみます。
val startServer = taskKey[Unit]("start server")
val stopServer = taskKey[Unit]("stop server")
val sampleIntTask = taskKey[Int]("A sample int task.")
val sampleStringTask = taskKey[String]("A sample string task.")
ThisBuild / organization := "com.example"
ThisBuild / version := "0.1.0-SNAPSHOT"
ThisBuild / scalaVersion := "2.12.18"
lazy val library = (project in file("library"))
.settings(
startServer := {
println("starting...")
Thread.sleep(500)
},
stopServer := {
println("stopping...")
Thread.sleep(500)
},
sampleIntTask := {
startServer.value
val sum = 1 + 2
println("sum: " + sum)
stopServer.value // THIS WON'T WORK
sum
},
sampleStringTask := {
startServer.value
val s = sampleIntTask.value.toString
println("s: " + s)
s
}
)
sbtインタラクティブプロンプトからsampleIntTask
を実行すると、次のようになります。
> sampleIntTask
stopping...
starting...
sum: 3
[success] Total time: 1 s, completed Dec 22, 2014 5:00:00 PM
何が起こったのかを確認するために、sampleIntTask
の図解表記を見てみましょう。
単純なScalaメソッド呼び出しとは異なり、タスクに対してvalue
メソッドを呼び出すと、厳密に評価されません。代わりに、sampleIntTask
がstartServer
とstopServer
タスクに依存していることを示すプレースホルダーとして機能します。あなたがsampleIntTask
を呼び出すと、sbtのタスクエンジンは
sampleIntTask
を評価する前にタスク依存関係を評価します(部分的な順序付け)最後の点を示すために、sbtインタラクティブプロンプトからsampleStringTask
を実行できます。
> sampleStringTask
stopping...
starting...
sum: 3
s: 3
[success] Total time: 1 s, completed Dec 22, 2014 5:30:00 PM
sampleStringTask
はstartServer
とsampleIntTask
タスクの両方に依存し、sampleIntTask
もstartServer
タスクに依存しているため、タスク依存関係として2回表示されます。これが単純なScalaメソッド呼び出しであれば2回評価されますが、value
は単にタスク依存関係を示しているため、1回だけ評価されます。以下は、sampleStringTask
の評価の図解表記です。
タスク依存関係を重複排除しないと、Test / test
のタスク依存関係としてTest / compile
が複数回表示されるため、test
タスクが呼び出されたときにテストソースコードが何度もコンパイルされることになります。
stopServer
タスクをどのように実装すべきでしょうか?クリーンアップタスクの概念は、タスクの依存関係を追跡することに関することであるため、タスクの実行モデルには適合しません。最後の操作は、他の中間タスクに依存するタスクになるはずです。例えば、stopServer
はsampleStringTask
に依存する必要があります。その時点で、stopServer
はsampleStringTask
になります。
lazy val library = (project in file("library"))
.settings(
startServer := {
println("starting...")
Thread.sleep(500)
},
sampleIntTask := {
startServer.value
val sum = 1 + 2
println("sum: " + sum)
sum
},
sampleStringTask := {
startServer.value
val s = sampleIntTask.value.toString
println("s: " + s)
s
},
sampleStringTask := {
val old = sampleStringTask.value
println("stopping...")
Thread.sleep(500)
old
}
)
それが機能することを示すために、インタラクティブプロンプトからsampleStringTask
を実行します。
> sampleStringTask
starting...
sum: 3
s: 3
stopping...
[success] Total time: 1 s, completed Dec 22, 2014 6:00:00 PM
何かを別の何かが起こった後に確実に実行させるもう一つの方法は、Scalaを使用することです。例えば、project/ServerUtil.scala
に単純な関数を次のように実装できます。
sampleIntTask := {
ServerUtil.startServer
try {
val sum = 1 + 2
println("sum: " + sum)
} finally {
ServerUtil.stopServer
}
sum
}
プレーンなメソッド呼び出しはシーケンシャルセマンティクスに従うため、すべてが順序どおりに実行されます。重複排除がないため、その点に注意する必要があります。
多くのカスタムコードがある場合は、複数のビルドで再利用するためにプラグインに移動することを検討してください。
前に触れられていたように、そしてここで詳しく説明されているように、プラグインの作成は非常に簡単です。
このページは簡単な紹介でした。タスクページには、カスタムタスクについてさらに多くの情報があります。