mirror of https://github.com/sbt/sbt.git
[2.0.x] Cache analysis using file last modified
**Problem** Current MixedAnalyzingCompiler analysis cache caches using the last write, assuming all writing happens via it. However, this doesn't work with sbt 2.x caching system where the gz file under it can switch. **Solution** This switches to using caffeine caching, which includes file size and file timestamp for local analysis caching.
This commit is contained in:
parent
5ebc20ac32
commit
0712811dff
|
|
@ -79,18 +79,13 @@ import sjsonnew.*
|
|||
import scala.annotation.nowarn
|
||||
import scala.collection.immutable.ListMap
|
||||
import scala.concurrent.duration.*
|
||||
import scala.jdk.OptionConverters.*
|
||||
import scala.util.control.NonFatal
|
||||
import scala.xml.NodeSeq
|
||||
|
||||
// incremental compiler
|
||||
import sbt.SlashSyntax0.*
|
||||
import sbt.internal.inc.{
|
||||
Analysis,
|
||||
AnalyzingCompiler,
|
||||
ManagedLoggedReporter,
|
||||
MixedAnalyzingCompiler,
|
||||
ScalaInstance
|
||||
}
|
||||
import sbt.internal.inc.{ Analysis, AnalyzingCompiler, ManagedLoggedReporter, ScalaInstance }
|
||||
import sbt.internal.io.Retry
|
||||
import xsbti.{
|
||||
AppConfiguration,
|
||||
|
|
@ -2181,8 +2176,9 @@ object Defaults extends BuildCommon {
|
|||
val setup: Setup = compileIncSetup.value
|
||||
val _ = compileIncremental.value
|
||||
val exportP = exportPipelining.value
|
||||
val c = fileConverter.value
|
||||
// Save analysis midway if pipelining is enabled
|
||||
val store = analysisStore(compileAnalysisFile)
|
||||
val store = analysisStore(compileAnalysisFile.value.toPath(), c)
|
||||
val contents = store.unsafeGet()
|
||||
if (exportP) {
|
||||
// this stores the early analysis (again) in case the subproject contains a macro
|
||||
|
|
@ -2207,8 +2203,9 @@ object Defaults extends BuildCommon {
|
|||
.debug(s"${name.value}: compileEarly: blocking on earlyOutputPing")
|
||||
earlyOutputPing.await.value
|
||||
}) {
|
||||
val store = analysisStore(earlyCompileAnalysisFile)
|
||||
store.get.toOption match {
|
||||
val c = fileConverter.value
|
||||
val store = analysisStore(earlyCompileAnalysisFile.value.toPath(), c)
|
||||
store.get.toScala match {
|
||||
case Some(contents) => contents.getAnalysis
|
||||
case _ => Analysis.empty
|
||||
}
|
||||
|
|
@ -2219,8 +2216,8 @@ object Defaults extends BuildCommon {
|
|||
|
||||
def compileTask: Initialize[Task[CompileAnalysis]] = Def.task {
|
||||
val setup: Setup = compileIncSetup.value
|
||||
val store = analysisStore(compileAnalysisFile)
|
||||
val c = fileConverter.value
|
||||
val store = analysisStore(compileAnalysisFile.value.toPath(), c)
|
||||
// TODO - expose bytecode manipulation phase.
|
||||
val analysisResult: CompileResult = manipulateBytecode.value
|
||||
if (analysisResult.hasModified) {
|
||||
|
|
@ -2247,7 +2244,7 @@ object Defaults extends BuildCommon {
|
|||
val dir = c.toPath(backendOutput.value).toFile
|
||||
result match
|
||||
case Result.Value(res) =>
|
||||
val store = analysisStore(compileAnalysisFile)
|
||||
val store = analysisStore(compileAnalysisFile.value.toPath(), c)
|
||||
val analysis = store.unsafeGet().getAnalysis()
|
||||
reporter.sendSuccessReport(analysis)
|
||||
bspTask.notifySuccess(analysis)
|
||||
|
|
@ -2270,8 +2267,8 @@ object Defaults extends BuildCommon {
|
|||
val ci2 = (compile / compileInputs2).value
|
||||
val ping = (TaskZero / earlyOutputPing).value
|
||||
val setup: Setup = (TaskZero / compileIncSetup).value
|
||||
val store = analysisStore(compileAnalysisFile)
|
||||
val c = fileConverter.value
|
||||
val store = analysisStore(compileAnalysisFile.value.toPath(), c)
|
||||
// TODO - Should readAnalysis + saveAnalysis be scoped by the compile task too?
|
||||
val analysisResult = Retry.io(compileIncrementalTaskImpl(bspTask, s, ci, ping))
|
||||
val analysisOut = c.toVirtualFile(setup.cachePath())
|
||||
|
|
@ -2354,7 +2351,7 @@ object Defaults extends BuildCommon {
|
|||
override def definesClass(classpathEntry: VirtualFile): DefinesClass =
|
||||
cachedPerEntryDefinesClassLookup(classpathEntry)
|
||||
val extra = extraIncOptions.value.map(t2)
|
||||
val store = analysisStore(earlyCompileAnalysisFile)
|
||||
val store = analysisStore(earlyCompileAnalysisFile.value.toPath(), converter)
|
||||
val eaOpt = if exportPipelining.value then Some(store) else None
|
||||
Setup.of(
|
||||
lookup,
|
||||
|
|
@ -2469,8 +2466,9 @@ object Defaults extends BuildCommon {
|
|||
def compileAnalysisSettings: Seq[Setting[?]] = Seq(
|
||||
previousCompile := Def.uncached {
|
||||
val setup = compileIncSetup.value
|
||||
val store = analysisStore(compileAnalysisFile)
|
||||
val prev = store.get().toOption match {
|
||||
val c = fileConverter.value
|
||||
val store = analysisStore(compileAnalysisFile.value.toPath(), c)
|
||||
val prev = store.get().toScala match {
|
||||
case Some(contents) =>
|
||||
val analysis = Option(contents.getAnalysis).toOptional
|
||||
val setup = Option(contents.getMiniSetup).toOptional
|
||||
|
|
@ -2481,11 +2479,8 @@ object Defaults extends BuildCommon {
|
|||
}
|
||||
)
|
||||
|
||||
private inline def analysisStore(inline analysisFile: TaskKey[File]): AnalysisStore =
|
||||
MixedAnalyzingCompiler.staticCachedStore(
|
||||
analysisFile = analysisFile.value.toPath,
|
||||
useTextAnalysis = false,
|
||||
)
|
||||
private def analysisStore(path: NioPath, converter: FileConverter): AnalysisStore =
|
||||
BuildDef.cachedAnalysisStore(path, converter)
|
||||
|
||||
def printWarningsTask: Initialize[Task[Unit]] =
|
||||
Def.task {
|
||||
|
|
|
|||
|
|
@ -11,17 +11,19 @@ package internal
|
|||
|
||||
import com.github.benmanes.caffeine.cache.{ Cache as CCache, Caffeine, Weigher }
|
||||
import java.io.File
|
||||
import java.nio.file.{ Files, NoSuchFileException }
|
||||
import java.nio.file.{ Files, NoSuchFileException, Path as NioPath }
|
||||
import java.nio.file.attribute.BasicFileAttributes
|
||||
import java.util.Optional
|
||||
import Keys.{ organization, thisProject, autoGeneratedProject, publish, publishLocal, skip }
|
||||
import Def.Setting
|
||||
// import sbt.ProjectExtra.apply
|
||||
import sbt.io.Hash
|
||||
import sbt.internal.util.{ Attributed, StringAttributeMap }
|
||||
import sbt.internal.inc.{ FileAnalysisStore, ReflectUtilities }
|
||||
import sbt.internal.inc.{ FileAnalysisStore, MixedAnalyzingCompiler, ReflectUtilities }
|
||||
import sbt.util.CacheImplicits.given
|
||||
import scala.jdk.OptionConverters.*
|
||||
import xsbti.{ FileConverter, VirtualFileRef }
|
||||
import xsbti.compile.{ AnalysisContents, CompileAnalysis }
|
||||
import xsbti.compile.{ AnalysisContents, AnalysisStore, CompileAnalysis }
|
||||
|
||||
trait BuildDef {
|
||||
def projectDefinitions(@deprecated("unused", "") baseDirectory: File): Seq[Project] = projects
|
||||
|
|
@ -134,4 +136,39 @@ private[sbt] object BuildDef:
|
|||
content <- getContents(VirtualFileRef.of(ref))
|
||||
yield content.getAnalysis
|
||||
|
||||
private[sbt] def cachedAnalysisStore(path: NioPath, converter: FileConverter): AnalysisStore =
|
||||
CachedAnalysisStore(path, converter)
|
||||
|
||||
private class CachedAnalysisStore(path: NioPath, converter: FileConverter) extends AnalysisStore:
|
||||
private val underlying = MixedAnalyzingCompiler.staticCachedStore(
|
||||
analysisFile = path,
|
||||
useTextAnalysis = false,
|
||||
cacheLast = false,
|
||||
)
|
||||
override def get: Optional[AnalysisContents] =
|
||||
val ref: VirtualFileRef = converter.toVirtualFile(path)
|
||||
try
|
||||
val attrs = Files.readAttributes(path, classOf[BasicFileAttributes])
|
||||
if attrs.isDirectory then underlying.get
|
||||
else
|
||||
val lastModified = attrs.lastModifiedTime().toMillis()
|
||||
val sizeBytes = attrs.size()
|
||||
getOrElseUpdate(ref, lastModified, sizeBytes)(underlying.get.toScala).toJava
|
||||
catch case _: NoSuchFileException => underlying.get
|
||||
|
||||
override def unsafeGet: AnalysisContents = get.toScala.get
|
||||
|
||||
override def set(contents: AnalysisContents): Unit =
|
||||
val ref: VirtualFileRef = converter.toVirtualFile(path)
|
||||
try
|
||||
val attrs = Files.readAttributes(path, classOf[BasicFileAttributes])
|
||||
if attrs.isDirectory then underlying.set(contents)
|
||||
else
|
||||
val lastModified = attrs.lastModifiedTime().toMillis()
|
||||
val sizeBytes = attrs.size()
|
||||
inMemoryAnalysisCache.put(ref.id(), (Some(contents), lastModified, sizeBytes))
|
||||
underlying.set(contents)
|
||||
catch case _: NoSuchFileException => underlying.set(contents)
|
||||
|
||||
end CachedAnalysisStore
|
||||
end BuildDef
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ object Dependencies {
|
|||
sys.env.get("BUILD_VERSION") orElse sys.props.get("sbt.build.version")
|
||||
|
||||
// sbt modules
|
||||
val ioVersion = nightlyVersion.getOrElse("1.10.5")
|
||||
val zincVersion = nightlyVersion.getOrElse("2.0.0-M16")
|
||||
val ioVersion = nightlyVersion.getOrElse("1.12.0")
|
||||
val zincVersion = nightlyVersion.getOrElse("2.0.0-M17")
|
||||
|
||||
private val sbtIO = "org.scala-sbt" %% "io" % ioVersion
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
package example
|
||||
|
||||
object Main {
|
||||
def main(args: Array[String]): Unit = ()
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import sbt.internal.inc.Analysis
|
||||
import complete.DefaultParsers.*
|
||||
|
||||
scalaVersion := "2.13.18"
|
||||
|
||||
val checkStampSize = inputKey[Unit]("Verifies the accumulated number of iterations of incremental compilation.")
|
||||
checkStampSize := {
|
||||
val s = streams.value
|
||||
val expected: Int = (Space ~> NatBasic).parsed
|
||||
val analysis = (Compile / compile).value match
|
||||
case a: Analysis => a
|
||||
s.log.info(s"analysis: $analysis")
|
||||
val sourceStampSize = analysis.readStamps.getAllSourceStamps.size
|
||||
assert(sourceStampSize == expected, s"sourceStampSize = $sourceStampSize")
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
> checkStampSize 1
|
||||
|
||||
# move A.scala elsewhere
|
||||
$ copy-file A.scala A.txt
|
||||
$ delete A.scala
|
||||
> checkStampSize 0
|
||||
|
||||
# move A.scala back
|
||||
$ copy-file A.txt A.scala
|
||||
$ delete A.txt
|
||||
$ exists A.scala
|
||||
|
||||
> show Compile/sources
|
||||
|
||||
# if we recompile, we should regain stamp size 1
|
||||
> checkStampSize 1
|
||||
Loading…
Reference in New Issue