このドキュメントでは、sbt の設計とコーディングスタイル全体を網羅するコア原則について詳しく説明します。sbt のコア原則は非常に簡単に述べることができます。
Type を持たせるべきであり、実用的な範囲で強制する。これらの原則を念頭に置いて、sbt のコア設計を見ていきましょう。
これは sbt を起動したときに最初に遭遇するものです。sbt のコマンドエンジンは、ビルド状態を使用してユーザーリクエストを処理する手段です。コマンドエンジンは、本質的にユーザーリクエストを実行するために、ビルド状態に状態変換を適用する手段です。
sbt では、コマンドは現在のビルド状態 (sbt.State) を受け取り、次の状態を生成する関数です。言い換えれば、これらは基本的に sbt.State => sbt.State の関数です。しかし、実際には、コマンドは実際には、何らかの文字列入力を受け取り、それに対して作用し、次のビルド状態を返す文字列プロセッサです。
したがって、sbt 全体が sbt.State クラスによって駆動されています。このクラスは、カスタムコードとプラグインに耐性がある必要があるため、潜在的なクライアントの状態を保存するメカニズムが必要です。動的言語では、これはオブジェクト上で直接行うことができます。
Scala での単純なアプローチは、Map<String,Any> を使用することです。ただし、これはテナント #1 に違反します。すべてに Type を持たせるべきです。そこで、sbt は AttributeMap という新しいタイプのマップを定義します。AttributeMap は、キーが文字列と値に対して予期される Type の両方であるキーと値のストレージメカニズムです。
型安全な AttributeKey キーは次のようになります。
sealed trait AttributeKey[T] {
/** The label is the identifier for the key and is camelCase by convention. */
def label: String
/** The runtime evidence for ``T`` */
def manifest: Manifest[T]
}
これらのキーは、label (string) といくつかのランタイム型情報 (manifest) の両方を格納します。AttributeMap に何かを置いたり取得したりするには、まずこれらのキーの 1 つを構築する必要があります。AttributeMap の基本定義を見てみましょう。
trait AttributeMap {
/** Gets the value of type ``T`` associated with the key ``k`` or ``None`` if no value is associated.
* If a key with the same label but a different type is defined, this method will return ``None``. */
def get[T](k: AttributeKey[T]): Option[T]
/** Adds the mapping ``k -> value`` to this map, replacing any existing mapping for ``k``.
* Any mappings for keys with the same label but different types are unaffected. */
def put[T](k: AttributeKey[T], value: T): AttributeMap
}
ビルド状態とは何かを定義したので、それを動的に構築する方法が必要です。sbt では、これは Setting[_] シーケンスを通じて行われます。
設定は、ビルド状態の AttributeMap 内の特定の AttributeKey[_] の値を構築する手段を表します。設定は 2 つの部分で構成されます。
AttributeKey[T]。Initialize[T] オブジェクト。sbt の初期化時間は、基本的にこれらの Setting[_] オブジェクトのシーケンスを取得し、それらの初期化オブジェクトを実行し、値を AttributeMap に格納するだけです。つまり、キーの既存の値を上書きすることは、そうする Setting[_] をシーケンスの最後に追加するのと同じくらい簡単です。
興味深いのは、Initialize[T] がビルド状態の他の AttributeKey[_] に依存できることです。各 Initialize[_] は、ビルド状態の AttributeMap 内の任意の AttributeKey[_] から値を取得して、その値を計算できます。sbt は、Initialize[_] の依存関係に関していくつかのことを保証します。
1 つの Initialize[_] が別の Initialize[_] キーに依存する場合、
そのキーに関連付けられたすべての Initialize[_] ブロックは、値をロードする前に実行されている必要があります。
設定に格納されるものを見てみましょう。
normalizedName := normalize(name.value)
ここで、name AttributeKey の値に依存することを理解する Setting[_] が構築されます。その初期化ブロックは、最初に name キーの値を取得し、次にその値を計算するために normalize 関数を実行します。
これは、sbt のビルド状態を構築する方法のコアメカニズムを表しています。概念的には、ある時点で依存関係と初期化関数のグラフがあり、これを使用して最初のビルド状態を構築できます。これが完了したら、ユーザーリクエストの処理を開始できます。
sbt の次のレイヤーは、これらのユーザーリクエスト、つまりタスクに関するものです。ユーザーがビルドを構成する場合、プロジェクトで実行できる一連の反復可能なタスクを定義しています。compile や test などです。これらのタスクも依存関係グラフを持っており、たとえば、test タスクは正常に実行される前に compile が実行されている必要があります。
sbt は Task[T] クラスを定義します。T 型パラメーターは、タスクによって返されるデータの型を表します。sbt の原則を覚えていますか?「すべてのものには型がある」と「依存関係は明示的である」の両方がタスクに当てはまります。sbt は、共有の変更可能な状態を使用するのではなく、ユーザーにデータを返すという、より関数型プログラミングに近いタスク依存関係のスタイルを推奨します。
ほとんどのビルドツールはファイルシステムを介して通信し、実際、必然的に sbt はこれの一部を実行します。ただし、安定した並列化のためには、タスクをファイルシステム上で分離し、型を通じて直接通信する方がはるかに優れています。
Setting[_] が依存関係と初期化関数の両方を格納するのと同様に、Task[_] は Task[_] の依存関係と動作 (関数) の両方を格納します。
TODO - Task[_] について詳しく
TODO - InputTask[_] への移行、コマンドの再構成
TODO - スコープへの移行。