キャッシュ化タスク

このページではキャッシュ化タスクの詳細を説明する。概要の解説はキャッシュ化を参照。

自動キャッシュ

val someKey = taskKey[String]("something")

someKey := name.value + version.value + "!"

sbt 2.x では、タスク結果は nameversion の 2 つのセッティングに基づいて自動的にキャッシュされる。初回実行時はオンサイトで実行されるが、2 回目以降はディスクキャッシュが使用される:

sbt:demo> show someKey
[info] demo0.1.0-SNAPSHOT!
[success] elapsed time: 0 s, cache 0%, 1 onsite task
sbt:demo> show someKey
[info] demo0.1.0-SNAPSHOT!
[success] elapsed time: 0 s, cache 100%, 1 disk cache hit

キャッシュ化はシリアライズ困難である

自動キャッシュに参加するには、入力キー(例: nameversion)が sjsonnew.HashWriter 型クラス用の given インスタンスを提供し、戻り値の型が sjsonnew.JsonFormat 用の given インスタンスを提供する必要がある。

[error] -- Error: /Users/xxx/caching/project/FooPlugin.scala:17:4 -
[error]  17 |    foo := {
[error]     |    ^
[error]     |given evidence sjsonnew.JsonFormat[sbt.HashedVirtualFileRef] is not found; opt out of caching by annotating the key with @transient, or as foo := Def.uncached(...), or provide a given value
[error]     |
[error]  18 |      val b = baseDirectory.value

HashedVirtualFileRef のような組み込み型のコーデックは CacheImplicits.given にて提供される:

import CacheImplicits.given

....

  override lazy val projectSettings: Seq[Setting[?]] = Seq(
    foo := {
      val b = baseDirectory.value
      val conv = fileConverter.value
      conv.toVirtualFile((b / "build.sbt").toPath)
    },
    bar := foo.value,
  )

Contraband で sjson-new のコーデックを生成することもできる。

エフェクトの追跡

ファイル作成のエフェクト

ファイル名を返すだけでなく、ファイル作成のエフェクトをキャッシュするには、Def.declareOutput(vf) を使ってファイル作成のエフェクトを追跡する必要がある。

someKey := {
  val conv = fileConverter.value
  val out: java.nio.file.Path = createFile(...)
  val vf: xsbti.VirtualFile = conv.toVirtualFile(out)
  Def.declareOutput(vf)
  vf: xsbti.HashedVirtualFileRef
}

システム・プロパティーのエフェクト

キャッシュ化タスクからシステム・プロパティーや環境変数の捕獲するとキャッシュの安定性を破壊することがある。以下に具体例を用いて説明する:

build.sbt bad example

lazy val someInt = taskKey[Int]("")

// BAD
someInt := {
  sys.props("release").toInt + 1
}

まずは -Drelease=0 とともに実行したとする:

$ sbt --server -Drelease=0
....
sbt:caching> show someInt
[info] 1
[success] elapsed time: 0 s, cache 0%, 1 onsite task

しかし、次に -Drelease=1 とともに実行したとしても、値は変化しない:

$ sbt --server -Drelease=1
....
sbt:caching> show someInt
[info] 1
[success] elapsed time: 0 s, cache 100%, 1 disk cache hit

これは、someInt がタスク定義のコードの形と入力となるセッティングとタスクのみをキャッシュ・キーとして見ているためだ。someInt を正しくキャッシュ化するには、システム・プロパティーをセッティングで包む必要がある:

build.sbt

lazy val someInt = taskKey[Int]("")
lazy val releaseVer = settingKey[Int]("")

releaseVer := sys.props("release").toInt
someInt := releaseVer.value + 1

これでビルドが読み込まれるたびにシステム・プロパティーが再評価されるようになった:

$ sbt --server -Drelease=1
sbt:caching> show someInt
[info] 2
[success] elapsed time: 0 s, cache 0%, 1 onsite tas

キャッシュからのオプトアウト

タスクキー単位でのオプトアウト

次に、一部のタスクキーをキャッシュからオプトアウトしたい場合は、以下のようにキャッシュレベルを設定する:

@transient
val someKey = taskKey[String]("something")

または

@cacheLevel(include = Array.empty)
val someKey = taskKey[String]("something")

タスク単位でのオプトアウト

キャッシュを個別にオプトアウトするには、以下のように Def.uncached(...) を使用する:

val someKey = taskKey[String]("something")

someKey := Def.uncached {
  name.value + somethingUncachable.value + "!"
}

ビルド全体でのオプトアウト

デフォルトのカスタムタスクキャッシュをオプトアウトするには、project/plugins.sbt に以下を追加する:

Compile / scalacOptions += "-Xmacro-settings:sbt:no-default-task-cache"

Note

これはビルドで導入したカスタムタスクにのみ適用される。sbt またはプラグインが提供するキャッシュ化タスクはキャッシュされたままである。

リモート・キャッシュ

sbt 2.x は Bazel 互換の gRPC インターフェースを実装しており、オープンソースおよび商用の多数のバックエンドと連携する。詳細はリモート・キャッシュの設定を参照。