1. タスク/設定: 動機

タスク/設定: 動機 

このページでは、タスクと設定システムの動機について説明します。タスクと設定の使用方法については、入門ガイドタスクページで説明されているため、すでに理解している必要があります。

タスクシステムの重要な側面は、ビルドにおける2つの一般的な関連するステップを組み合わせることです。

  1. 他のタスクが実行されていることを確認する。
  2. そのタスクの結果を使用する。

以前のバージョンの sbt では、これらのステップを以下を使用して個別に設定していました。

  1. 依存関係宣言
  2. 何らかの形式の共有状態

これらを組み合わせることの利点を確認するために、Scala で変数の初期化を遅延させる場合と比較してみましょう。次の Scala コードは、初期化が遅延される値を公開するための悪い方法です。

// Define a variable that will be initialized at some point
// We don't want to do it right away, because it might be expensive
var foo: Foo = _

// Define a function to initialize the variable
def makeFoo(): Unit = ... initialize foo ...

典型的な使い方は次のとおりです。

makeFoo()
doSomething(foo)

この例は、やや誇張されていますが、2段階のタスク定義とほぼ同じ状況であると主張します。これが悪い具体的な理由としては、以下が挙げられます。

  1. クライアントは、最初に `makeFoo()` を呼び出す必要があることを知る必要があります。
  2. `foo` は他のコードによって変更される可能性があります。たとえば、`def makeFoo2()` が存在する可能性があります。
  3. `foo` へのアクセスはスレッドセーフではありません。

最初のポイントはタスクの依存関係を宣言するようなもので、2番目のポイントは2つのタスクが同じ状態 (プロジェクト変数またはファイル) を変更するようなもので、3番目のポイントは同期されていない共有状態の結果です。

Scala では、これを簡単に修正できる組み込み機能があります。`lazy val` です。

lazy val foo: Foo = ... initialize foo ...

使用例は次のとおりです。

doSomething(foo)

ここでは、`lazy val` は、スレッドセーフ性、アクセス前の初期化の保証、および不変性をすべて1つの DRY 構成で提供します。 sbt のタスクシステムは、タスクに対して `lazy val` が上記の悪い例に対して行ったのと同じことを行います (さらに多くのことを行いますが、ここでは説明しません)。

タスク定義では、その入力とその出力の型を宣言する必要があります。 sbt は、入力タスクが実行されたことを確認し、その結果をタスクを実装する関数に提供します。この関数は独自の結果を生成します。他のタスクはこの結果を使用して、タスクが (1回) 実行されたことを保証し、そのプロセスでスレッドセーフとタイプセーフを実現できます。

タスク定義の一般的な形式は次のとおりです。

myTask := {
  val a: A = aTask.value
  val b: B = bTask.value
  ... do something with a, b and generate a result ...
}

(これはタスクの背後にあるアイデアについての議論のみを目的としているため、使用方法の詳細はsbt タスクページを参照してください。) ここでは、`aTask` は `A` 型の結果を生成し、`bTask` は `B` 型の結果を生成すると想定されています。

適用 

例として、プロジェクトのバイナリ jar、ソース jar、およびドキュメント jar を含む zip ファイルを生成することを考えてみましょう。最初に、どのタスクが jar を生成するかを決定します。この場合、入力タスクは、メインの `Compile` スコープの `packageBin`、`packageSrc`、および `packageDoc` です。これらの各タスクの結果は、生成された jar のファイルです。 zip ファイルタスクは、これらのパッケージタスクをマッピングし、それらの出力を zip ファイルに含めることによって定義されます。グッドプラクティスとして、この zip のファイルを返して、他のタスクがこの zip タスクをマッピングできるようにします。

zip := {
    val bin: File = (Compile / packageBin).value
    val src: File = (Compile / packageSrc).value
    val doc: File = (Compile / packageDoc).value
    val out: File = zipPath.value
    val inputs: Seq[(File,String)] = Seq(bin, src, doc) x Path.flat
    IO.zip(inputs, out)
    out
}

`val inputs` 行は、入力ファイルが zip 内のパスにどのようにマッピングされるかを定義します。詳細はファイルのマッピングを参照してください。明示的な型は必須ではありませんが、明確にするために含まれています。

`zipPath` 入力は、zip ファイルの場所を定義するためのカスタムタスクになります。例えば

zipPath := target.value / "out.zip"