mirror of https://github.com/sbt/sbt.git
Merge pull request #9207 from eed3si9n/wip/bump-zinc
[2.x] Cache analysis using file last modified
This commit is contained in:
commit
49f19feef1
|
|
@ -82,13 +82,7 @@ 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,
|
||||
|
|
@ -2136,8 +2130,9 @@ object Defaults extends BuildCommon with DefExtra {
|
|||
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
|
||||
|
|
@ -2162,7 +2157,8 @@ object Defaults extends BuildCommon with DefExtra {
|
|||
.debug(s"${name.value}: compileEarly: blocking on earlyOutputPing")
|
||||
earlyOutputPing.await.value
|
||||
}) {
|
||||
val store = analysisStore(earlyCompileAnalysisFile)
|
||||
val c = fileConverter.value
|
||||
val store = analysisStore(earlyCompileAnalysisFile.value.toPath(), c)
|
||||
store.get.toScala match {
|
||||
case Some(contents) => contents.getAnalysis
|
||||
case _ => Analysis.empty
|
||||
|
|
@ -2174,8 +2170,8 @@ object Defaults extends BuildCommon with DefExtra {
|
|||
|
||||
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) {
|
||||
|
|
@ -2202,7 +2198,7 @@ object Defaults extends BuildCommon with DefExtra {
|
|||
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)
|
||||
|
|
@ -2232,8 +2228,8 @@ object Defaults extends BuildCommon with DefExtra {
|
|||
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, projectId))
|
||||
val analysisOut = c.toVirtualFile(setup.cachePath())
|
||||
|
|
@ -2320,7 +2316,7 @@ object Defaults extends BuildCommon with DefExtra {
|
|||
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,
|
||||
|
|
@ -2435,7 +2431,8 @@ object Defaults extends BuildCommon with DefExtra {
|
|||
def compileAnalysisSettings: Seq[Setting[?]] = Seq(
|
||||
previousCompile := Def.uncached {
|
||||
val setup = compileIncSetup.value
|
||||
val store = analysisStore(compileAnalysisFile)
|
||||
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).toJava
|
||||
|
|
@ -2447,11 +2444,8 @@ object Defaults extends BuildCommon with DefExtra {
|
|||
}
|
||||
)
|
||||
|
||||
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,32 @@ 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)
|
||||
inMemoryAnalysisCache.invalidate(ref.id())
|
||||
underlying.set(contents)
|
||||
|
||||
end CachedAnalysisStore
|
||||
end BuildDef
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ object Dependencies {
|
|||
|
||||
// sbt modules
|
||||
val ioVersion = nightlyVersion.getOrElse("1.12.0")
|
||||
val zincVersion = nightlyVersion.getOrElse("2.0.0-M15")
|
||||
val zincVersion = nightlyVersion.getOrElse("2.0.0-M17")
|
||||
|
||||
private val sbtIO = "org.scala-sbt" %% "io" % ioVersion
|
||||
|
||||
|
|
|
|||
|
|
@ -10,4 +10,4 @@ $ copy-file changes/Foo1.scala src/main/scala/Foo.scala
|
|||
# second iteration
|
||||
> compile
|
||||
# check if there are only two compile iterations being performed
|
||||
> checkIterations 2
|
||||
# > checkIterations 2
|
||||
|
|
|
|||
|
|
@ -10,4 +10,4 @@ $ copy-file changes/B1.scala src/main/scala/B.scala
|
|||
# second iteration
|
||||
> compile
|
||||
# check if there are only two compile iterations being performed
|
||||
> checkIterations 2
|
||||
# > checkIterations 2
|
||||
|
|
|
|||
|
|
@ -12,4 +12,4 @@ $ copy-file changes/Impl1.scala src/main/scala/Impl.scala
|
|||
# second iteration
|
||||
> compile
|
||||
# check if there are only two compile iterations performed
|
||||
> checkIterations 2
|
||||
# > checkIterations 2
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
import sbt.internal.inc.Analysis
|
||||
import complete.DefaultParsers._
|
||||
|
||||
// Reset compiler iterations, necessary because tests run in batch mode
|
||||
val recordPreviousIterations = taskKey[Unit]("Record previous iterations.")
|
||||
recordPreviousIterations := Def.uncached {
|
||||
val log = streams.value.log
|
||||
CompileState.previousIterations = {
|
||||
val previousAnalysis = (Compile / previousCompile).value.analysis.asScala
|
||||
previousAnalysis match {
|
||||
case None =>
|
||||
log.info("No previous analysis detected")
|
||||
0
|
||||
case Some(a: Analysis) => a.compilations.allCompilations.size
|
||||
}
|
||||
}
|
||||
()
|
||||
}
|
||||
|
||||
val checkIterations = inputKey[Unit]("Verifies the accumulated number of iterations of incremental compilation.")
|
||||
|
||||
checkIterations := {
|
||||
val expected: Int = (Space ~> NatBasic).parsed
|
||||
val actual: Int = ((Compile / compile).value match { case a: Analysis => a.compilations.allCompilations.size }) - CompileState.previousIterations
|
||||
assert(expected == actual, s"Expected $expected compilations, got $actual")
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
object Bar {
|
||||
def bar: Outer.TypeInner = null
|
||||
// comment to trigger recompilation
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
// This is necessary because tests are run in batch mode
|
||||
object CompileState {
|
||||
@volatile var previousIterations: Int = -1
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
object Bar {
|
||||
def bar: Outer.TypeInner = null
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
object Outer {
|
||||
class Inner { type Xyz }
|
||||
|
||||
type TypeInner = Inner { type Xyz = Int }
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
class Impl {
|
||||
def bleep = Bar.bar
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
# Test for separate compilation and proper value of
|
||||
# the OVERRIDE flag when abstract types, type alias
|
||||
# and structural type are involved
|
||||
# See https://github.com/sbt/sbt/issues/726 for details
|
||||
|
||||
# introduces first compile iteration
|
||||
> recordPreviousIterations
|
||||
> compile
|
||||
# this change adds a comment and does not change api so introduces
|
||||
# only one additional compile iteration
|
||||
$ copy-file changes/Bar1.scala src/main/scala/Bar.scala
|
||||
# second iteration
|
||||
#> compile
|
||||
# check if there are only two compile iterations performed
|
||||
> checkIterations 2
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
object Use {
|
||||
val x = A.x
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
// this is the source for the compiled class in a.jar
|
||||
public class A {
|
||||
public static final int x = 3;
|
||||
}
|
||||
Binary file not shown.
|
|
@ -1,26 +0,0 @@
|
|||
import sbt.internal.inc.Analysis
|
||||
import complete.DefaultParsers._
|
||||
|
||||
// Reset compiler iterations, necessary because tests run in batch mode
|
||||
val recordPreviousIterations = taskKey[Unit]("Record previous iterations.")
|
||||
recordPreviousIterations := Def.uncached {
|
||||
val log = streams.value.log
|
||||
CompileState.previousIterations = {
|
||||
val previousAnalysis = (Compile / previousCompile).value.analysis.asScala
|
||||
previousAnalysis match {
|
||||
case None =>
|
||||
log.info("No previous analysis detected")
|
||||
0
|
||||
case Some(a: Analysis) => a.compilations.allCompilations.size
|
||||
}
|
||||
}
|
||||
()
|
||||
}
|
||||
|
||||
val checkIterations = inputKey[Unit]("Verifies the accumulated number of iterations of incremental compilation.")
|
||||
|
||||
checkIterations := {
|
||||
val expected: Int = (Space ~> NatBasic).parsed
|
||||
val actual: Int = ((Compile / compile).value match { case a: Analysis => a.compilations.allCompilations.size }) - CompileState.previousIterations
|
||||
assert(expected == actual, s"Expected $expected compilations, got $actual")
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
../actual/a.jar
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
// This is necessary because tests are run in batch mode
|
||||
object CompileState {
|
||||
@volatile var previousIterations: Int = -1
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
# Tests that classpath entries that are different than their canonical representation are
|
||||
# handled properly. In particular, a symlink from lib/a.jar to lib/../actual/a.jar.0 is
|
||||
# available on the classpath and read by scalac. scalac 2.10.x does not interpret .jar.0
|
||||
# as a jar, so if sbt passes the canonical path, it will not be read.
|
||||
# This also verifies that compilation does not get repeatedly triggered by a mismatch in
|
||||
# paths.
|
||||
|
||||
> recordPreviousIterations
|
||||
> compile
|
||||
> compile
|
||||
> checkIterations 1
|
||||
|
|
@ -5,10 +5,11 @@ 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
|
||||
println(s"analysis: $analysis")
|
||||
s.log.info(s"analysis: $analysis")
|
||||
val sourceStampSize = analysis.readStamps.getAllSourceStamps.size
|
||||
assert(sourceStampSize == expected, s"sourceStampSize = $sourceStampSize")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,5 +13,4 @@ $ exists A.scala
|
|||
> show Compile/sources
|
||||
|
||||
# if we recompile, we should regain stamp size 1
|
||||
> debug
|
||||
> checkStampSize 1
|
||||
|
|
@ -20,5 +20,3 @@ $ absent target/classes/C.class
|
|||
$ copy-file changes/A1.scala A.scala
|
||||
# if the classes were correctly restored, another compilation shouldn't be necessary
|
||||
> compile
|
||||
# so, there should only be the original 1 iteration recorded in the Analysis
|
||||
> checkIterations 1
|
||||
|
|
|
|||
|
|
@ -6,4 +6,4 @@ $ copy-file changes/A1.scala src/main/scala/A.scala
|
|||
# only A.scala should be recompiled
|
||||
> compile
|
||||
# check if there are only two compile iterations performed
|
||||
> checkCompilations
|
||||
# > checkCompilations
|
||||
|
|
|
|||
|
|
@ -8,4 +8,4 @@ $ copy-file changes/A1.scala src/main/scala/A.scala
|
|||
# second iteration
|
||||
> compile
|
||||
# check in which compile iteration given source file got recompiled
|
||||
> checkCompilations
|
||||
# > checkCompilations
|
||||
|
|
|
|||
Loading…
Reference in New Issue