mirror of https://github.com/sbt/sbt.git
Include value of `nameHashing` flag in `CompileSetup`.
The CompileSetup class is being used to detect changes to arguments of incremental compiler that affect result of compilation and trigger recompilation. Examples of such arguments include, the target (output) directory, Scala compiler options, Scala compiler version, etc. By adding `nameHashing` to CompileSetup we have a chance to handle change to that flag smoothly by throwing away old Analysis object and starting with an empty one. That's implemented in AggressiveComile by extending the logic that was responsible for detection of changes to CompileSetup values. Thanks to this change we fix #1081. Analysis formats has been updated to support persisting of newly added value in CompileSetup. We used to not store the value of `nameHashing` flag in persisted Analysis file and infer it from contents of relations but that leads to issue #1071 when empty relations are involved. Given the fact that CompileSetup stores `nameHashing` value now, we can just use it when reading relations and fix #1071. This requires reading/writing compile setup before reading relations. I decided to make that change even if there's a comment saying that reading/writing relations first was done intentionally.
This commit is contained in:
parent
9428bb3344
commit
c7f435026f
|
|
@ -11,7 +11,8 @@ package sbt
|
|||
// because complexity(Equiv[Seq[String]]) > complexity(Equiv[CompileSetup])
|
||||
// (6 > 4)
|
||||
final class CompileOptions(val options: Seq[String], val javacOptions: Seq[String])
|
||||
final class CompileSetup(val output: APIOutput, val options: CompileOptions, val compilerVersion: String, val order: CompileOrder)
|
||||
final class CompileSetup(val output: APIOutput, val options: CompileOptions, val compilerVersion: String,
|
||||
val order: CompileOrder, val nameHashing: Boolean)
|
||||
|
||||
object CompileSetup
|
||||
{
|
||||
|
|
@ -21,7 +22,8 @@ object CompileSetup
|
|||
equivOutput.equiv(a.output, b.output) &&
|
||||
equivOpts.equiv(a.options, b.options) &&
|
||||
equivComp.equiv(a.compilerVersion, b.compilerVersion) &&
|
||||
a.order == b.order // equivOrder.equiv(a.order, b.order)
|
||||
a.order == b.order && // equivOrder.equiv(a.order, b.order)
|
||||
a.nameHashing == b.nameHashing
|
||||
}
|
||||
implicit val equivFile: Equiv[File] = new Equiv[File] {
|
||||
def equiv(a: File, b: File) = a.getAbsoluteFile == b.getAbsoluteFile
|
||||
|
|
@ -32,7 +34,7 @@ object CompileSetup
|
|||
case (m1: MultipleOutput, m2: MultipleOutput) =>
|
||||
(m1.outputGroups.length == m2.outputGroups.length) &&
|
||||
(m1.outputGroups.sorted zip m2.outputGroups.sorted forall {
|
||||
case (a,b) =>
|
||||
case (a,b) =>
|
||||
equivFile.equiv(a.sourceDirectory, b.sourceDirectory) && equivFile.equiv(a.outputDirectory, b.outputDirectory)
|
||||
})
|
||||
case (s1: SingleOutput, s2: SingleOutput) => equivFile.equiv(s1.outputDirectory, s2.outputDirectory)
|
||||
|
|
@ -42,12 +44,12 @@ object CompileSetup
|
|||
implicit val equivOpts: Equiv[CompileOptions] = new Equiv[CompileOptions] {
|
||||
def equiv(a: CompileOptions, b: CompileOptions) =
|
||||
(a.options sameElements b.options) &&
|
||||
(a.javacOptions sameElements b.javacOptions)
|
||||
(a.javacOptions sameElements b.javacOptions)
|
||||
}
|
||||
implicit val equivCompilerVersion: Equiv[String] = new Equiv[String] {
|
||||
def equiv(a: String, b: String) = a == b
|
||||
}
|
||||
|
||||
|
||||
implicit val equivOrder: Equiv[CompileOrder] = new Equiv[CompileOrder] {
|
||||
def equiv(a: CompileOrder, b: CompileOrder) = a == b
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,8 @@ class AggressiveCompile(cacheFile: File)
|
|||
skip: Boolean = false,
|
||||
incrementalCompilerOptions: IncOptions)(implicit log: Logger): Analysis =
|
||||
{
|
||||
val setup = new CompileSetup(output, new CompileOptions(options, javacOptions), compiler.scalaInstance.actualVersion, compileOrder)
|
||||
val setup = new CompileSetup(output, new CompileOptions(options, javacOptions),
|
||||
compiler.scalaInstance.actualVersion, compileOrder, incrementalCompilerOptions.nameHashing)
|
||||
compile1(sources, classpath, setup, progress, store, analysisMap, definesClass,
|
||||
compiler, javac, reporter, skip, cache, incrementalCompilerOptions)
|
||||
}
|
||||
|
|
@ -144,6 +145,12 @@ class AggressiveCompile(cacheFile: File)
|
|||
|
||||
val sourcesSet = sources.toSet
|
||||
val analysis = previousSetup match {
|
||||
case Some(previous) if previous.nameHashing != currentSetup.nameHashing =>
|
||||
// if the value of `nameHashing` flag has changed we have to throw away
|
||||
// previous Analysis completely and start with empty Analysis object
|
||||
// that supports the particular value of the `nameHashing` flag.
|
||||
// Otherwise we'll be getting UnsupportedOperationExceptions
|
||||
Analysis.empty(currentSetup.nameHashing)
|
||||
case Some(previous) if equiv.equiv(previous, currentSetup) => previousAnalysis
|
||||
case _ => Incremental.prune(sourcesSet, previousAnalysis)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,8 +73,8 @@ object AnalysisFormats
|
|||
wrap[Severity, Byte]( _.ordinal.toByte, b => Severity.values.apply(b.toInt) )
|
||||
|
||||
|
||||
implicit def setupFormat(implicit outputF: Format[APIOutput], optionF: Format[CompileOptions], compilerVersion: Format[String], orderF: Format[CompileOrder]): Format[CompileSetup] =
|
||||
asProduct4[CompileSetup, APIOutput, CompileOptions, String, CompileOrder]( (a,b,c,d) => new CompileSetup(a,b,c,d) )(s => (s.output, s.options, s.compilerVersion, s.order))(outputF, optionF, compilerVersion, orderF)
|
||||
implicit def setupFormat(implicit outputF: Format[APIOutput], optionF: Format[CompileOptions], compilerVersion: Format[String], orderF: Format[CompileOrder], nameHashingF: Format[Boolean]): Format[CompileSetup] =
|
||||
asProduct5[CompileSetup, APIOutput, CompileOptions, String, CompileOrder, Boolean]( (a,b,c,d,e) => new CompileSetup(a,b,c,d,e) )(s => (s.output, s.options, s.compilerVersion, s.order, s.nameHashing))(outputF, optionF, compilerVersion, orderF, nameHashingF)
|
||||
|
||||
implicit val outputGroupFormat: Format[OutputGroup] =
|
||||
asProduct2((a: File,b: File) => new OutputGroup{def sourceDirectory = a; def outputDirectory = b}) { out => (out.sourceDirectory, out.outputDirectory) }(fileFormat, fileFormat)
|
||||
|
|
|
|||
|
|
@ -57,31 +57,33 @@ object TextAnalysisFormat {
|
|||
|
||||
def write(out: Writer, analysis: Analysis, setup: CompileSetup) {
|
||||
VersionF.write(out)
|
||||
// We start with relations because that's the part of greatest interest to external readers,
|
||||
// We start with writing compile setup which contains value of the `nameHashing`
|
||||
// flag that is needed to properly deserialize relations
|
||||
FormatTimer.time("write setup") { CompileSetupF.write(out, setup) }
|
||||
// Next we write relations because that's the part of greatest interest to external readers,
|
||||
// who can abort reading early once they're read them.
|
||||
FormatTimer.time("write relations") { RelationsF.write(out, analysis.relations) }
|
||||
FormatTimer.time("write stamps") { StampsF.write(out, analysis.stamps) }
|
||||
FormatTimer.time("write apis") { APIsF.write(out, analysis.apis) }
|
||||
FormatTimer.time("write sourceinfos") { SourceInfosF.write(out, analysis.infos) }
|
||||
FormatTimer.time("write compilations") { CompilationsF.write(out, analysis.compilations) }
|
||||
FormatTimer.time("write setup") { CompileSetupF.write(out, setup) }
|
||||
out.flush()
|
||||
}
|
||||
|
||||
def read(in: BufferedReader): (Analysis, CompileSetup) = {
|
||||
VersionF.read(in)
|
||||
val relations = FormatTimer.time("read relations") { RelationsF.read(in) }
|
||||
val setup = FormatTimer.time("read setup") { CompileSetupF.read(in) }
|
||||
val relations = FormatTimer.time("read relations") { RelationsF.read(in, setup.nameHashing) }
|
||||
val stamps = FormatTimer.time("read stamps") { StampsF.read(in) }
|
||||
val apis = FormatTimer.time("read apis") { APIsF.read(in) }
|
||||
val infos = FormatTimer.time("read sourceinfos") { SourceInfosF.read(in) }
|
||||
val compilations = FormatTimer.time("read compilations") { CompilationsF.read(in) }
|
||||
val setup = FormatTimer.time("read setup") { CompileSetupF.read(in) }
|
||||
|
||||
(Analysis.Empty.copy(stamps, apis, relations, infos, compilations), setup)
|
||||
}
|
||||
|
||||
private[this] object VersionF {
|
||||
val currentVersion = "4"
|
||||
val currentVersion = "5"
|
||||
|
||||
def write(out: Writer) {
|
||||
out.write("format version: %s\n".format(currentVersion))
|
||||
|
|
@ -165,7 +167,7 @@ object TextAnalysisFormat {
|
|||
writeRelation(Headers.usedNames, names)
|
||||
}
|
||||
|
||||
def read(in: BufferedReader): Relations = {
|
||||
def read(in: BufferedReader, nameHashing: Boolean): Relations = {
|
||||
def readRelation[T](expectedHeader: String, s2t: String => T): Relation[File, T] = {
|
||||
val items = readPairs(in)(expectedHeader, new File(_), s2t).toIterator
|
||||
// Reconstruct the forward map. This is more efficient than Relation.empty ++ items.
|
||||
|
|
@ -216,9 +218,10 @@ object TextAnalysisFormat {
|
|||
}
|
||||
// we don't check for emptiness of publicInherited/inheritance relations because
|
||||
// we assume that invariant that says they are subsets of direct/memberRef holds
|
||||
assert((directSrcDeps == emptySource) || (memberRefSrcDeps == emptySourceDependencies),
|
||||
"One mechanism is supported for tracking source dependencies at the time")
|
||||
val nameHashing = memberRefSrcDeps != emptySourceDependencies
|
||||
assert(nameHashing || (memberRefSrcDeps == emptySourceDependencies),
|
||||
"When name hashing is disabled the `memberRef` relation should be empty.")
|
||||
assert(!nameHashing || (directSrcDeps == emptySource),
|
||||
"When name hashing is enabled the `direct` relation should be empty.")
|
||||
val classes = readStringRelation(Headers.classes)
|
||||
val names = readStringRelation(Headers.usedNames)
|
||||
|
||||
|
|
@ -322,6 +325,7 @@ object TextAnalysisFormat {
|
|||
val javacOptions = "javac options"
|
||||
val compilerVersion = "compiler version"
|
||||
val compileOrder = "compile order"
|
||||
val nameHashing = "name hashing"
|
||||
}
|
||||
|
||||
private[this] val singleOutputMode = "single"
|
||||
|
|
@ -340,16 +344,19 @@ object TextAnalysisFormat {
|
|||
writeSeq(out)(Headers.javacOptions, setup.options.javacOptions, identity[String])
|
||||
writeSeq(out)(Headers.compilerVersion, setup.compilerVersion :: Nil, identity[String])
|
||||
writeSeq(out)(Headers.compileOrder, setup.order.name :: Nil, identity[String])
|
||||
writeSeq(out)(Headers.nameHashing, setup.nameHashing :: Nil, (b: Boolean) => b.toString)
|
||||
}
|
||||
|
||||
def read(in: BufferedReader): CompileSetup = {
|
||||
def s2f(s: String) = new File(s)
|
||||
def s2b(s: String): Boolean = s.toBoolean
|
||||
val outputDirMode = readSeq(in)(Headers.outputMode, identity[String]).headOption
|
||||
val outputAsMap = readMap(in)(Headers.outputDir, s2f, s2f)
|
||||
val compileOptions = readSeq(in)(Headers.compileOptions, identity[String])
|
||||
val javacOptions = readSeq(in)(Headers.javacOptions, identity[String])
|
||||
val compilerVersion = readSeq(in)(Headers.compilerVersion, identity[String]).head
|
||||
val compileOrder = readSeq(in)(Headers.compileOrder, identity[String]).head
|
||||
val nameHashing = readSeq(in)(Headers.nameHashing, s2b).head
|
||||
|
||||
val output = outputDirMode match {
|
||||
case Some(s) => s match {
|
||||
|
|
@ -370,7 +377,7 @@ object TextAnalysisFormat {
|
|||
}
|
||||
|
||||
new CompileSetup(output, new CompileOptions(compileOptions, javacOptions), compilerVersion,
|
||||
xsbti.compile.CompileOrder.valueOf(compileOrder))
|
||||
xsbti.compile.CompileOrder.valueOf(compileOrder), nameHashing)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue