1. 自訂設定與任務

自訂設定與任務 

此頁面將協助您開始建立自己的設定和任務。

若要了解此頁面,請務必閱讀入門指南中先前的頁面,尤其是 build.sbt任務圖

定義鍵 

Keys 包含許多範例,說明如何定義鍵。大多數鍵都在 Defaults 中實作。

鍵有三種類型。SettingKeyTaskKey.sbt 建置定義中說明。請在 輸入任務頁面中閱讀有關 InputKey 的資訊。

Keys 中的一些範例

val scalaVersion = settingKey[String]("The version of Scala used for building.")
val clean = taskKey[Unit]("Deletes files produced by the build, such as generated sources, compiled classes, and task caches.")

鍵建構子有兩個字串參數:鍵的名稱 ("scalaVersion") 和文件字串 ("用於建置的 scala 版本。")。

請回想一下,從 .sbt 建置定義中,SettingKey[T] 中的類型參數 T 指示設定具有的值的類型。TaskKey[T] 中的 T 指示任務結果的類型。另請回想一下,從 .sbt 建置定義中,設定在專案重新載入之前具有固定值,而任務則會針對每個「任務執行」重新計算(每次有人在 sbt 互動提示符或批次模式中輸入命令時)。

鍵可以定義在 .sbt 檔案.scala 檔案自動外掛程式中。在已啟用的自動外掛程式的 autoImport 物件下找到的任何 val 都會自動匯入您的 .sbt 檔案中。

實作任務 

一旦您為您的任務定義了鍵,您就需要使用任務定義來完成它。您可能正在定義自己的任務,或者您可能正計畫重新定義現有任務。無論哪種方式看起來都一樣;使用 := 將某些程式碼與任務鍵建立關聯

val sampleStringTask = taskKey[String]("A sample string task.")
val sampleIntTask = taskKey[Int]("A sample int task.")

ThisBuild / organization := "com.example"
ThisBuild / version      := "0.1.0-SNAPSHOT"
ThisBuild / scalaVersion := "2.12.18"

lazy val library = (project in file("library"))
  .settings(
    sampleStringTask := System.getProperty("user.home"),
    sampleIntTask := {
      val sum = 1 + 2
      println("sum: " + sum)
      sum
    }
  )

如果任務具有相依性,您可以使用 value 參考它們的值,如 任務圖中所述。

實作任務最困難的部分通常不是 sbt 特有的;任務只不過是 Scala 程式碼。困難的部分可能是撰寫您的任務的「主體」,以執行您嘗試執行的任何操作。例如,您可能嘗試格式化 HTML,在這種情況下,您可能想要使用 HTML 函式庫(您可以將函式庫相依性新增至您的建置定義,並根據 HTML 函式庫撰寫程式碼,或許)。

sbt 有一些公用程式函式庫和便利功能,特別是您通常可以使用 IO 中的便利 API 來操作檔案和目錄。

任務的執行語意 

當使用 value 從自訂任務相依於其他任務時,一個需要注意的重要細節是任務的執行語意。所謂的執行語意,我們指的是這些任務的確切評估時間

如果我們以 sampleIntTask 為例,則應該依序嚴格評估任務主體中的每一行。這就是循序語意

sampleIntTask := {
  val sum = 1 + 2        // first
  println("sum: " + sum) // second
  sum                    // third
}

實際上,JVM 可能會將 sum 內嵌為 3,但任務的可觀察效果仍會與每一行都依序執行相同。

現在假設我們定義另外兩個自訂任務 startServerstopServer,並如下修改 sampleIntTask

val startServer = taskKey[Unit]("start server")
val stopServer = taskKey[Unit]("stop server")
val sampleIntTask = taskKey[Int]("A sample int task.")
val sampleStringTask = taskKey[String]("A sample string task.")

ThisBuild / organization := "com.example"
ThisBuild / version      := "0.1.0-SNAPSHOT"
ThisBuild / scalaVersion := "2.12.18"

