このページでは、コア設定エンジンについて少し説明します。 これは、sbtの外部で使用する場合に役立つ場合があります。 また、sbtが内部でどのように機能するかを理解するのに役立つ場合もあります。
ドキュメントは2つの部分で構成されています。最初の部分は、設定エンジンに基づいて構築された設定システムの例を示しています。2番目の部分は、sbtの設定システムが設定エンジンに基づいてどのように構築されているかについてコメントしています。これにより、コア設定エンジンが正確に何を提供し、sbt設定システムのようなものを構築するために何が必要かを明確にするのに役立つ場合があります。
この例を実行するには、まず次のbuild.sbtファイルを使用して新しいプロジェクトを作成します
libraryDependencies += "org.scala-sbt" %% "collections" % sbtVersion.value
resolvers += sbtResolver.value
次に、次の例をソースファイルSettingsExample.scalaとSettingsUsage.scalaに記述します。最後に、sbtを実行し、consoleを使用してREPLを入力します。以下で説明する出力を確認するには、SettingsUsageを入力します。
例の最初の部分は、カスタム設定システムを定義します。主な部分は3つあります
Scope型を定義します。Scope(プラスAttributeKey)をStringに変換する関数を定義します。Scopeのシーケンスを定義する委譲関数を定義します。4番目もありますが、その使用法はおそらく現時点ではsbtに固有のものです。この例では、この部分に簡単な実装を使用しています。
SettingsExample.scala:
import sbt._
/** Define our settings system */
// A basic scope indexed by an integer.
final case class Scope(index: Int)
// Extend the Init trait.
// (It is done this way because the Scope type parameter is used everywhere in Init.
// Lots of type constructors would become binary, which as you may know requires lots of type lambdas
// when you want a type function with only one parameter.
// That would be a general pain.)
object SettingsExample extends Init[Scope]
{
// Provides a way of showing a Scope+AttributeKey[_]
val showFullKey: Show[ScopedKey[_]] = new Show[ScopedKey[_]] {
def apply(key: ScopedKey[_]) = key.scope.index + "/" + key.key.label
}
// A sample delegation function that delegates to a Scope with a lower index.
val delegates: Scope => Seq[Scope] = { case s @ Scope(index) =>
s +: (if(index <= 0) Nil else delegates(Scope(index-1)) )
}
// Not using this feature in this example.
val scopeLocal: ScopeLocal = _ => Nil
// These three functions + a scope (here, Scope) are sufficient for defining our settings system.
}
この部分では、定義したシステムを使用する方法を示します。最終的な結果は、Settings[Scope]値です。この型は基本的にマッピングScope -> AttributeKey[T] -> Option[T]です。詳細については、Settings APIドキュメントを参照してください。
SettingsUsage.scala:
/** Usage Example **/
import sbt._
import SettingsExample._
import Types._
object SettingsUsage {
// Define some keys
val a = AttributeKey[Int]("a")
val b = AttributeKey[Int]("b")
// Scope these keys
val a3 = ScopedKey(Scope(3), a)
val a4 = ScopedKey(Scope(4), a)
val a5 = ScopedKey(Scope(5), a)
val b4 = ScopedKey(Scope(4), b)
// Define some settings
val mySettings: Seq[Setting[_]] = Seq(
setting( a3, value( 3 ) ),
setting( b4, map(a4)(_ * 3)),
update(a5)(_ + 1)
)
// "compiles" and applies the settings.
// This can be split into multiple steps to access intermediate results if desired.
// The 'inspect' command operates on the output of 'compile', for example.
val applied: Settings[Scope] = make(mySettings)(delegates, scopeLocal, showFullKey)
// Show results.
for(i <- 0 to 5; k <- Seq(a, b)) {
println( k.label + i + " = " + applied.get( Scope(i), k) )
}
}
実行すると、次の出力が生成されます
a0 = None
b0 = None
a1 = None
b1 = None
a2 = None
b2 = None
a3 = Some(3)
b3 = None
a4 = Some(3)
b4 = Some(9)
a5 = Some(4)
b5 = Some(9)
Noneの結果の場合、値を定義したことはなく、委譲する値もありませんでした。a3の場合、明示的に3になるように定義しました。a4は定義されていなかったため、delegates関数に従ってa3に委譲します。b4はa4の値(a3に委譲するため3)を取得し、3を掛けますa5はa5の前の値+1として定義され、a5の前の値は定義されていないため、a4に委譲し、3+1=4になります。b5は明示的に定義されていないため、b4に委譲されるため、同様に9に等しくなりますsbtは、ビルドでの設定の標準的な使用法に対して、ここで示すものよりも複雑なスコープを定義します。 このスコープには、プロジェクト軸、構成軸、タスク軸、および追加軸の4つのコンポーネントがあります。 各コンポーネントは、Zero(特定の値なし)、This(現在のコンテキスト)、またはSelect(特定の値を含む)のいずれかになります。 sbtは、コンテキストに応じてZeroまたはSelectのいずれかにThis_を解決します。
たとえば、プロジェクトでは、Thisプロジェクト軸は、定義プロジェクトを参照するSelectになります。Thisである他のすべての軸は、Zeroに変換されます。 inConfigやinTaskなどの関数は、Thisを特定の値のSelectに変換します。 たとえば、inConfig(Compile)(someSettings)は、軸の値がThisの場合、someSettings内のすべての設定の構成軸をSelect(Compile)に変換します。
したがって、例とsbtのスコープから、コア設定エンジンはスコープの構造にあまり制約を課していないことがわかります。必要なのは、delegates関数Scope => Seq[Scope]とdisplay関数だけです。状況に合ったスコープ型を選択できます。
app、value、update、および関連するメソッドは、設定を構築するためのコアメソッドです。これらのメソッドは通常直接使用されず、より高レベルの抽象化でラップされているため、この例は明らかにsbtのインターフェースとは異なります。
コア設定エンジンを使用すると、他の設定にアクセスするためにHListを使用します。 sbtの高レベルシステムでは、N = 1〜9のTupleNおよびFunctionNのHListのラッパーがあります(ただし、Tuple1は実際には使用されません)。任意のarityを扱う場合、これらのラッパーを可能な限り最高レベルで作成すると便利です。これは、ラッパーが定義されると、すべてのNに対してコードを複製する必要があるためです。トップレベルでラッパーを作成することにより、これには1レベルの重複のみが必要です。
さらに、sbtはタスクエンジンを設定システムに均一に統合します。 基になる設定エンジンにはタスクの概念はありません。 これが、sbtがSettingKey型とTaskKey型を使用する理由です。 基になるTaskKey[T]のメソッドは、基本的に基になるSettingKey[Task[T]]で動作するように変換されます(両方とも基になるAttributeKeyをラップします)。
たとえば、SettingKey *a*のa := 3は、おおまかにsetting(a, value(3))に変換されます。 TaskKey *a*の場合、おおまかにsetting(a, value( task { 3 } ) )に変換されます。 詳細については、main/Structure.scalaを参照してください。
sbtは、ファイル(build.sbtおよびBuild.scala)でこれらの設定を定義する方法も提供します。これは、基本的な解析を使用してbuild.sbtで行われ、結果として得られるコードのチャンクをcompile/Eval.scalaに渡します。すべての定義について、sbtはクラスパスと再コンパイルプロセスを管理して、設定を取得します。また、ユーザーがプロジェクト、タスク、および構成の委譲を定義する方法を提供します。これは、最終的にdelegates関数で使用されます。