タスクと設定は入門ガイドで紹介されており、タスクでより詳細に説明されています。最初にそれらを読むことをお勧めします。
カスタムタスクを定義する場合、不要な作業を避けるために値をキャッシュすることがあります。
sbt.util.Cache
は基本的なキャッシング機能を提供します。
package sbt.util
/**
* A simple cache with keys of type `I` and values of type `O`
*/
trait Cache[I, O] {
/**
* Queries the cache backed with store `store` for key `key`.
*/
def apply(store: CacheStore)(key: I): CacheResult[O]
}
I
とO
の両方にsjsonnew.JsonFormat
インスタンスがある場合、sbt.util.CacheImplicits._
をインポートすることでCache[I, O]
のインスタンスを導出できます(これによりBasicJsonProtocol
もインポートされます)。
キャッシュを使用するには、CacheStore
(またはファイル)と実際の作業を行う関数を指定して、Cache.cached
を呼び出すことで、キャッシュされた関数を生成できます。通常、キャッシュストアはstreams.value.cacheStoreFactory / "something"
として作成されます。次のREPLの例では、一時ファイルからキャッシュストアを作成します。
scala> import sbt._, sbt.util.CacheImplicits._
import sbt._
import sbt.util.CacheImplicits._
scala> def doWork(i: Int): List[String] = {
println("working...")
Thread.sleep(1000)
List.fill(i)("foo")
}
doWork: (i: Int)List[String]
// use streams.value.cacheStoreFactory.make("something") for real tasks
scala> val store = sbt.util.CacheStore(file("/tmp/something"))
store: sbt.util.CacheStore = sbt.util.FileBasedStore@5a4a6716
scala> val cachedWork: Int => List[String] = Cache.cached(store)(doWork)
cachedWork: Int => List[String] = sbt.util.Cache$$$Lambda$5577/1548870528@3bb59fba
scala> cachedWork(1)
working...
res0: List[String] = List(foo)
scala> cachedWork(1)
res1: List[String] = List(foo)
scala> cachedWork(3)
working...
res2: List[String] = List(foo, foo, foo)
scala> cachedWork(1)
working...
res3: List[String] = List(foo)
ご覧のとおり、cachedWork(1)
は連続して呼び出されたときにキャッシュされます。
TaskKey
にはprevious
というメソッドがあり、Option[A]
を返します。これは軽量なトラッカーとして使用できます。最初に"hi"
を返し、後続の呼び出しで"!"
を追加するタスクを作成したいとします。hi
というTaskKey[String]
を定義し、その以前の値(Option[String]
型)を取得できます。最初の呼び出しでは以前の値はNone
になり、後続の呼び出しではSome(x)
になります。
lazy val hi = taskKey[String]("say hi again")
hi := {
import sbt.util.CacheImplicits._
val prev = hi.previous
prev match {
case None => "hi"
case Some(x) => x + "!"
}
}
sbtシェルからshow hi
を実行してテストできます。
sbt:hello> show hi
[info] hi
[success] Total time: 0 s, completed Aug 16, 2019 12:24:32 AM
sbt:hello> show hi
[info] hi!
[success] Total time: 0 s, completed Aug 16, 2019 12:24:33 AM
sbt:hello> show hi
[info] hi!!
[success] Total time: 0 s, completed Aug 16, 2019 12:24:34 AM
sbt:hello> show hi
[info] hi!!!
[success] Total time: 0 s, completed Aug 16, 2019 12:24:35 AM
各呼び出しで、hi.previous
にはhi
の評価結果の以前の結果が含まれています。
sbt.util.Tracked
は、他のトラッカーと組み合わせることができる部分キャッシング機能を提供します。
タスクキーに関連付けられた以前の値と同様に、sbt.util.Tracked.lastOutput
は最後に計算された値のトラッカーを作成します。Tracked.lastOutput
は、値を格納する場所に関してより柔軟性があります。(これにより、複数のタスク間で値を共有できます)。
最初にInt
を入力として受け取り、String
に変換しますが、後続の呼び出しでは"!"
を追加するとします。
scala> import sbt._, sbt.util.CacheImplicits._
import sbt._
import sbt.util.CacheImplicits._
// use streams.value.cacheStoreFactory.make("last") for real tasks
scala> val store = sbt.util.CacheStore(file("/tmp/last"))
store: sbt.util.CacheStore = sbt.util.FileBasedStore@5a4a6716
scala> val badCachedWork = Tracked.lastOutput[Int, String](store) {
case (in, None) => in.toString
case (in, Some(read)) => read + "!"
}
badCachedWork: Int => String = sbt.util.Tracked$$$Lambda$6326/638923124@68c6ff60
scala> badCachedWork(1)
res1: String = 1
scala> badCachedWork(1)
res2: String = 1!
scala> badCachedWork(2)
res3: String = 1!!
scala> badCachedWork(2)
res4: String = 1!!!
注記:Tracked.lastOutput
は、入力が変更された場合、キャッシュを無効化しません。
これを機能させるには、以下のTracked.inputChanged
セクションを参照してください。
入力パラメーターの変更を追跡するには、Tracked.inputChanged
を使用します。
scala> import sbt._, sbt.util.CacheImplicits._
import sbt._
import sbt.util.CacheImplicits._
// use streams.value.cacheStoreFactory.make("input") for real tasks
scala> val store = sbt.util.CacheStore(file("/tmp/input"))
store: sbt.util.CacheStore = sbt.util.FileBasedStore@5a4a6716
scala> val tracker = Tracked.inputChanged[Int, String](store) { case (changed, in) =>
if (changed) {
println("input changed")
}
in.toString
}
tracker: Int => String = sbt.util.Tracked$$$Lambda$6357/1296627950@6e6837e4
scala> tracker(1)
input changed
res6: String = 1
scala> tracker(1)
res7: String = 1
scala> tracker(2)
input changed
res8: String = 2
scala> tracker(2)
res9: String = 2
scala> tracker(1)
input changed
res10: String = 1
これで、Tracked.inputChanged
とTracked.lastOutput
をネストして、キャッシュの無効化を取り戻すことができます。
// use streams.value.cacheStoreFactory
scala> val cacheFactory = sbt.util.CacheStoreFactory(file("/tmp/cache"))
cacheFactory: sbt.util.CacheStoreFactory = sbt.util.DirectoryStoreFactory@3a3d3778
scala> def doWork(i: Int): String = {
println("working...")
Thread.sleep(1000)
i.toString
}
doWork: (i: Int)String
scala> val cachedWork2 = Tracked.inputChanged[Int, String](cacheFactory.make("input")) { case (changed: Boolean, in: Int) =>
val tracker = Tracked.lastOutput[Int, String](cacheFactory.make("last")) {
case (in, None) => doWork(in)
case (in, Some(read)) =>
if (changed) doWork(in)
else read
}
tracker(in)
}
cachedWork2: Int => String = sbt.util.Tracked$$$Lambda$6548/972308467@1c9788cc
scala> cachedWork2(1)
working...
res0: String = 1
scala> cachedWork2(1)
res1: String = 1
トラッカーや以前の値を組み合わせる利点の1つは、無効化のタイミングを制御できることです。たとえば、2回だけ機能するキャッシュを作成できます。
lazy val hi = taskKey[String]("say hi")
lazy val hiCount = taskKey[(String, Int)]("track number of the times hi was called")
hi := hiCount.value._1
hiCount := {
import sbt.util.CacheImplicits._
val prev = hiCount.previous
val s = streams.value
def doWork(x: String): String = {
s.log.info("working...")
Thread.sleep(1000)
x + "!"
}
val cachedWork = Tracked.inputChanged[String, (String, Int)](s.cacheStoreFactory.make("input")) { case (changed: Boolean, in: String) =>
prev match {
case None => (doWork(in), 0)
case Some((last, n)) =>
if (changed || n > 1) (doWork(in), 0)
else (last, n + 1)
}
}
cachedWork("hi")
}
これは、hiCount
タスクの以前の値を使用して呼び出された回数を追跡し、n > 1
の場合にキャッシュを無効化します。
sbt:hello> hi
[info] working...
[success] Total time: 1 s, completed Aug 17, 2019 10:36:34 AM
sbt:hello> hi
[success] Total time: 0 s, completed Aug 17, 2019 10:36:35 AM
sbt:hello> hi
[success] Total time: 0 s, completed Aug 17, 2019 10:36:38 AM
sbt:hello> hi
[info] working...
[success] Total time: 1 s, completed Aug 17, 2019 10:36:40 AM
ファイルはキャッシングターゲットとして頻繁に登場しますが、java.io.File
はファイル名だけを保持するため、キャッシングの目的ではそれ自体ではあまり役に立ちません。
ファイルキャッシングのために、sbtはsbt.util.FileFunction.cached(...)という機能を提供して、ファイルの入出力結果をキャッシュします。次の例では、*.md
の行数をカウントし、行数を内容としてクロス ターゲット ディレクトリに*.md
を出力するキャッシュされたタスクを実装しています。
lazy val countInput = taskKey[Seq[File]]("")
lazy val countFiles = taskKey[Seq[File]]("")
def doCount(in: Set[File], outDir: File): Set[File] =
in map { source =>
val out = outDir / source.getName
val c = IO.readLines(source).size
IO.write(out, c + "\n")
out
}
lazy val root = (project in file("."))
.settings(
countInput :=
sbt.nio.file.FileTreeView.default
.list(Glob(baseDirectory.value + "/*.md"))
.map(_._1.toFile),
countFiles := {
val s = streams.value
val in = countInput.value
val t = crossTarget.value
// wraps a function doCount in an up-to-date check
val cachedFun = FileFunction.cached(s.cacheDirectory / "count") { (in: Set[File]) =>
doCount(in, t): Set[File]
}
// Applies the cached function to the inputs files
cachedFun(in.toSet).toSeq.sorted
},
)
最初の引数リストには、ファイル追跡スタイルを明示的に指定できる追加の引数が2つあります。デフォルトでは、入力追跡スタイルはファイルの最終更新時刻に基づくFilesInfo.lastModified
であり、出力追跡スタイルはファイルの存在のみに基づくFilesInfo.exists
です。
FileInfo.exists
はファイルが存在するかどうかを追跡します。FileInfo.lastModified
は最終更新時刻を追跡します。FileInfo.hash
はSHA-1コンテンツハッシュを追跡します。FileInfo.full
は最終更新時刻とコンテンツハッシュの両方を追跡します。scala> FileInfo.exists(file("/tmp/cache/last"))
res23: sbt.util.PlainFileInfo = PlainFile(/tmp/cache/last,true)
scala> FileInfo.lastModified(file("/tmp/cache/last"))
res24: sbt.util.ModifiedFileInfo = FileModified(/tmp/cache/last,1565855326328)
scala> FileInfo.hash(file("/tmp/cache/last"))
res25: sbt.util.HashFileInfo = FileHash(/tmp/cache/last,List(-89, -11, 75, 97, 65, -109, -74, -126, -124, 43, 37, -16, 9, -92, -70, -100, -82, 95, 93, -112))
scala> FileInfo.full(file("/tmp/cache/last"))
res26: sbt.util.HashModifiedFileInfo = FileHashModified(/tmp/cache/last,List(-89, -11, 75, 97, 65, -109, -74, -126, -124, 43, 37, -16, 9, -92, -70, -100, -82, 95, 93, -112),1565855326328)
sbt.util.FilesInfo
もあり、File
のSet
を受け取ります(ただし、使用している複雑な抽象型のため、常に動作するとは限りません)。
scala> FilesInfo.exists(Set(file("/tmp/cache/last"), file("/tmp/cache/nonexistent")))
res31: sbt.util.FilesInfo[_1.F] forSome { val _1: sbt.util.FileInfo.Style } = FilesInfo(Set(PlainFile(/tmp/cache/last,true), PlainFile(/tmp/cache/nonexistent,false)))
次の例では、README.md
の行数をカウントするキャッシュされたタスクを実装しています。
lazy val count = taskKey[Int]("")
count := {
import sbt.util.CacheImplicits._
val prev = count.previous
val s = streams.value
val toCount = baseDirectory.value / "README.md"
def doCount(source: File): Int = {
s.log.info("working...")
IO.readLines(source).size
}
val cachedCount = Tracked.inputChanged[ModifiedFileInfo, Int](s.cacheStoreFactory.make("input")) {
(changed: Boolean, in: ModifiedFileInfo) =>
prev match {
case None => doCount(in.file)
case Some(last) =>
if (changed) doCount(in.file)
else last
}
}
cachedCount(FileInfo.lastModified(toCount))
}
sbtシェルからshow count
を実行して試すことができます。
sbt:hello> show count
[info] working...
[info] 2
[success] Total time: 0 s, completed Aug 16, 2019 9:58:38 PM
sbt:hello> show count
[info] 2
[success] Total time: 0 s, completed Aug 16, 2019 9:58:39 PM
// change something in README.md
sbt:hello> show count
[info] working...
[info] 3
[success] Total time: 0 s, completed Aug 16, 2019 9:58:44 PM
これは、sbt.util.FileInfo
が自身を永続化するためにJsonFormat
を実装しているため、すぐに動作します。
追跡は、ファイルにスタンプ(ファイル属性の収集)を付け、スタンプをキャッシュに格納し、後で比較することによって機能します。場合によっては、スタンプのタイミングに注意することが重要です。TypeScriptファイルをフォーマットし、SHA-1ハッシュを使用して変更を検出したいとします。フォーマッタを実行する前にファイルにスタンプを付けると、タスクへの後続の呼び出しでキャッシュが無効になります。これは、フォーマッタ自体がTypeScriptファイルを修正する可能性があるためです。
これを防ぐには、Tracked.outputChanged
を使用して作業が完了した後にスタンプを付けます。
lazy val compileTypeScript = taskKey[Unit]("compiles *.ts files")
lazy val formatTypeScript = taskKey[Seq[File]]("format *.ts files")
compileTypeScript / sources := (baseDirectory.value / "src").globRecursive("*.ts").get
formatTypeScript := {
import sbt.util.CacheImplicits._
val s = streams.value
val files = (compileTypeScript / sources).value
def doFormat(source: File): File = {
s.log.info(s"formatting $source")
val lines = IO.readLines(source)
IO.writeLines(source, lines ++ List("// something"))
source
}
val tracker = Tracked.outputChanged(s.cacheStoreFactory.make("output")) {
(outChanged: Boolean, outputs: Seq[HashFileInfo]) =>
if (outChanged) outputs map { info => doFormat(info.file) }
else outputs map { _.file }
}
tracker(() => files.map(FileInfo.hash(_)))
}
sbtシェルから`formatTypeScript`と入力して、その動作を確認してください。
sbt:hello> formatTypeScript
[info] formatting /Users/eed3si9n/work/hellotest/src/util.ts
[info] formatting /Users/eed3si9n/work/hellotest/src/hello.ts
[success] Total time: 0 s, completed Aug 17, 2019 10:07:30 AM
sbt:hello> formatTypeScript
[success] Total time: 0 s, completed Aug 17, 2019 10:07:32 AM
この実装の潜在的な欠点の1つは、ファイルの変更に関するtrue/false
情報しか持っていないことです。これにより、1つのファイルが変更されるたびに、すべてのファイルの再フォーマットが行われる可能性があります。
// make change to one file
sbt:hello> formatTypeScript
[info] formatting /Users/eed3si9n/work/hellotest/src/util.ts
[info] formatting /Users/eed3si9n/work/hellotest/src/hello.ts
[success] Total time: 0 s, completed Aug 17, 2019 10:13:47 AM
このオールオアナッシングの動作を防ぐには、以下のTracked.diffOuputs
を参照してください。
Tracked.outputChanged
の別の潜在的な用途は、FileInfo.exists(_)
と組み合わせて、出力ファイルがまだ存在するかどうかを追跡することです。キャッシュも格納されているtarget
ディレクトリに出力する場合は、通常は必要ありません。
Tracked.inputChanged
トラッカーはBoolean
値しか提供しないため、キャッシュが無効化されると、すべての作業をやり直す必要があります。違いを追跡するにはTracked.diffInputs
を使用します。
Tracked.diffInputs
はsbt.util.ChangeReport
というデータ型を報告します。
/** The result of comparing some current set of objects against a previous set of objects.*/
trait ChangeReport[T] {
/** The set of all of the objects in the current set.*/
def checked: Set[T]
/** All of the objects that are in the same state in the current and reference sets.*/
def unmodified: Set[T]
/**
* All checked objects that are not in the same state as the reference. This includes objects that are in both
* sets but have changed and files that are only in one set.
*/
def modified: Set[T] // all changes, including added
/** All objects that are only in the current set.*/
def added: Set[T]
/** All objects only in the previous set*/
def removed: Set[T]
def +++(other: ChangeReport[T]): ChangeReport[T] = new CompoundChangeReport(this, other)
....
}
レポートの動作を印刷して確認してみましょう。
lazy val compileTypeScript = taskKey[Unit]("compiles *.ts files")
compileTypeScript / sources := (baseDirectory.value / "src").globRecursive("*.ts").get
compileTypeScript := {
val s = streams.value
val files = (compileTypeScript / sources).value
Tracked.diffInputs(s.cacheStoreFactory.make("input_diff"), FileInfo.lastModified)(files.toSet) {
(inDiff: ChangeReport[File]) =>
s.log.info(inDiff.toString)
}
}
たとえば、ファイルの名前を変更した場合の表示方法は次のとおりです。
sbt:hello> compileTypeScript
[info] Change report:
[info] Checked: /Users/eed3si9n/work/hellotest/src/util.ts, /Users/eed3si9n/work/hellotest/src/hello.ts
[info] Modified: /Users/eed3si9n/work/hellotest/src/util.ts, /Users/eed3si9n/work/hellotest/src/hello.ts
[info] Unmodified:
[info] Added: /Users/eed3si9n/work/hellotest/src/util.ts, /Users/eed3si9n/work/hellotest/src/hello.ts
[info] Removed:
[success] Total time: 0 s, completed Aug 17, 2019 10:42:50 AM
sbt:hello> compileTypeScript
[info] Change report:
[info] Checked: /Users/eed3si9n/work/hellotest/src/util.ts, /Users/eed3si9n/work/hellotest/src/bye.ts
[info] Modified: /Users/eed3si9n/work/hellotest/src/hello.ts, /Users/eed3si9n/work/hellotest/src/bye.ts
[info] Unmodified: /Users/eed3si9n/work/hellotest/src/util.ts
[info] Added: /Users/eed3si9n/work/hellotest/src/bye.ts
[info] Removed: /Users/eed3si9n/work/hellotest/src/hello.ts
[success] Total time: 0 s, completed Aug 17, 2019 10:43:37 AM
*.ts
ファイルと*.js
ファイルのマッピングがあれば、コンパイルをよりインクリメンタルにすることができます。Scalaのインクリメンタルコンパイルでは、Zincは*.scala
と*.class
ファイルの関係と*.scala
間の関係の両方を追跡します。TypeScriptで同様のものを作成できます。次の内容をproject/TypeScript.scala
として保存します。
import sbt._
import sjsonnew.{ :*:, LList, LNil}
import sbt.util.CacheImplicits._
/**
* products - products keep the mapping between source *.ts files and *.js files that are generated.
* references - references keep the mapping between *.ts files referencing other *.ts files.
*/
case class TypeScriptAnalysis(products: List[(File, File)], references: List[(File, File)]) {
def ++(that: TypeScriptAnalysis): TypeScriptAnalysis =
TypeScriptAnalysis(products ++ that.products, references ++ that.references)
}
object TypeScriptAnalysis {
implicit val analysisIso = LList.iso(
{ a: TypeScriptAnalysis => ("products", a.products) :*: ("references", a.references) :*: LNil },
{ in: List[(File, File)] :*: List[(File, File)] :*: LNil => TypeScriptAnalysis(in._1, in._2) })
}
build.sbt
では
lazy val compileTypeScript = taskKey[TypeScriptAnalysis]("compiles *.ts files")
compileTypeScript / sources := (baseDirectory.value / "src").globRecursive("*.ts").get
compileTypeScript / target := target.value / "js"
compileTypeScript := {
import sbt.util.CacheImplicits._
val prev0 = compileTypeScript.previous
val prev = prev0.getOrElse(TypeScriptAnalysis(Nil, Nil))
val s = streams.value
val files = (compileTypeScript / sources).value
def doCompile(source: File): TypeScriptAnalysis = {
println("working...")
val out = (compileTypeScript / target).value / source.getName.replaceAll("""\.ts$""", ".js")
IO.touch(out)
// add a fake reference from any file to util.ts
val references: List[(File, File)] =
if (source.getName != "util.ts") List(source -> (baseDirectory.value / "src" / "util.ts"))
else Nil
TypeScriptAnalysis(List(source -> out), references)
}
Tracked.diffInputs(s.cacheStoreFactory.make("input_diff"), FileInfo.lastModified)(files.toSet) {
(inDiff: ChangeReport[File]) =>
val products = scala.collection.mutable.ListBuffer(prev.products: _*)
val references = scala.collection.mutable.ListBuffer(prev.references: _*)
val initial = inDiff.modified & inDiff.checked
val reverseRefs = initial.flatMap(x => Set(x) ++ references.collect({ case (k, `x`) => k }).toSet )
products --= products.filter({ case (k, v) => reverseRefs(k) || inDiff.removed(k) })
references --= references.filter({ case (k, v) => reverseRefs(k) || inDiff.removed(k) })
reverseRefs foreach { x =>
val temp = doCompile(x)
products ++= temp.products
references ++= temp.references
}
TypeScriptAnalysis(products.toList, references.toList)
}
}
上記は、target/js
の下に.js
ファイルを作成するだけの偽のコンパイルです。
sbt:hello> compileTypeScript
working...
working...
[success] Total time: 0 s, completed Aug 16, 2019 10:22:58 PM
sbt:hello> compileTypeScript
[success] Total time: 0 s, completed Aug 16, 2019 10:23:03 PM
hello.ts
からutil.ts
への参照を追加したので、src/util.ts
を変更した場合、src/util.ts
とsrc/hello.ts
の両方のコンパイルがトリガーされるはずです。
sbt:hello> show compileTypeScript
working...
working...
[info] TypeScriptAnalysis(List((/Users/eed3si9n/work/hellotest/src/util.ts,/Users/eed3si9n/work/hellotest/target/js/util.ts), (/Users/eed3si9n/work/hellotest/src/hello.ts,/Users/eed3si9n/work/hellotest/target/js/hello.ts)),List((/Users/eed3si9n/work/hellotest/src/hello.ts,/Users/eed3si9n/work/hellotest/src/util.ts)))
動作します。
Tracked.diffOutputs
は、Tracked.outputChanged
よりも詳細なバージョンで、作業完了後にスタンプを打ち、変更されたファイルのセットも報告できます。
これを使用して、変更されたTypeScriptファイルのみをフォーマットできます。
lazy val formatTypeScript = taskKey[Seq[File]]("format *.ts files")
compileTypeScript / sources := (baseDirectory.value / "src").globRecursive("*.ts").get
formatTypeScript := {
val s = streams.value
val files = (compileTypeScript / sources).value
def doFormat(source: File): File = {
s.log.info(s"formatting $source")
val lines = IO.readLines(source)
IO.writeLines(source, lines ++ List("// something"))
source
}
Tracked.diffOutputs(s.cacheStoreFactory.make("output_diff"), FileInfo.hash)(files.toSet) {
(outDiff: ChangeReport[File]) =>
val initial = outDiff.modified & outDiff.checked
initial.toList map doFormat
}
}
シェルでのformatTypeScript
の例を示します。
sbt:hello> formatTypeScript
[info] formatting /Users/eed3si9n/work/hellotest/src/util.ts
[info] formatting /Users/eed3si9n/work/hellotest/src/hello.ts
[success] Total time: 0 s, completed Aug 17, 2019 9:28:56 AM
sbt:hello> formatTypeScript
[success] Total time: 0 s, completed Aug 17, 2019 9:28:58 AM
sbt-scalafmtは、scalafmt
とscalafmtCheck
タスクを実装し、互いに連携します。例えば、scalafmt
が正常に実行され、ソースに変更がない場合は、scalafmtCheck
のチェックをスキップします。
その実装例のスニペットを以下に示します。
private def cachedCheckSources(
cacheStoreFactory: CacheStoreFactory,
sources: Seq[File],
config: Path,
log: Logger,
writer: PrintWriter
): ScalafmtAnalysis = {
trackSourcesAndConfig(cacheStoreFactory, sources, config) {
(outDiff, configChanged, prev) =>
log.debug(outDiff.toString)
val updatedOrAdded = outDiff.modified & outDiff.checked
val filesToCheck =
if (configChanged) sources
else updatedOrAdded.toList
val failed = prev.failed filter { _.exists }
val files = (filesToCheck ++ failed.toSet).toSeq
val result = checkSources(files, config, log, writer)
// cachedCheckSources moved the outDiff cursor forward,
// save filesToCheck so scalafmt can later run formatting
prev.copy(
failed = result.failed,
pending = (prev.pending ++ filesToCheck).distinct
)
}
}
private def trackSourcesAndConfig(
cacheStoreFactory: CacheStoreFactory,
sources: Seq[File],
config: Path
)(
f: (ChangeReport[File], Boolean, ScalafmtAnalysis) => ScalafmtAnalysis
): ScalafmtAnalysis = {
val prevTracker = Tracked.lastOutput[Unit, ScalafmtAnalysis](cacheStoreFactory.make("last")) {
(_, prev0) =>
val prev = prev0.getOrElse(ScalafmtAnalysis(Nil, Nil))
val tracker = Tracked.inputChanged[HashFileInfo, ScalafmtAnalysis](cacheStoreFactory.make("config")) {
case (configChanged, configHash) =>
Tracked.diffOutputs(cacheStoreFactory.make("output-diff"), FileInfo.lastModified)(sources.toSet) {
(outDiff: ChangeReport[File]) =>
f(outDiff, configChanged, prev)
}
}
tracker(FileInfo.hash(config.toFile))
}
prevTracker(())
}
上記では、trackSourcesAndConfig
は、設定ファイル、ソースの最終更新スタンプ、2つのタスク間で共有される以前の値を追跡する三重にネストされたトラッカーです。2つの異なるタスク間で以前の値を共有するために、キーに関連付けられた.previous
メソッドではなく、Tracked.lastOutput
を使用しています。
必要な制御レベルに応じて、sbtは値とファイルをキャッシュおよび追跡するための柔軟なユーティリティセットを提供します。
.previous
、FileFunction.cached
、およびCache.cached
は、開始するための基本的なキャッシュです。Tracked.inputChanged
を使用します。FileInfo.exists
、FileInfo.lastModified
、およびFileInfo.hash
を使用して値として追跡できます。Tracked
は、入力の無効化、出力の無効化、差分の追跡を行うために、多くの場合ネストされたトラッカーを提供します。