mirror of https://github.com/sbt/sbt.git
Merge pull request #4908 from eatkins/incremental-consistency
Override zinc compile analysis for source changes
This commit is contained in:
commit
20f46370ca
|
|
@ -410,6 +410,7 @@ object Defaults extends BuildCommon {
|
|||
},
|
||||
unmanagedSources := (unmanagedSources / inputFileStamps).value.map(_._1.toFile),
|
||||
managedSourceDirectories := Seq(sourceManaged.value),
|
||||
managedSources / outputFileStamper := sbt.nio.FileStamper.Hash,
|
||||
managedSources := {
|
||||
val stamper = inputFileStamper.value
|
||||
val cache = managedFileStampCache.value
|
||||
|
|
@ -603,7 +604,18 @@ object Defaults extends BuildCommon {
|
|||
else ""
|
||||
s"inc_compile$extra.zip"
|
||||
},
|
||||
incOptions := { incOptions.value.withExternalHooks(ExternalHooks.default.value) },
|
||||
externalHooks := {
|
||||
val current =
|
||||
(unmanagedSources / inputFileStamps).value ++ (managedSources / outputFileStamps).value
|
||||
val previous = (externalHooks / inputFileStamps).previous
|
||||
ExternalHooks.default.value(previous.flatMap(sbt.nio.Settings.changedFiles(_, current)))
|
||||
},
|
||||
externalHooks / inputFileStamps := {
|
||||
compile.value // ensures the inputFileStamps previous value is only set if compile succeeds.
|
||||
(unmanagedSources / inputFileStamps).value ++ (managedSources / outputFileStamps).value
|
||||
},
|
||||
externalHooks / inputFileStamps := (externalHooks / inputFileStamps).triggeredBy(compile).value,
|
||||
incOptions := { incOptions.value.withExternalHooks(externalHooks.value) },
|
||||
compileIncSetup := compileIncSetupTask.value,
|
||||
console := consoleTask.value,
|
||||
collectAnalyses := Definition.collectAnalysesTask.map(_ => ()).value,
|
||||
|
|
|
|||
|
|
@ -212,6 +212,7 @@ object Keys {
|
|||
val copyResources = taskKey[Seq[(File, File)]]("Copies resources to the output directory.").withRank(AMinusTask)
|
||||
val aggregate = settingKey[Boolean]("Configures task aggregation.").withRank(BMinusSetting)
|
||||
val sourcePositionMappers = taskKey[Seq[xsbti.Position => Option[xsbti.Position]]]("Maps positions in generated source files to the original source it was generated from").withRank(DTask)
|
||||
private[sbt] val externalHooks = taskKey[ExternalHooks]("The external hooks used by zinc.")
|
||||
|
||||
// package keys
|
||||
val packageBin = taskKey[File]("Produces a main artifact, such as a binary jar.").withRank(ATask)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
package sbt.internal
|
||||
|
||||
import java.nio.file.Paths
|
||||
import java.nio.file.{ Path, Paths }
|
||||
import java.util.Optional
|
||||
|
||||
import sbt.Def
|
||||
|
|
@ -16,18 +16,17 @@ import sbt.internal.inc.ExternalLookup
|
|||
import sbt.internal.inc.Stamp.equivStamp.equiv
|
||||
import sbt.io.syntax._
|
||||
import sbt.nio.Keys._
|
||||
import sbt.nio.file.RecursiveGlob
|
||||
import sbt.nio.file.syntax._
|
||||
import sbt.nio.file.{ ChangedFiles, RecursiveGlob }
|
||||
import sbt.nio.{ FileStamp, FileStamper }
|
||||
import xsbti.compile._
|
||||
import xsbti.compile.analysis.Stamp
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
import scala.collection.mutable
|
||||
|
||||
private[sbt] object ExternalHooks {
|
||||
private val javaHome = Option(System.getProperty("java.home")).map(Paths.get(_))
|
||||
def default: Def.Initialize[sbt.Task[ExternalHooks]] = Def.task {
|
||||
def default: Def.Initialize[sbt.Task[Option[ChangedFiles] => ExternalHooks]] = Def.task {
|
||||
val unmanagedCache = unmanagedFileStampCache.value
|
||||
val managedCache = managedFileStampCache.value
|
||||
val cp = dependencyClasspath.value.map(_.data)
|
||||
|
|
@ -39,9 +38,11 @@ private[sbt] object ExternalHooks {
|
|||
fileTreeView.value.list(classGlob).foreach {
|
||||
case (path, _) => managedCache.update(path, FileStamper.LastModified)
|
||||
}
|
||||
apply((compileOptions in compile).value, unmanagedCache, managedCache)
|
||||
val options = (compileOptions in compile).value
|
||||
apply(_, options, unmanagedCache, managedCache)
|
||||
}
|
||||
private def apply(
|
||||
changedFiles: Option[ChangedFiles],
|
||||
options: CompileOptions,
|
||||
unmanagedCache: FileStamp.Cache,
|
||||
managedCache: FileStamp.Cache
|
||||
|
|
@ -53,22 +54,34 @@ private[sbt] object ExternalHooks {
|
|||
val getRemoved: java.util.Set[File] = new java.util.HashSet[File]
|
||||
val getChanged: java.util.Set[File] = new java.util.HashSet[File]
|
||||
val getUnmodified: java.util.Set[File] = new java.util.HashSet[File]
|
||||
private def add(p: Path, sets: java.util.Set[File]*): Unit = {
|
||||
sets.foreach(add(p.toFile, _))
|
||||
}
|
||||
private def add(f: File, set: java.util.Set[File]): Unit = { set.add(f); () }
|
||||
val allChanges = new java.util.HashSet[File]
|
||||
changedFiles foreach {
|
||||
case ChangedFiles(c, d, u) =>
|
||||
c.foreach(add(_, getAdded, allChanges))
|
||||
d.foreach(add(_, getRemoved, allChanges))
|
||||
u.foreach(add(_, getChanged, allChanges))
|
||||
}
|
||||
override def isEmpty: java.lang.Boolean =
|
||||
getAdded.isEmpty && getRemoved.isEmpty && getChanged.isEmpty
|
||||
val prevSources: mutable.Map[File, Stamp] =
|
||||
previousAnalysis.readStamps().getAllSourceStamps.asScala
|
||||
prevSources.foreach {
|
||||
case (file: File, s: Stamp) =>
|
||||
private val prevSources = previousAnalysis.readStamps().getAllSourceStamps
|
||||
prevSources.forEach { (file: File, s: Stamp) =>
|
||||
if (!allChanges.contains(file)) {
|
||||
val path = file.toPath
|
||||
unmanagedCache
|
||||
.get(path)
|
||||
.orElse(managedCache.getOrElseUpdate(file.toPath, FileStamper.Hash)) match {
|
||||
case None => getRemoved.add(file)
|
||||
case None => add(file, getRemoved)
|
||||
case Some(stamp) =>
|
||||
if (equiv(stamp.stamp, s)) getUnmodified.add(file) else getChanged.add(file)
|
||||
if (equiv(stamp.stamp, s)) add(file, getUnmodified)
|
||||
else add(file, getChanged)
|
||||
}
|
||||
}
|
||||
}
|
||||
options.sources.foreach(file => if (!prevSources.contains(file)) getAdded.add(file))
|
||||
options.sources.foreach(file => if (!prevSources.containsKey(file)) getAdded.add(file))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -251,36 +251,38 @@ private[sbt] object Settings {
|
|||
): Def.Setting[_] =
|
||||
addTaskDefinition(changeKey in scopedKey.scope := {
|
||||
val current = (stampKey in scopedKey.scope).value
|
||||
(stampKey in scopedKey.scope).previous match {
|
||||
case Some(previous) =>
|
||||
val createdBuilder = new VectorBuilder[Path]
|
||||
val deletedBuilder = new VectorBuilder[Path]
|
||||
val updatedBuilder = new VectorBuilder[Path]
|
||||
val currentMap = current.toMap
|
||||
val prevMap = previous.toMap
|
||||
current.foreach {
|
||||
case (path, currentStamp) =>
|
||||
prevMap.get(path) match {
|
||||
case Some(oldStamp) => if (oldStamp != currentStamp) updatedBuilder += path
|
||||
case None => createdBuilder += path
|
||||
}
|
||||
}
|
||||
previous.foreach {
|
||||
case (path, _) =>
|
||||
if (currentMap.get(path).isEmpty) deletedBuilder += path
|
||||
}
|
||||
val created = createdBuilder.result()
|
||||
val deleted = deletedBuilder.result()
|
||||
val updated = updatedBuilder.result()
|
||||
if (created.isEmpty && deleted.isEmpty && updated.isEmpty) {
|
||||
None
|
||||
} else {
|
||||
val cf = ChangedFiles(created = created, deleted = deleted, updated = updated)
|
||||
Some(cf)
|
||||
}
|
||||
case None => None
|
||||
}
|
||||
(stampKey in scopedKey.scope).previous.flatMap(changedFiles(_, current))
|
||||
})
|
||||
private[sbt] def changedFiles(
|
||||
previous: Seq[(Path, FileStamp)],
|
||||
current: Seq[(Path, FileStamp)]
|
||||
): Option[ChangedFiles] = {
|
||||
val createdBuilder = new VectorBuilder[Path]
|
||||
val deletedBuilder = new VectorBuilder[Path]
|
||||
val updatedBuilder = new VectorBuilder[Path]
|
||||
val currentMap = current.toMap
|
||||
val prevMap = previous.toMap
|
||||
current.foreach {
|
||||
case (path, currentStamp) =>
|
||||
prevMap.get(path) match {
|
||||
case Some(oldStamp) => if (oldStamp != currentStamp) updatedBuilder += path
|
||||
case None => createdBuilder += path
|
||||
}
|
||||
}
|
||||
previous.foreach {
|
||||
case (path, _) =>
|
||||
if (currentMap.get(path).isEmpty) deletedBuilder += path
|
||||
}
|
||||
val created = createdBuilder.result()
|
||||
val deleted = deletedBuilder.result()
|
||||
val updated = updatedBuilder.result()
|
||||
if (created.isEmpty && deleted.isEmpty && updated.isEmpty) {
|
||||
None
|
||||
} else {
|
||||
val cf = ChangedFiles(created = created, deleted = deleted, updated = updated)
|
||||
Some(cf)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides an automatically generated clean method for a task that provides fileOutputs.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
val generateSourceFile = taskKey[Unit]("generate source file")
|
||||
generateSourceFile := {
|
||||
val testDir = ((Test / scalaSource).value.toPath / "Foo.scala").toString
|
||||
val content = s"object Foo { val x = 2 }"
|
||||
val src =
|
||||
s"""
|
||||
|import scala.language.experimental.macros
|
||||
|import scala.reflect.macros.blackbox
|
||||
|import java.nio.file.{ Files, Paths }
|
||||
|
|
||||
|object Generate {
|
||||
| def gen: Unit = macro genImpl
|
||||
| def genImpl(c: blackbox.Context): c.Expr[Unit] = {
|
||||
| Files.write(Paths.get("${testDir.replace("\\", "\\\\")}"), "$content".getBytes)
|
||||
| c.universe.reify(())
|
||||
| }
|
||||
|}
|
||||
|""".stripMargin
|
||||
IO.write((Compile / scalaSource).value / "Generate.scala", src)
|
||||
}
|
||||
|
||||
libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value
|
||||
libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.5" % "test"
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
object Foo {
|
||||
def x = 1
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
import org.scalatest.FlatSpec
|
||||
|
||||
class FooTest extends FlatSpec {
|
||||
Generate.gen
|
||||
it should "work" in {
|
||||
assert(Foo.x == 2)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
> generateSourceFile
|
||||
|
||||
-> test
|
||||
|
||||
> test
|
||||
|
|
@ -14,6 +14,7 @@ recordPreviousIterations := {
|
|||
log.info("No previous analysis detected")
|
||||
0
|
||||
case Some(a: Analysis) => a.compilations.allCompilations.size
|
||||
case Some(_) => -1 // should be unreachable but causes warnings
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue