sbt 1.3.0

in

皆さんこんにちは。sbt プロジェクトを代表して sbt 1.3.0 をアナウンスします。これは sbt 1 のフィーチャーリリース第3弾で、バイナリ互換性は維持しつつ新機能にフォーカスを当てたリリースとなっている。sbt 1 は Semantic Versioning にもとづいてリリースされるので、プラグインは sbt 1.x シリーズ中機能することが期待されている。

sbt 1.3 の主な新機能はデフォルトでの Coursier を使ったライブラリ管理、ClassLoader レイヤリング、IO の改善、そして super shell だ。これらの機能の組み合わせがビルドのユーザーエクスペリエンスを向上することを願っている。

互換性に影響のある変更点

  • Coursier を用いたライブラリ管理。詳細は後ほど。
  • ClassLoader レイヤリング。詳細は後ほど。
  • super shell。詳細は後ほど。
  • マルチコマンドの先頭にセミコロンが要らなくなった。clean;Test/compile; で動作するようになった。 #4456 by @eatkins
  • sbt.internal.inc.ZincUtil 以下の関数で LM を使うものが ZincLmUtil に移動して、Zinc から LM に依存しないようになった。 zinc#655 by @dwijnand

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) が再び LM engine としての置き換えを行うためのいくつかの変更を Alex さんの協力のもと行った。

ClassLoader レイヤリング

sbt は、runtest タスクを実行するときはこれまでも 2レイヤーの ClassLoader を使ってきた。複数のタスク実行の間で scala パッケージを再利用するために、一番上のレイヤーの ClassLoader は Scala ライブラリ JAR を含む。2番目のレイヤーでライブラリ依存を含むプロジェクトクラスパスの残りの全てを読み込む。

sbt 1.3.0 はデフォルトで 3レイヤーの ClassLoader を使い、第2レイヤーはライブラリ依存性の JAR を読み込み、第3レイヤーはプロジェクトの JAR とクラスファイルを読み込む (Test コンフィグレーションは ClassLoaderLayeringStrategy.TestDependencies、その他は ClassLoaderLayeringStrategy.ScalaLibrary)。ライブラリ JAR の ClassLoader をキャッシュすることで、同セッション内で複数回 runtest を実行したときの立ち上がりが大幅に早くなる。ライブラリ JAR を何度も読み込まないことで GC への負荷も軽減されることが期待される。

注意: この変更はライブラリがプロジェクトクラスパス内のクラスを Java serialization を用いてシリアライズ、またはデシリアライズしたときに影響が出る可能性がある。その場合は、ClassLoaderLayeringStrategy.Flat を選ぶことでレイヤリングを拒否することができる。

sbt 1.3.0 は classLoaderLayeringStrategy セッティングを導入してこれらの設定を可能とする。

Compile / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.Flat
Compile / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.ScalaLibrary
Compile / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.RuntimeDependencies
 
Test / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.Flat
Test / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.ScalaLibrary
Test / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.RuntimeDependencies
Test / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.TestDependencies
Test / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.ShareRuntimeDependenciesLayerWithTestDependencies
  • ClassLoaderLayeringStrategy.Flat は Java ランタイム以外の全ての JAR ファイルを含む。この方法を選んだタスクは、(JVM の起動オーバーヘッドが無い) fork を使ったのとかなり近いふるまいになるはずだ。
  • ClassLoaderLayeringStrategy.ScalaLibrary は 2レイヤーの ClassLoader を作り、Scala 標準ライブラリが温かい状態に保たれる。
  • ClassLoaderLayeringStrategy.RuntimeDependencies は 3レイヤーの ClassLoader を作り、実行時ライブラリ依存性が温かい状態に保たれる。
  • ClassLoaderLayeringStrategy.TestDependencies は 3レイヤーの ClassLoader を作り、テストのライブラリ依存性が温かい状態に保たれる。
  • ClassLoaderLayeringStrategy.ShareRuntimeDependenciesLayerWithTestDependencies は 4レイヤーの ClassLoader を作り、テスト依存性とランタイム依存性で別のレイヤーが作られる。名前が示唆するように初めの 2つのレイヤーはランタイムレイヤーと共有されるため、コンパイル/ランタイムライブラリ依存性は一度だけ読み込まれる。注意: テスト依存性がランタイム依存性を evict した場合は不整合が発生する可能性がある。

ClassLoaderLayeringStrategy.RuntimeDependenciesClassLoaderLayeringStrategy.TestDependencies はそれぞれ runtest タスクの応答時間を改善し、GC 負荷を軽減するはずだ。

一方、ClassLoaderLayeringStrategy.Flat はレイヤー化した ClassLoader と相性の悪い一部のアプリケーションに役立つはずだ。その一例として、Java serialization と Scala コレクションで使われる serialization proxy pattern の組み合わせが挙げられる。

ClassLoader レイヤリングは Ethan Atkins さん (@eatkins) に #4476 としてコントリビュートしていただいた。

