search term:

sbt 1.3.0

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

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

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

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.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 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.

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.

Other bug fixes and improvements

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.