1. sbt 1.3.x 版本

sbt 1.3.x 版本 

sbt 1.3.0 

這是 sbt 1.x 的第三個功能版本,一個二進位檔相容的版本,著重於新功能。sbt 1.x 是在語意版本控制下發佈的,外掛程式預期會在整個 1.x 系列中運作。

sbt 1.3 的主要功能是開箱即用的 Coursier 函式庫管理、ClassLoader 分層、IO 改善和超級 Shell。結合在一起,我們希望這些功能可以改善您執行建置的使用者體驗。

具有相容性影響的變更 

  • 使用 Coursier 進行函式庫管理。詳情請見下文。
  • 超級 Shell。詳情請見下文。
  • 多重命令不再需要前導分號。clean;Test/compile; 會運作。#4456 by @eatkins
  • 棄用 HTTP 解析器,但允許 localhost 或標記為 .withAllowInsecureProtocol(true) 的解析器 #4997
  • 棄用 CrossVersion.Disabled。請改用 CrossVersion.disabled sbt/librarymanagement#316
  • ClassLoader 管理:為了防止資源洩漏,sbt 1.3.0 會在 runtest 任務完成後關閉這些任務使用的臨時 ClassLoader。如果任務使用 ShutdownHooks,或如果任務建立的任何執行緒在任務完成後繼續執行,可能會導致下游損毀。若要停用此行為,請設定 Compile / run / fork := true 或使用 -Dsbt.classloader.close=false 執行 sbt。

使用 Coursier 進行函式庫管理 

sbt 1.3.0 採用 Coursier 進行函式庫管理。Coursier 是像 Ivy 一樣的相依性解析器,由 Alexandre Archambault (@alexarchambault) 以 Scala 重寫,目標是成為更快的替代方案。

注意:在某些情況下,Coursier 可能不會以與 Ivy 相同的方式解析(例如遠端 -SNAPSHOT 會快取 24 小時)。如果您希望返回 Apache Ivy 進行函式庫管理,請在您的 build.sbt 中放入下列內容

ThisBuild / useCoursier := false

許多人參與將 Coursier 導入 sbt 的工作。早在 2018 年,Leonard Ehrenfried (@leonardehrenfried) 就開始將 Coursier 支援的 LM API 實作做為 lm#190。在秋季期間,Andrea Peruffo (@andreaTP) 進一步改進了它,lm-coursier 最終成為由 Alex 維護的 coursier/sbt-coursier 儲存庫的一部分。今年春天,Eugene (@eed3si9n) 再次檢閱了它,並做了一些變更,以便我們可以在 #4614 中替換 LM 引擎,並得到 Alex 的協助。

具有 ClassLoader 分層的 Turbo 模式 

sbt 1.3.0 新增了「turbo」模式,可啟用可能需要在建置使用者無法正常運作時進行偵錯的實驗性或進階功能。

ThisBuild / turbo := true

最初,我們將分層 ClassLoader (ClassLoaderLayeringStrategy.AllLibraryJars) 放在此旗標後面。

sbt 在評估 runtest 任務時,一律會建立雙層 ClassLoader。ClassLoader 的頂層包含 scala 函式庫 jar,以便可以跨多個任務評估重複使用 scala 套件中的類別。第二層載入專案類別路徑的其餘部分,包括函式庫相依性和專案類別檔案。sbt 1.3.0 引入了實驗性classLoaderLayeringStrategy 功能,進一步擴展此概念。

Compile / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.Flat
// default
Compile / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.ScalaLibrary
// enabled with turbo
Compile / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.AllLibraryJars

Test / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.Flat
// default
Test / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.ScalaLibrary
// enabled with turbo
Test / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.AllLibraryJars
  • ClassLoaderLayeringStrategy.Flat 包含除 Java 執行階段之外的所有類別和 JAR。使用此策略的任務行為應該類似於 forking,而沒有啟動新 jvm 的額外負荷。
  • ClassLoaderLayeringStrategy.ScalaLibrary 會建立一個雙層 ClassLoader,其中 Scala 標準函式庫會保持暖機,類似於 sbt 1.2.x
  • ClassLoaderLayeringStrategy.AllLibraryJars 會建立一個三層 ClassLoader,其中函式庫相依性(除了 Scala 標準函式庫之外)會保持暖機

ClassLoaderLayeringStrategy.AllLibraryJars 應該能縮短執行和測試任務的反應時間。藉由快取函式庫 jar ClassLoader,當在同一工作階段中多次執行時,可以大幅減少執行和測試任務的啟動延遲。GC 的壓力也降低了,因為每次評估任務時都不會重新載入函式庫 jar。

注意:ClassLoaderLayeringStrategy.AllLibraryJars 會重複使用測試之間的單例物件,這要求函式庫在自身之後進行清除。

