1. 核心原則

核心原則 

本文件詳述了 sbt 設計和程式碼風格的核心原則。sbt 的核心原則可以簡單地說明如下:

  1. 所有事物都應具有 Type,並且在實際情況下盡可能強制執行。
  2. 相依性應為明確的
  3. 一旦學習,概念應在 sbt 的所有部分中保持一致。
  4. 平行處理為預設值。

謹記這些原則,讓我們來檢視 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[_] 值的方式。設定包含兩個部分

  1. 應指派設定值的 AttributeKey[T]
  2. 能夠為此設定建構值的 Initialize[T] 物件。

sbt 的初始化時間基本上只是接受這些 Setting[_] 物件的序列,並執行其初始化物件,然後將值儲存到 AttributeMap 中。這表示,覆寫索引鍵的現有值就像將 Setting[_] 附加到序列末尾一樣容易,這樣即可達成覆寫。

有趣的是,Initialize[T] 可以取決於建置狀態中的其他 AttributeKey[_]。每個 Initialize[_] 都可以從建置狀態的 AttributeMap 中的任何 AttributeKey[_] 提取值,以計算其值。sbt 在 Initialize[_] 相依性方面確保了幾件事

  1. 不能有循環相依性
  2. 如果一個 Initialize[_] 取決於另一個 Initialize[_] 索引鍵,則

    在載入值之前,該索引鍵的所有相關聯 Initialize[_] 區塊都必須已執行。

讓我們看看設定的儲存內容:

normalizedName := normalize(name.value)

image

這裡會建構一個 Setting[_],它知道它取決於 name AttributeKey 中的值。其初始化區塊會先抓取 name 鍵的值,然後在其上執行函式 normalize 來計算其值。

這表示如何建構 sbt 建置狀態的核心機制。從概念上講,在某個時間點,我們會具有相依性和初始化函式的圖表,可用來建構第一個建置狀態。一旦完成,我們就可以開始處理使用者請求。

任務架構 

sbt 的下一層是圍繞這些使用者請求或任務。當使用者設定建置時,他們會定義一組可重複執行的任務,這些任務可以在其專案上執行。例如 compiletest 等。這些任務具有相依性圖表,例如,test 任務需要 compile 先執行,才能成功執行。

sbt 定義了一個類別 Task[T]T 型別參數表示任務傳回的資料型別。還記得 sbt 的原則嗎?「所有事物都有型別」和「相依性是明確的」這兩者都適用於任務。sbt 提倡一種更接近函數程式設計的任務相依性樣式:為使用者傳回資料,而不是使用共用的可變狀態。

大多數建置工具都透過檔案系統進行通訊,而且實際上,sbt 也會執行一些操作。但是,為了實現穩定的平行處理,最好將任務隔離在檔案系統上,並直接透過型別進行通訊。

Setting[_] 儲存相依性和初始化函式的方式類似,Task[_] 會同時儲存其 Task[_] 相依性和行為(函式)。

TODO - 關於 Task[_] 的更多資訊

TODO - 轉換為 InputTask[_],重新整理命令

TODO - 轉換為作用域。