Cached task

This page covers the cached task details. See Caching for a general explanation.

Automatic caching

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

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

In sbt 2.x, the task result will be automatically cached based on the two settings name and version. The first time we run the task it will be executed onsite, but the second time onward, it will use the disk cache:

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

Caching is serialization-hard

To participate in the automatic caching, the input keys (e.g. name and version) must provide a given instance for sjsonnew.HashWriter typeclass and return type must provide a given instance for sjsonnew.JsonFormat.

[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

Codecs for some of the built-in types like HashedVirtualFileRef can be made available using 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 can be used to generate sjson-new codecs.

Effect tracking

The effect of file creation

To cache the effect of file creation, not just returning the name of the file, we need to track the effect of file creation using 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
}

The effect of system properties

Capturing a system property or an environment variable in a cached task can break the cache stability. Consider the following cached task:

build.sbt bad example

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

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

Suppose we run it first with -Drelease=0:

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

However, even if we run with -Drelease=1 the value does not change:

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

This is because someInt only looks at the shape of its task definition and the input settings and tasks as cache key. To correctly cache someInt, wrap the system property as a setting as follows:

build.sbt

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

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

This will reevaluate the system property each time the build loads:

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

Opting out from caching

Per-task-key opt-out

Next, if you want to opt some task keys from caching, you can set the cache level as follows:

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

or

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

Per-task opt-out

To opt out of the cache individually, use Def.uncached(...) as follows:

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

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

Build-wide opt-out

To opt out of by-default custom task caching, add the following to project/plugins.sbt:

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

Note

This applies only to the custom tasks introduced in the build. Any cached tasks provided by sbt or plugins will remain cached.

Execution log

To debug caching issues, sbt ships with execution log feature. The execution log can be enabled with sbt.experimental_execution_log system property set to either true or a file path:

$ sbt --server -Dsbt.experimental_execution_log=true compile

When set to true, an execution log will be created target/global-logging:

{
  "input": {
    "digest": "sha256-a50da1cd086987bc273861a815da4e90ba4735d4a21b965e861e583382a985d6/48",
    "codeContentHash": "murmur3-0000000000000000ffffffffce70de5a/0",
    "extraHash": "murmur3-00000000000000000000000000000000/0",
    "str": "(CompileInputs2(Vector(
${CSR_CACHE}/https/repo1.maven.org/maven2/org/scala-lang/scala3-library_3/3.7.4/scala3-library_3-3.7.4.jar, ...))))"
  },
  "cacheHit": true,
  "exitCode": 0,
  "outputs": [
    "${OUT}/value/sha256-a50da1cd086987bc273861a815da4e90ba4735d4a21b965e861e583382a985d6/48.json>
    sha256-f222bfd59f319bb6a2d4d58003b2131c0c42a10cdb4da9df971480ca8d044f7b/179",
  ]
}

This feature is under an experimental flag since the JSON format is subject to change.

Remote caching

sbt 2.x implements Bazel-compatible gRPC interface, which works with number of backend both open source and commercial. See Remote cache setup for more details.