lazy val library = (project in file("library"))
  .settings(
    startServer := {
      println("starting...")
      Thread.sleep(500)
    },
    stopServer := {
      println("stopping...")
      Thread.sleep(500)
    },
    sampleIntTask := {
      startServer.value
      val sum = 1 + 2
      println("sum: " + sum)
      stopServer.value // THIS WON'T WORK
      sum
    },
    sampleStringTask := {
      startServer.value
      val s = sampleIntTask.value.toString
      println("s: " + s)
      s
    }
  )

從 sbt 互動提示符執行 sampleIntTask 會產生以下結果

> sampleIntTask
stopping...
starting...
sum: 3
[success] Total time: 1 s, completed Dec 22, 2014 5:00:00 PM

若要檢視發生的情況,讓我們看看 sampleIntTask 的圖形符號

task-dependency

與一般的 Scala 方法呼叫不同,在任務上叫用 value 方法不會嚴格評估。相反地,它們只是充當預留位置,表示 sampleIntTask 相依於 startServerstopServer 任務。當您叫用 sampleIntTask 時,sbt 的任務引擎將會

  • 在評估 sampleIntTask 之前,先評估任務相依性(部分排序)
  • 如果任務相依性是獨立的,則嘗試平行評估任務相依性(平行化)
  • 每個任務相依性在每次命令執行時都會評估一次且僅一次(重複資料刪除)

任務相依性的重複資料刪除 

為了示範最後一點,我們可以從 sbt 互動提示符執行 sampleStringTask

> sampleStringTask
stopping...
starting...
sum: 3
s: 3
[success] Total time: 1 s, completed Dec 22, 2014 5:30:00 PM

因為 sampleStringTask 相依於 startServersampleIntTask 任務,而 sampleIntTask 也相依於 startServer 任務,所以它會以任務相依性的形式出現兩次。如果這是一般的 Scala 方法呼叫,則會評估兩次,但是由於 value 只是表示任務相依性,因此它只會評估一次。以下是 sampleStringTask 評估的圖形符號

task-dependency

如果我們沒有重複資料刪除任務相依性,則在叫用 test 任務時,我們會多次編譯測試來源程式碼,因為 Test / compile 會多次以 Test / test 的任務相依性形式出現。

清除任務 

應該如何實作 stopServer 任務?清除任務的概念不符合任務的執行模型,因為任務是關於追蹤相依性。最後一個操作應變成相依於其他中繼任務的任務。例如,stopServer 應該相依於 sampleStringTask,屆時 stopServer 應該是 sampleStringTask

lazy val library = (project in file("library"))
  .settings(
    startServer := {
      println("starting...")
      Thread.sleep(500)
    },
    sampleIntTask := {
      startServer.value
      val sum = 1 + 2
      println("sum: " + sum)
      sum
    },
    sampleStringTask := {
      startServer.value
      val s = sampleIntTask.value.toString
      println("s: " + s)
      s
    },
    sampleStringTask := {
      val old = sampleStringTask.value
      println("stopping...")
      Thread.sleep(500)
      old
    }
  )

為了示範其運作方式,請從互動提示符執行 sampleStringTask

> sampleStringTask
starting...
sum: 3
s: 3
stopping...
[success] Total time: 1 s, completed Dec 22, 2014 6:00:00 PM

task-dependency

使用純 Scala 

另一種確保某件事在其他事之後發生的方法是使用 Scala。例如,在 project/ServerUtil.scala 中實作簡單的函式,您可以撰寫

sampleIntTask := {
  ServerUtil.startServer
  try {
    val sum = 1 + 2
    println("sum: " + sum)
  } finally {
    ServerUtil.stopServer
  }
  sum
}

由於純方法呼叫遵循循序語意,因此所有事情都會依序發生。沒有重複資料刪除,因此您必須注意這一點。

將它們變成外掛程式 

如果您發現您有很多自訂程式碼,請考慮將其移至外掛程式,以便在多個建置中重複使用。

建立外掛程式非常容易,如先前所提及在此處更詳細討論

這個頁面只是快速瀏覽;關於自訂任務的更多內容,請參閱任務頁面。