1. 状態とアクション

状態とアクション 

State は、sbtで使用可能なすべての情報のエントリポイントです。主要なメソッドは次のとおりです。

  • definedCommands: Seq[Command] は、登録されているすべてのコマンド定義を返します。
  • remainingCommands: List[Exec] は、実行される残りのコマンドを返します。
  • attributes: AttributeMap は、汎用データを含みます。

コマンドのアクション部分は、作業を実行し、Stateを変換します。以降のセクションでは、State => State変換について説明します。前述のように、コマンドは通常、解析された値も処理します:(State, T) => State

コマンド関連データ 

コマンドは、現在登録されているコマンドまたは実行されるコマンドを変更できます。これは、アクション部分で、コマンドに提供された(不変の)Stateを変換することで行われます。追加のパワーコマンドを登録する関数は、次のようになります。

val powerCommands: Seq[Command] = ...

val addPower: State => State =
  (state: State) =>
    state.copy(definedCommands =
      (state.definedCommands ++ powerCommands).distinct
    )

これは、現在のコマンドを取得し、新しいコマンドを追加し、重複を削除します。あるいは、Stateには上記の処理を行うための便利なメソッドがあります。

val addPower2 = (state: State) => state ++ powerCommands

実行する残りのコマンドを変更する関数の例をいくつか示します。

val appendCommand: State => State =
  (state: State) =>
    state.copy(remainingCommands = state.remainingCommands :+ "cleanup")

val insertCommand: State => State =
  (state: State) =>
    state.copy(remainingCommands = "next-command" +: state.remainingCommands)

最初の関数は、現在指定されているすべてのコマンドの実行後に実行されるコマンドを追加します。2番目の関数は、次に実行されるコマンドを挿入します。残りのコマンドは、挿入されたコマンドが完了した後に実行されます。

コマンドが失敗し、実行を続行しないことを示すには、state.failを返します。

(state: State) => {
  val success: Boolean = ...
  if(success) state else state.fail
}

プロジェクト関連データ 

プロジェクト関連情報は、attributesに保存されます。通常、コマンドはこれへ直接アクセスせず、代わりに最も有用な情報を抽出するための便利なメソッドを使用します。

val state: State
val extracted: Extracted = Project.extract(state)
import extracted._

Extracted は、以下を提供します。

  • 現在のビルドとプロジェクトへのアクセス(currentRef
  • 初期化されたプロジェクト設定データへのアクセス(structure.data
  • セッションSettingと、.sbtファイルおよび.scalaファイルからの元の永続的な設定へのアクセス(それぞれsession.appendとsession.original)
  • ビルドコンテキストでScala式を評価するための現在のEvalインスタンスへのアクセス。

プロジェクトデータ 

すべてのプロジェクトデータは、structure.dataに保存されます。これはsbt.Settings[Scope]型です。通常、T型の情報は次の方法で取得します。

val key: SettingKey[T]
val scope: Scope
val value: Option[T] = key in scope get structure.data

ここで、SettingKey[T]は通常Keysから取得され、たとえば.sbtファイルで設定を定義するために使用される型と同じです。Scopeは、キーが取得されるスコープを選択します。必要なスコープ軸のみを指定するために使用できるinの便利なオーバーロードがあります。Structure.scalaで、inおよび設定インターフェースの他の部分が定義されている場所を参照してください。いくつかの例を示します。

import Keys._
val extracted: Extracted
import extracted._

// get name of current project
val nameOpt: Option[String] = (currentRef / name).get(structure.data)

// get the package options for the `Test/packageSrc` task or Nil if none are defined
val pkgOpts: Seq[PackageOption] = (currentRef / Test / packageSrc / packageOptions).get(structure.data).getOrElse(Nil)

BuildStructureには、ビルドとプロジェクトの関係に関する情報が含まれています。主要なメンバーは次のとおりです。

units: Map[URI, LoadedBuildUnit]
root: URI

URIはビルドを識別し、rootはロードされた最初のビルドを識別します。LoadedBuildUnitは、単一のビルドに関する情報を提供します。LoadedBuildUnitの主要なメンバーは次のとおりです。

// Defines the base directory for the build
localBase: File

// maps the project ID to the Project definition
defined: Map[String, ResolvedProject]

ResolvedProjectは、project/Build.scalaで使用されるProjectと同じ情報を持っていますが、ProjectReferencesProjectRefに解決されます。

クラスパス 

sbtのクラスパスはSeq[Attributed[File]]型です。これにより、クラスパスエントリに任意の情報をタグ付けできます。sbtは現在これを使い、エントリにAnalysisを関連付けます。これは、マルチプロジェクトのインクリメンタルリコンパイルに必要な情報を管理する方法です。また、管理されたエントリ(依存関係管理によって取得されたエントリ)にModuleIDとArtifactを関連付けます。基になるSeq[File]のみが必要な場合は、filesを使用します。

val attributedClasspath: Seq[Attribute[File]] = ...
val classpath: Seq[File] = attributedClasspath.files

タスクの実行 

コマンド(*別のタスクからはなく*)から特定のプロジェクトタスクを実行し、その結果を取得することが役立つ場合があります。たとえば、IDE関連のコマンドはプロジェクトからクラスパスを取得したり、タスクはコンパイルの結果を分析したりする場合があります。関連するメソッドはProject.runTaskで、次のシグネチャを持ちます。

def runTask[T](taskKey: ScopedKey[Task[T]], state: State,
  checkCycles: Boolean = false): Option[(State, Result[T])]

たとえば、

val eval: State => State = (state: State) => {

    // This selects the main 'compile' task for the current project.
    //   The value produced by 'compile' is of type inc.Analysis,
    //   which contains information about the compiled code.
    val taskKey = Compile / Keys.compile

    // Evaluate the task
    // None if the key is not defined
    // Some(Inc) if the task does not complete successfully (Inc for incomplete)
    // Some(Value(v)) with the resulting value
    val result: Option[(State, Result[inc.Analysis])] = Project.runTask(taskKey, state)
    // handle the result
    result match
    {
        case None => // Key wasn't defined.
        case Some((newState, Inc(inc))) => // error detail, inc is of type Incomplete, use Incomplete.show(inc.tpe) to get an error message
        case Some((newState, Value(v))) => // do something with v: inc.Analysis
    }
}

特定のプロジェクトのテストクラスパスを取得するには、このキーを使用します。

val projectRef: ProjectRef = ...
val taskKey: Task[Seq[Attributed[File]]] =
  (projectRef / Test / Keys.fullClasspath)

タスクでのStateの使用 

タスクから現在のStateにアクセスするには、stateタスクを入力として使用します。たとえば、

myTask := ... state.value ...

タスクでのStateの更新 

タスクでsbtの状態を更新することも可能です。そのためには、タスクはStateTransform型を返す必要があります。タスクの評価が完了すると、状態が変換されます。StateTransformは、前のStateの値を受け入れて新しい状態を生成するState => State関数で構成されます。たとえば

import complete.DefaultParsers._
val counter = AttributeKey[Int]("counter")
val setCounter = inputKey[StateTransform]("Set the value of the counter attribute")
setCounter := {
  val count = (Space ~> IntBasic).parsed
  StateTransform(_.put(counter, count))
}

は、カウンター属性を何らかの値に設定する入力タスクsetCounterを作成します。