1. 常見問題

常見問題 

專案資訊 

名稱「sbt」代表什麼?為什麼不應該寫成「SBT」? 

簡而言之,名稱 sbt 不代表任何意義,它就只是「sbt」,而且應該這樣寫。

當 Mark Harrah (@harrah) 第一次建立專案時,他稱之為「Simple Build Tool」,但在他的第一次公開宣告中,他已經將其簡稱為「sbt」。隨著時間的推移,有些人將 sbt 重新定義為「Scala Build Tool」,但我們認為這也不準確,因為它可以用來建置僅限 Java 的專案。

現在我們只稱 sbt 為「sbt」,為了強調這個名稱不再是首字母縮略字,我們總是全部用小寫字母書寫。不過,我們對酢豚 (subuta) 這個暱稱感到很酷。

我該如何獲得幫助? 

我該如何回報錯誤? 

我該如何提供協助? 

用法 

我上次的指令沒有執行,但我看不到解釋。為什麼? 

預設情況下,sbt 1.9.8 會隱藏大多數堆疊追蹤和偵錯資訊。它具有在畫面上減少雜訊的優點,但作為一個新手,它可能會讓您不知所措。若要查看較高詳細程度的指令先前輸出,請輸入 last <task>,其中 <task> 是失敗的任務或您想要檢視詳細輸出的任務。例如,如果您發現您的 update 無法如預期載入所有依賴,您可以輸入

> last update

它將顯示上次執行 update 指令的完整輸出。

如何在輸出中停用 ansi 程式碼? 

有時 sbt 無法偵測到不支援 ansi 程式碼,而且您會得到如下所示的輸出

