1. 任務/設定:動機

任務/設定:動機 

本頁說明任務和設定系統的動機。您應該已經知道如何使用任務和設定,這些內容在入門指南任務頁面中說明。

任務系統的一個重要方面是結合建置中的兩個常見相關步驟

  1. 確保執行其他一些任務。
  2. 使用該任務的某些結果。

較早版本的 sbt 使用以下方法單獨設定這些步驟

  1. 依賴宣告
  2. 某些形式的共用狀態

為了了解為何結合它們是有利的,請將這種情況與 Scala 中延遲變數初始化的情況進行比較。此 Scala 程式碼是一種不好的方式來公開其初始化被延遲的值

// Define a variable that will be initialized at some point
// We don't want to do it right away, because it might be expensive
var foo: Foo = _

// Define a function to initialize the variable
def makeFoo(): Unit = ... initialize foo ...

典型用法如下

makeFoo()
doSomething(foo)

這個範例在其糟糕程度上有些誇張,但我認為它與我們的兩步驟任務定義幾乎相同。這很糟糕的特別原因包括

  1. 用戶端需要知道先呼叫 makeFoo()
  2. foo 可能會被其他程式碼變更。例如,可能會存在 def makeFoo2()。
  3. 對 foo 的存取不是執行緒安全的。

第一點類似於宣告任務依賴,第二點類似於兩個任務修改相同的狀態(專案變數或檔案),而第三點是不同步的共用狀態的結果。

在 Scala 中,我們具有內建的功能來輕鬆解決此問題:lazy val

lazy val foo: Foo = ... initialize foo ...

例如使用方式

doSomething(foo)

在這裡,lazy val 為我們提供執行緒安全、保證存取前初始化,以及不可變性,全部在一個 DRY 建構中。sbt 中的任務系統對任務執行與 lazy val 對我們糟糕的範例所做的事情相同(而且更多,但我們在此不做深入探討)。

任務定義必須宣告其輸入和其輸出的類型。sbt 將確保輸入任務已執行,然後將其結果提供給實作任務的函式,該函式將產生其自己的結果。其他任務可以使用此結果,並確保任務已執行(一次),並且在此過程中是執行緒安全且類型安全的。

任務定義的一般形式如下

myTask := {
  val a: A = aTask.value
  val b: B = bTask.value
  ... do something with a, b and generate a result ...
}

(這僅旨在討論任務背後的概念,因此請參閱sbt 任務頁面以了解使用詳情。)在這裡,假設 aTask 產生類型為 A 的結果,而 bTask 產生類型為 B 的結果。

應用程式 

舉例來說,請考慮產生包含專案二進位 jar、來源 jar 和文件 jar 的 zip 檔案。首先,判斷哪些任務會產生 jar。在此情況下,輸入任務是主要 Compile 範圍中的 packageBinpackageSrcpackageDoc。每個這些任務的結果是它們產生的 jar 的 File。我們的 zip 檔案任務是透過對應這些封裝任務並在其輸出中包含 zip 檔案來定義的。作為良好實踐,然後我們傳回此 zip 的 File,以便其他任務可以對應 zip 任務。

zip := {
    val bin: File = (Compile / packageBin).value
    val src: File = (Compile / packageSrc).value
    val doc: File = (Compile / packageDoc).value
    val out: File = zipPath.value
    val inputs: Seq[(File,String)] = Seq(bin, src, doc) x Path.flat
    IO.zip(inputs, out)
    out
}

val inputs 行定義輸入檔案如何對應到 zip 中的路徑。如需詳細資訊,請參閱對應檔案。並非必須明確宣告類型,但為了清楚起見而包含。

zipPath 輸入將是一個自訂任務,用於定義 zip 檔案的位置。例如

zipPath := target.value / "out.zip"