另一方面,ClassLoaderLayeringStrategy.Flat 將使某些無法與分層 ClassLoader 良好搭配的應用程式受益。一個這樣的範例是 Scala 集合使用的 Java 序列化 + 序列化 Proxy 模式。

ClassLoader 分層由 Ethan Atkins (@eatkins) 貢獻,做為 #4476

IO 改善 

除了 ClassLoader 分層之外,sbt 1.3.0 還包含許多效能增強功能,包括

  • 更快的遞迴目錄清單 — sbt 內部使用原生函式庫,swoval,它提供原生作業系統 API 的 jni 介面,可比 Java 標準函式庫中的實作更快地進行遞迴目錄清單。
  • 縮短持續建置中檔案變更偵測的延遲。在大多數情況下,檔案事件會在 10 毫秒內觸發任務評估。

在撰寫本文時,sbt 1.3.0 對於 5000 個原始碼檔案的編輯-編譯-測試迴圈,比使用 sbt 0.13、Gradle 和我們測試的其他建置工具對三個原始碼檔案的編輯-編譯-測試更快(詳情請參閱 建置效能)。這些變更是由 Ethan Atkins (@eatkins) 貢獻。

Glob 

sbt 1.3.0 引入了新的型別 Glob,描述路徑搜尋查詢。例如,專案目錄中的所有 Scala 原始碼可以用 Glob(baseDirectory.value, RecursiveGlob / "*.scala")baseDirectory.value.toGlob / ** / "*.scala" 來描述,其中 **RecursiveGlob 的別名。Glob 擴展了 PathFinders,但它們可以組合,而沒有 IO 額外負荷。可以使用 FileTreeView 擷取 Globs。例如,可以撰寫

val scalaSources = baseDirectory.value.toGlob / ** / "*.scala"
val javaSources = baseDirectory.value.toGlob / ** / "*.java"
val allSources = fileTreeView.value.list(Seq(scalaSources, javaSources))

FileTreeView 只會遍歷基底目錄一次。Globs 和 FileTreeView 由 Ethan Atkins (@eatkins) 在 io#178io#216io#226 中新增。

Watch 改善 

sbt 1.3.0 引入了新的檔案監控實作。它使用增強的 API,使用作業系統事件來追蹤檔案變更事件。它新增了一個新的剖析器,可擷取將監控原始碼檔案的特定任務,並在偵測到變更時重新執行。只會監控正在執行任務的原始碼相依性。例如,執行 ~compile 時,對測試原始碼檔案的變更不會觸發新的建置。在檔案事件之間,現在也有選項可返回 Shell、重新執行先前的命令或結束 sbt。這些變更由 Ethan Atkins (@eatkins) 在 io#178#216#226#4512#4627 中實作。

建置定義原始碼監看 

sbt 1.3.0 會自動監看建置定義原始碼,如果您在未重新載入的情況下執行任務,則會顯示警告。可以設定為如下自動重新載入

Global / onChangedBuildSource := ReloadOnSourceChanges

此功能由 Ethan Atkins (@eatkins) 在 #4664 中貢獻。

自訂增量任務 

sbt 1.3.0 提供了基於檔案實作自訂增量任務的支援。 針對會回傳 java.nio.file.PathSeq[java.nio.file.Path]FileSeq[File] 的自訂任務,您可以定義一些輔助任務,使其更具增量性。

import java.nio.file._
import scala.sys.process._
val gccCompile = taskKey[Seq[Path]]("compile C code using gcc")
val gccHeaders = taskKey[Seq[Path]]("header files")
val gccInclude = settingKey[Path]("include directory")
val gccLink = taskKey[Path]("link C code using gcc")

gccCompile / sourceDirectory := sourceDirectory.value
gccCompile / fileInputs += (gccCompile / sourceDirectory).value.toGlob / ** / "*.c"
gccInclude := (gccCompile / sourceDirectory).value.toPath / "include"
gccHeaders / fileInputs += gccInclude.value.toGlob / "*.h"
gccCompile / target := baseDirectory.value / "out"

gccCompile := {
  val objectDir = Files.createDirectories((gccCompile / target).value.toPath / "objects")
  def objectFile(path: Path): Path =
    target.value.toPath / path.getFileName.toString.replaceAll(".c$", ".o")
  Files.createDirectories(target.value.toPath)
  val headerChanges = gccHeaders.inputFileChanges.hasChanges
  val changes = gccCompile.inputFileChanges
  changes.deleted.foreach(sf => Files.deleteIfExists(objectFile(sf)))
  val sourceFileChanges = changes.created ++ changes.modified
  val needRecompile = (sourceFileChanges ++ (if (headerChanges) changes.unmodified else Nil)).toSet

  val logger = streams.value.log
  gccCompile.inputFiles.map { sf =>
    val of = objectFile(sf)
    if (!Files.exists(of) || needRecompile(sf)) {
      logger.info(s"Compiling $sf")
      s"gcc -I${gccInclude.value} -c $sf -o $of".!!
    }
    of
  }
}