IO まわりの改善

ClassLoader レイヤリングの他にも sbt 1.3.0 は多くの性能向上を含む:

  • ディレクトリの一覧の高速化。sbt 内部で native ライブラリの swoval を用い、これは Java 標準ライブラリで使われるディレクトリの一覧を高速化するための native の OS API を呼び出すための JNI を提供する。
  • triggered execution 時のファイル変更の検知時間の短縮。多くの場合、ファイルイベントはタスクの評価を 10ms 以内に行う。

これを書いている時点では、sbt 1.3.0 の 5000 ファイルを使った edit-compile-test ループは、sbt 0.13、Gradle やその他の我々テストを行ったビルドツールで 3つのソースを edit-compile-test した場合よりも高速だという結果が出ている (詳細は build
performance
参照)。これらの変更は Ethan Atkins さん (@eatkins) にコントリビュートしていただいた

Glob

sbt 1.3.0 は Glob という新しいデータ型を導入し、これは Unix shell glob のようなパス検索クエリを表す。
例えば、プロジェクトディレクトリ内の全ての Scala ソースは Glob(baseDirectory.value, RecursiveGlob / "*.scala") もしくは、baseDirectory.value.toGlob / ** / "*.scala" と書くことができ、ここで **RecursiveGlob のエイリアスである。Glob は PathFinders を発展させたもので、IO オーバーヘッド無しで合成可能だ。Glob の取得は FileTreeView を用いる。例えば、以下のように書ける。

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

このとき FileTreeView はベースディレクトリは一度だけ走査する。Glob と FileTreeView は Ethan Atkins さん (@eatkins) より io#178io#216io#226 として追加された。

Watch の改善

sbt 1.3.0 は新しいファイル監視実装を導入する。これはファイル変更イベントを OS を用いて追跡するための 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]File、もしくは Seq[File] を返す場合ヘルパータスクを定義してよりインクリメンタルにすることが可能となった。

import java.nio.Path
val gccCompile = taskKey[Seq[Path]]("compile C code using gcc")
val gccLink = taskKey[Path]("link C code using gcc")
 
gccCompile / sourceDirectory := sourceDirectory.value
gccCompile / fileInputs := {
  val base: Glob = (gccCompile / sourceDirectory).value.toGlob
  base / ** / "*.c" :: base / "include" / "*.h" :: Nil
}
gccCompile / target := baseDirectory.value / "out" / "objects"
 
gccCompile := {
  gccCompile.previous match {
    val changedFiles: Option[Seq[Path]] = (gccCompile / changedInputFiles).value match {
      case Some(ChangedFiles(c, _, u)) => Some(c ++ u)
      case None => None
    }
    case Some(outputs: Seq[Path]) if changedFiles.isEmpty =>
      outputs
    case _ =>
      // do something and generate files in (gccCompile / target)
  }
}

上記の設定を行うと、gccCompile / allInputFiles は全ての入力ファイルを返し、gccCompile / changedInputFiles は以前の実行後に変更されたファイルのみを返す。これを gccCompile.previous 値と組み合わせることで不必要な処理を避けることができる。

gccLink といった別のタスクからは、gccCompile の結果も gccCompile / changedOutputFile を使って追従することが可能だ。

gccLink := {
  val changedObjs = (gccCompile / changedOutputFiles).value
  gccLink.previous match {
    case Some(p: Path) if changedObjs.isEmpty =>
      p
    case _ =>
      // do something
  }
}

この機能は Ethan Atkins さん (@eatkins) により #4627 としてコントリビュートしていただいた。

Super shell

ANSI 互換のターミナル内で sbt 1.3.0 を実行すると、sbt 1.3.0 は現在実行しているタスクを表示するようになった。これによってデベロッパーはどのタスクが並列で処理できているか、ビルドのどこで時間を消費しているかが分かるようになった。Gradle の "Rich Console" と Buck の "Super Console" に因んで、我々も "super shell" と呼ぶことにした。

使いたく無ければ、以下を build.sbt に書くか:

ThisBuild / useSuperShell := false

sbt を --supershell=false (もしくは -Dsbt.supershell=false) と実行することでオプトアウトできる。この機能は Eugene Yokota (@eed3si9n) により #4396/util#196 として追加された。

Tracing

To view the task breakdown visually, run sbt with --traces (or -Dsbt.traces=true). This will generate build.traces file, which is viewable using Chrome Tracing chrome://tracing/. This feature was contributed by Jason Zaugg (@retronym).

To output the task timings on screen, run sbt with --timings (or -Dsbt.task.timings=true -Dsbt.task.timings.on.shutdown=true).

SemanticDB support

sbt 1.3.0 makes it easier to generate SemanticDB. To enable the generation of SemanticDB build-wide:

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

This was added by @eed3si9n as #4410.

print command

sbt 1.3.0 adds a new print command, similar to show but prints directly to standard out.

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

This was contributed by David Knapp (@Falmarri) as #4341