[0m[ [0minfo [0m]  [0mSet current project to root

或者支援 ansi 程式碼,但您想要停用彩色輸出。若要完全停用 ansi 程式碼,請傳遞 -no-colors 選項

$ sbt -no-colors

如何使用 sbt 專案組態 (依賴等) 啟動 Scala 直譯器 (REPL)? 

在 sbt 的 Shell 中執行 console

建置定義 

:=+=++= 方法是什麼? 

這些是金鑰上用來建構 SettingTask 的方法。《入門指南》涵蓋了所有這些方法,例如,請參閱.sbt 建置定義任務圖附加值

% 方法是什麼? 

它用於在指定管理的依賴時,從字串建立 ModuleID。閱讀《入門指南》中關於函式庫依賴的內容。

ThisBuild / scalaVersion 的含義是什麼? 

ThisBuild 充當一個特殊的子專案名稱,您可以使用它來定義建置的預設值。當您定義一個或多個子專案,而且當子專案沒有定義 scalaVersion 金鑰時,它會尋找 ThisBuild / scalaVersion

請參閱建置範圍設定

ModuleIDProject 等是什麼? 

若要找出未知的類型或方法,請查看入門指南 (如果您還沒看過)。也可以嘗試查看常用方法、值和類型的索引API 文件

如何將檔案新增到 jar 套件? 

依預設,成品中包含的檔案會由相關套件任務限定範圍的任務 mappings 設定。mappings 任務會傳回從要包含的檔案到 jar 內路徑的對應序列 Seq[(File,String)]。請參閱對應檔案,以取得有關建立這些對應的詳細資訊。

例如,若要將產生的來源新增到封裝的來源成品

Compile / packageSrc / mappings ++= {
  import Path.{flat, relativeTo}
  val base = (Compile / sourceManaged).value
  val srcs = (Compile / managedSources).value
  srcs pair (relativeTo(base) | flat)
}

這會從 managedSources 任務取得來源,並根據 managedSource 基礎目錄使來源相對化,然後回退到扁平化的對應。如果來源產生任務沒有將來源寫入 managedSource 目錄,則必須調整對應函式,嘗試根據其他目錄進行相對化,或者對產生器使用更適當的處理方式。

如何產生原始碼或資源? 

請參閱產生檔案

如果輸入檔案沒有變更,任務如何避免重複執行工作? 

請參閱快取

擴充 sbt 

如何新增新的依賴組態? 

請參閱如何定義自訂依賴組態

如何新增測試組態? 

請參閱測試其他測試組態章節。

除了 run 之外,我該如何建立自訂執行任務? 

此答案摘錄自郵件清單討論

請閱讀《入門指南》,直到自訂設定,以了解背景資訊。

基本執行任務是由下列程式碼建立的

lazy val myRunTask = taskKey[Unit]("A custom run task.")

// this can go either in a `build.sbt` or the settings member
//   of a Project in a full configuration
fullRunTask(myRunTask, Test, "foo.Foo", "arg1", "arg2")

如果您希望能夠在命令列上提供引數,請將 TaskKey 取代為 InputKey,並將 fullRunTask 取代為 fullRunInputTaskTest 部分可以用另一個組態取代,例如 Compile,以使用該組態的類別路徑。

此執行任務可以藉由在作用域中指定任務金鑰個別設定。例如

myRunTask / fork := true

myRunTask / javaOptions += "-Xmx6144m"

我應該如何表示對外部工具 (例如 proguard) 的依賴? 

工具依賴用於實作任務,而且專案原始碼不需要這些依賴。這些依賴可以在其自身的組態和類別路徑中宣告。以下是步驟

  1. 定義新的組態
  2. 在該組態中宣告工具依賴
  3. 定義從 update 產生的更新報告中提取依賴的類別路徑。
  4. 使用類別路徑來實作任務。

舉例來說,考慮一個 proguard 任務。此任務需要 ProGuard jar 才能執行工具。首先,定義並新增新的組態

lazy val ProguardConfig = config("proguard").hide

ivyConfigurations += ProguardConfig

然後,

// Add proguard as a dependency in the custom configuration.
//  This keeps it separate from project dependencies.
libraryDependencies +=
   "net.sf.proguard" % "proguard" % "4.4" % ProguardConfig.name

// Extract the dependencies from the UpdateReport.
ProguardConfig / managedClasspath := {
    // these are the types of artifacts to include
    val artifactTypes: Set[String] = (ProguardConfig / classpathTypes).value
    Classpaths.managedJars(proguardConfig, artifactTypes, update.value)
}

// Use the dependencies in a task, typically by putting them
//  in a ClassLoader and reflectively calling an appropriate
//  method.
proguard := {
    val cp: Seq[File] = (ProguardConfig / managedClasspath).value
  // ... do something with , which includes proguard ...
}

定義中繼類別路徑是選用的,但它對於偵錯或如果需要由多個任務使用,則可能會很有用。也可以內嵌指定成品類型。此替代的 proguard 任務看起來會像這樣

proguard := {
   val artifactTypes = Set("jar")
    val cp =
      Classpaths.managedJars(proguardConfig, artifactTypes, update.value)
  // ... do something with , which includes proguard ...
}

我該如何動態變更 sbt 的類別路徑? 

可以註冊額外的 JAR 檔,這些 JAR 檔會被放置在 sbt 的類別路徑 (classpath) 中。透過 State,可以取得一個 xsbti.ComponentProvider,它管理應用程式元件。元件是指 ~/.sbt/boot/ 目錄中的檔案群組,在這個情況下,應用程式是 sbt。除了基礎的類別路徑外,「額外」元件中的元件也會被包含在 sbt 的類別路徑中。

(注意:應用程式類別路徑上的額外元件是在啟動器組態檔 boot.properties[main] 區段中的 components 屬性所宣告的。)

因為這些元件會被新增到 ~/.sbt/boot/ 目錄,而 ~/.sbt/boot/ 目錄可能為唯讀,這可能會導致失敗。在這種情況下,使用者通常是故意這樣設定 sbt 的,因此通常不需要錯誤恢復(只需簡短的錯誤訊息說明情況即可)。

動態類別路徑增強範例 

以下程式碼可用於需要 State => State 的地方,例如 onLoad 設定(如下所述)或 command 中。它會將一些檔案新增到「額外」元件中,如果這些檔案尚未新增,則會重新載入 sbt。請注意,重新載入會捨棄使用者的會話狀態。

def augment(extra: Seq[File])(s: State): State = {
    // Get the component provider
  val cs: xsbti.ComponentProvider = s.configuration.provider.components()

    // Adds the files in 'extra' to the "extra" component
    //   under an exclusive machine-wide lock.
    //   The returned value is 'true' if files were actually copied and 'false'
    //   if the target files already exists (based on name only).
  val copied: Boolean = s.locked(cs.lockFile, cs.addToComponent("extra", extra.toArray))

    // If files were copied, reload so that we use the new classpath.
  if(copied) s.reload else s
}

專案載入或卸載時,我該如何採取行動? 

請參閱 如何在啟動時執行操作

專案載入/卸載掛鉤範例 

以下範例會維護專案載入次數的計數,並印出該數字。

{
  // the key for the current count
  val key = AttributeKey[Int]("loadCount")
  // the State transformer
  val f = (s: State) => {
    val previous = s get key getOrElse 0
    println("Project load count: " + previous)
    s.put(key, previous + 1)
  }
  Global / onLoad := {
    val previous = (Global / onLoad).value
    f compose previous
  }
}

錯誤 

在專案載入時,「參考未初始化的設定」 

設定初始化器會依序執行。如果某個設定的初始化依賴於尚未初始化的其他設定,sbt 將會停止載入。

在這個範例中,我們嘗試在 libraryDependencies 使用空序列初始化之前,將一個程式庫附加到其中。

libraryDependencies += "commons-io" % "commons-io" % "1.4" % "test"

disablePlugins(plugins.IvyPlugin)

要更正這個問題,請包含 IvyPlugin 外掛程式設定,其中包含 libraryDependencies := Seq()。因此,我們只需刪除明確的停用設定。

libraryDependencies += "commons-io" % "commons-io" % "1.4" % "test"

當使用 作用域設定時,會發生更微妙的此錯誤變體。

// error: Reference to uninitialized setting
settings = Seq(
  libraryDependencies += "commons-io" % "commons-io" % "1.2" % "test",
  fullClasspath := fullClasspath.value.filterNot(_.data.name.contains("commons-io"))
)

此設定在測試和編譯作用域之間有所不同。解決方案是使用作用域設定,既作為初始化器的輸入,又作為我們更新的設定。

Compile / fullClasspath := (Compile / fullClasspath).value.filterNot(_.data.name.contains("commons-io"))

相依性管理 

我該如何解決校驗碼錯誤? 

當發佈的校驗碼(例如 sha1 或 md5 雜湊值)與下載的成品(例如 jar 或 pom.xml)計算出的校驗碼不同時,就會發生此錯誤。這種錯誤的一個例子是

[warn]  problem while downloading module descriptor:
https://repo1.maven.org/maven2/commons-fileupload/commons-fileupload/1.2.2/commons-fileupload-1.2.2.pom:
invalid sha1: expected=ad3fda4adc95eb0d061341228cc94845ddb9a6fe computed=0ce5d4a03b07c8b00ab60252e5cacdc708a4e6d8 (1070ms)

無效的校驗碼通常應報告給存放庫所有者(如上述錯誤的 處理方式)。同時,您可以使用以下設定暫時停用檢查

checksums in update := Nil

有關詳細資訊,請參閱 程式庫管理

我新增了一個外掛程式,現在我的交叉編譯失敗了! 

這個問題經常出現。外掛程式只會針對 sbt 使用的 Scala 版本(目前為 2.12)發佈。您仍然可以在交叉編譯期間使用外掛程式,因為 sbt 只會尋找 2.12 版本的外掛程式。

...除非您將外掛程式指定在錯誤的地方!

一個典型的錯誤是將全域外掛程式定義放在 ~/.sbt/plugins.sbt 中。這是錯誤的。 ~/.sbt 中的 .sbt 檔案會針對每個建置載入,也就是針對每個交叉編譯載入。因此,如果您針對 Scala 2.11.0 建置,sbt 會嘗試尋找針對 2.11.0 編譯的外掛程式版本,而且通常找不到。那是因為它不知道相依性是外掛程式。

若要告知 sbt 相依性是 sbt 外掛程式,請確保您將全域外掛程式定義在 ~/.sbt/plugins/ 中的 .sbt 檔案中。sbt 知道 ~/.sbt/plugins 中的檔案僅供 sbt 本身使用,而不是作為一般建置定義的一部分。如果您在目錄下的檔案中定義外掛程式,它們就不會妨礙您的交叉編譯。任何以 .sbt 結尾的檔名都可以,但大多數人使用 ~/.sbt/plugins/build.sbt~/.sbt/plugins/plugins.sbt

雜項 

我可以在哪裡找到 1.9.8 的外掛程式? 

請參閱 社群外掛程式,以取得目前可用的外掛程式列表。