有了這個設定,gccCompile.inputFiles 將會回傳所有輸入的 c 原始碼檔案序列,gccCompile.inputFileChanges 會回傳一個資料結構,其中包含自上次執行 gccCompile 以來建立、刪除、修改和未修改的檔案,而 gccHeaders.changedInputFiles 則會回傳自上次執行 gccCompile 以來變更的標頭檔。總而言之,這些任務可以用來根據自上次 gccCompile 完成以來,檔案系統的變更,僅增量重建需要重建的原始碼檔案。

在另一個任務(例如 gccLink)中,gccCompile 的結果也可以使用 gccCompile.outputFileChanges 來追蹤。

gccLink := {
  val library = (gccCompile / target).value.toPath / "libmylib.dylib"
  val objectFiles = gccCompile.outputFiles
  val logger = streams.value.log
  if (!Files.exists(library) || gccCompile.outputFileChanges.hasChanges) {
    logger.info(s"Rebuilding $library")
    s"gcc -dynamiclib -o $library ${objectFiles mkString " "}".!!
  }
  library
}

任務的輸入會由 ~ 命令自動監控,該命令有一個新的、具備上下文感知能力的剖析器。 針對任何產生檔案輸出的任務,也實作了自訂的清除任務。 清除任務會在專案和組態範圍內進行彙總。 例如,Test / clean 將會清除 Test 組態中宣告的 Test 組態中,由任務產生的所有檔案,但不會清除 Compile 組態中產生的檔案。

此功能由 Ethan Atkins (@eatkins) 在 #4627 中貢獻。

超級 Shell 

在與 ANSI 相容的終端機中執行時,sbt 1.3.0 會顯示目前正在執行的任務。 這讓開發人員瞭解哪些任務正在平行處理,以及建置作業將時間花在哪裡。 為了向 Gradle 的「Rich Console」和 Buck 的「Super Console」致敬,我們稱其為「超級 Shell」。

若要退出,請將以下內容放入建置中

ThisBuild / useSuperShell := false

或使用 --supershell=false (或 -Dsbt.supershell=false) 執行 sbt。 此功能由 Eugene Yokota (@eed3si9n) 作為 #4396/util#196 新增。

追蹤 

若要以視覺方式檢視任務分解,請使用 --traces (或 -Dsbt.traces=true) 執行 sbt。 這將會產生 build.traces 檔案,可使用 Chrome Tracing chrome://tracing/ 檢視。 此功能由 Jason Zaugg (@retronym) 貢獻。

若要在螢幕上輸出任務時間,請使用 --timings (或 -Dsbt.task.timings=true -Dsbt.task.timings.on.shutdown=true) 執行 sbt。

SemanticDB 支援 

sbt 1.3.0 讓產生 SemanticDB 更為容易。 若要啟用整個建置的 SemanticDB 產生

ThisBuild / semanticdbEnabled := true
ThisBuild / semanticdbVersion := "4.1.9"
ThisBuild / semanticdbIncludeInJar := false

這是由 @eed3si9n 作為 #4410 新增。

print 命令 

sbt 1.3.0 新增了 print 命令,類似於 show,但會直接列印到標準輸出。

# sbt -no-colors --error  "print akka-cluster/scalaVersion"
2.12.8

這是由 David Knapp (@Falmarri) 作為 #4341 貢獻。

附加 Function1 

可以使用 += 附加 Function1

Global / onLoad += { s =>
  doSomething()
  s
}

這是由 Dale Wijnand (@dwijnand) 作為 #4521 貢獻。

JDK 11 支援 

sbt 1.3.0 是 sbt 首個在 JDK11 上經過廣泛測試的版本。 Travis CI 上的所有整合測試都在 AdoptOpenJDK 的 JDK 11 上進行,這些測試由 @eed3si9n 作為 #4389/zinc#639/[zinc640] 更新。

  • 透過升級到 protobuf 3.7.0 zinc#644,修正了 JDK 9+ 上的警告,由 @smarter 提供。
  • 修正了因 JDK 11 上 rt.jar 失效而導致的虛假重建 #4679,由 @eatkins 提供。

其他錯誤修正和改進 

  • 修正了使用單字母別名的交叉建置 #4355 / #1074,由 @eed3si9n 提供。
  • 移除了關於全域目錄的舊警告 #4356 / #1054,由 @eed3si9n 提供。
  • 改進了交叉 JDK 分支的 JDK 探索 #4313 / #4462,由 @raboof 提供。
  • -Dsbt.global.base 屬性中的 ~ 展開為使用者主目錄。 #4367,由 @kai-chi 提供。
  • 新增 def sequential[A](tasks: Seq[Initialize[Task[A]]]): Initialize[Task[A]]#4369,由 @3tty0n 提供。
  • 修正了 sbt 伺服器在命令失敗時傳送錯誤事件的問題。 #4378,由 @andreaTP 提供。
  • 實作了由 LSP 用戶端取消的要求。 #4384,由 @andreaTP 提供。
  • 在 sbt 中實作了 "sbt/completion" 命令到伺服器以完成 sbt 命令。 #4397,由 @andreaTP 提供。
  • 修正了 sbt 伺服器回報的錯誤順序。 #4497,由 @tdroxler 提供。
  • 修正了快取的解析。 #4424,由 @eed3si9n 提供。
  • sbt 任務定義檢查器預設會發出警告而非錯誤。 透過在範圍內加入 import sbt.dsl.LinterLevel.Ignore,可以完全停用檢查器。 #4485,由 @eatkins 提供。
  • 僅當 sbt 閒置至少一分鐘,且最多只在 Shell 命令之間執行一次時,才會自動觸發完整 GC。 這提高了 Shell 的回應速度。 #4544,由 @eatkins 提供。
  • 避免在 JDK12 中出現 NPE。 #4549,由 @retronym 提供。
  • 修正了驅逐警告摘要 lm#288,由 @bigwheel 提供。
  • 修正了 Zinc 的旗標,以跳過 API 資訊的持久性。 zinc#399,由 @romanowski 提供。
  • 修正了 Zinc 無法偵測到合成最上層成員變更的問題。 #4316/zinc#572,由 @jvican 提供。
  • 在編譯器的中間和後端階段之前,Zinc 通知回呼已產生的非本機類別。 zinc#582,由 @jvican 提供。
  • 為了提升效能,移除了 Zinc 中使用的正規表示式。 zinc#583,由 @retronym 提供。
  • 修正了涉及預設引數的增量編譯。 zinc#591,由 @jvican 提供。
  • 新增了 Zinc 的執行緒安全分析回呼。 zinc#626,由 @dotta 提供。
  • 修正了非零結束 Javadoc 未使任務失敗的問題。 zinc#625,由 @raboof 提供。

參與 

首先,我想介紹 Ethan Atkins,他是 sbt 專案的核心社群成員,也是 Close Watch 的作者,該專案使用原生程式碼在 macOS 上提供監看服務。 通常我不會公開提交次數,但以下是 sbt 1.3.0 的前 10 名

541 Ethan Atkins
369 Eugene Yokota (eed3si9n)
42  Jorge Vicente Cantero (jvican)
35  Łukasz Wawrzyk
34  Dale Wijnand
24  Andrea Peruffo
16​​  Kenji Yoshida (xuwei-k)
13  Guillaume Martres
7   Arnout Engelen
7   Jason Zaugg

作為社群成員,Ethan 貢獻了各種與 IO 相關的改進,讓 sbt 在他自己的時間內更具回應性。 sbt 1.3.0 反映了他的許多想法。

sbt 1 的最後一個功能版本是 2018 年 7 月的 sbt 1.2.0。 自那時起,我們已在 sbt 1.2.x 下發佈了八個修補程式版本來修正錯誤,但大部分的功能增強都已合併到 develop 分支。 在這幾個月的時間裡,有 45 位貢獻者參與了 sbt 1.3.0 和 Zinc:Ethan Atkins、Eugene Yokota (eed3si9n)、Jorge Vicente Cantero (jvican)、Łukasz Wawrzyk、Dale Wijnand、Andrea Peruffo、Kenji Yoshida (xuwei-k)、Guillaume Martres、Arnout Engelen、Jason Zaugg、Krzysztof Romanowski、Antonio Cunei、Mirco Dotta、OlegYch、Alex Dupre、Nepomuk Seiler、0lejk4、Alexandre Archambault、Eric Peters、Kazuhiro Sera、Philippus、Som Snytt、Syed Akber Jafri、Thomas Droxler、Veera Venky、bigwheel、Akhtyam Sakaev、Alexey Vakhrenev、Eugene Platonov、Helena Edelson、Ignasi Marimon-Clos、Julien Sirocchi、Justin Kaeser、Kajetan Maliszewski、Leonard Ehrenfried、Mikołaj Jakubowski、Nafer Sanabria、Stefan Wachter、Yasuhiro Tatsuno、Yusuke Izawa、falmarri、ilya、kai-chi、tanishiking、Ólafur Páll Geirsson。 謝謝!