本文件詳述了 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
上放置或取得內容,我們首先需要建構其中一個鍵。讓我們看看 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[_]
值的方式。設定包含兩個部分
AttributeKey[T]
。Initialize[T]
物件。sbt 的初始化時間基本上只是接受這些 Setting[_]
物件的序列,並執行其初始化物件,然後將值儲存到 AttributeMap
中。這表示,覆寫索引鍵的現有值就像將 Setting[_]
附加到序列末尾一樣容易,這樣即可達成覆寫。
有趣的是,Initialize[T]
可以取決於建置狀態中的其他 AttributeKey[_]
。每個 Initialize[_]
都可以從建置狀態的 AttributeMap
中的任何 AttributeKey[_]
提取值,以計算其值。sbt 在 Initialize[_]
相依性方面確保了幾件事
如果一個 Initialize[_]
取決於另一個 Initialize[_]
索引鍵,則
在載入值之前,該索引鍵的所有相關聯 Initialize[_]
區塊都必須已執行。
讓我們看看設定的儲存內容:
normalizedName := normalize(name.value)
這裡會建構一個 Setting[_]
,它知道它取決於 name
AttributeKey
中的值。其初始化區塊會先抓取 name
鍵的值,然後在其上執行函式 normalize
來計算其值。
這表示如何建構 sbt 建置狀態的核心機制。從概念上講,在某個時間點,我們會具有相依性和初始化函式的圖表,可用來建構第一個建置狀態。一旦完成,我們就可以開始處理使用者請求。
sbt 的下一層是圍繞這些使用者請求或任務。當使用者設定建置時,他們會定義一組可重複執行的任務,這些任務可以在其專案上執行。例如 compile
或 test
等。這些任務也具有相依性圖表,例如,test
任務需要 compile
先執行,才能成功執行。
sbt 定義了一個類別 Task[T]
。T
型別參數表示任務傳回的資料型別。還記得 sbt 的原則嗎?「所有事物都有型別」和「相依性是明確的」這兩者都適用於任務。sbt 提倡一種更接近函數程式設計的任務相依性樣式:為使用者傳回資料,而不是使用共用的可變狀態。
大多數建置工具都透過檔案系統進行通訊,而且實際上,sbt 也會執行一些操作。但是,為了實現穩定的平行處理,最好將任務隔離在檔案系統上,並直接透過型別進行通訊。
與 Setting[_]
儲存相依性和初始化函式的方式類似,Task[_]
會同時儲存其 Task[_]
相依性和行為(函式)。
TODO - 關於 Task[_]
的更多資訊
TODO - 轉換為 InputTask[_]
,重新整理命令
TODO - 轉換為作用域。