Appending Function1

Function1 can be appened using +=.

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

This was contributed by Dale Wijnand (@dwijnand) as #4521.

JDK 11 support

sbt 1.3.0 is first release of sbt that's been testing on JDK11 extensively.
All integration tests on Travis CI are on AdoptOpenJDK's JDK 11, which were updated by @eed3si9n as #4389/zinc#639/[zinc640].

  • Fixes warnings on JDK 9+ by upgrading to protobuf 3.7.0 zinc#644 by @smarter
  • Fixes spurious rebuilds caused by invalidation of rt.jar on JDK 11 #4679 by @eatkins

Other bug fixes and improvements

  • Fixes cross building with a single-letter alias #4355 / #1074 by @eed3si9n
  • Removes old warning about global directory #4356 / #1054 by @eed3si9n
  • Improves JDK discovery for cross-JDK forking #4313 / #4462 by @raboof
  • Expands ~ in -Dsbt.global.base property to user home. #4367 by @kai-chi
  • Adds def sequential[A](tasks: Seq[Initialize[Task[A]]]): Initialize[Task[A]]. #4369 by @3tty0n
  • Fixes sbt server to send error event on command failure. #4378 by @andreaTP
  • Implements cancellation of request by LSP client. #4384 by @andreaTP
  • Implements "sbt/completion" command in sbt to server to complete sbt commands. #4397 by @andreaTP
  • Fixes errors order reported by sbt server. #4497 by @tdroxler
  • Fixes cached resolution. #4424 by @eed3si9n
  • The sbt task definition linter warns rather than errors by default.
    The linter can be disabled entirely by putting import sbt.dsl.LinterLevel.Ignore in scope. #4485 by @eatkins
  • Full GC is only automatically triggered when sbt has been idle for at least a
    minute and is only run at most once between shell commands. This improves shell
    responsiveness. #4544 by @eatkins
  • Avoids NPE in JDK12. #4549 by @retronym
  • Fixes the eviction warning summary lm#288 by @bigwheel
  • Fixes Zinc's flag to skip the persistence of API info. zinc#399 by @romanowski
  • Fixes Zinc not detecting synthetic top level member changes. #4316/zinc#572 by @jvican
  • Zinc to notify callback of generated non-local classes before the compiler's middle and backend phases. zinc#582 by @jvican
  • Removes a use of regex in Zinc for performance. zinc#583 by @retronym
  • Fixes incremental compilation involving default arguments. zinc#591 by @jvican
  • Adds Analysis callback of Zinc thread-safe. zinc#626 by @dotta
  • Fixes a non-zero exit Javadoc not failing the task. zinc#625 by @raboof

Participation

First, I'd like to introduce Ethan Atkins, a core community member of sbt project, and author of Close Watch that uses native code to provide watch service on macOS. Normally I don't publicize the number of commits, but here's the top 10 for sbt 1.3.0:

323 Ethan Atkins
261 Eugene Yokota (eed3si9n)
42 Jorge Vicente Cantero (jvican)
35 Łukasz Wawrzyk
28 Dale Wijnand
24 Andrea Peruffo
11 Guillaume Martres
7 Jason Zaugg
7 Kenji Yoshida (xuwei-k)
6 Arnout Engelen

As a community member, Ethan has contributed various IO related improvements to make sbt more responsive in his own time. sbt 1.3.0 reflects many of his ideas.

The last feature release of sbt 1 was sbt 1.2.0 in July, 2018. Since then, we've released eight patch releases under sbt 1.2.x for bug fixes, but most of the feature enhancements were merged to develop branch. Over the course of these months, 38 contributors contributors participated in sbt 1.3.0 and Zinc: Ethan Atkins, Eugene Yokota (eed3si9n), Jorge Vicente Cantero (jvican), Łukasz Wawrzyk, Dale Wijnand, Andrea Peruffo, Guillaume Martres, Jason Zaugg, Kenji Yoshida (xuwei-k), Arnout Engelen, Krzysztof Romanowski, Antonio Cunei, Mirco Dotta, OlegYch, Nepomuk Seiler, 0lejk4, Alex Dupre, Alexandre Archambault, Eric Peters, Kazuhiro Sera, Philippus, Som Snytt, Thomas Droxler, Veera Venky, bigwheel, Eugene Platonov, Helena Edelson, Ignasi Marimon-Clos, Julien Sirocchi, Justin Kaeser, Kajetan Maliszewski, Leonard Ehrenfried, Ólafur Páll Geirsson, Stefan Wachter, Yusuke Izawa, falmarri, kai-chi, tanishiking. Thank you!

Thanks to everyone who's helped improve sbt and Zinc 1 by using them, reporting bugs, improving our documentation, porting builds, porting plugins, and submitting and reviewing pull requests. For anyone interested in helping sbt, there are many avenues for you to help, depending on your interest. If you're interested, Contributing, sbt-contrib, "help wanted", "good first issue" are good starting points.