Skip to content

Commit

Permalink
Include value of nameHashing flag in CompileSetup.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
gkossakowski authored and eed3si9n committed Mar 21, 2014
1 parent 55c7379 commit 32da54b
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 18 deletions.
12 changes: 7 additions & 5 deletions compile/inc/src/main/scala/sbt/CompileSetup.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand Down
4 changes: 2 additions & 2 deletions compile/persist/src/main/scala/sbt/inc/AnalysisFormats.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
27 changes: 17 additions & 10 deletions compile/persist/src/main/scala/sbt/inc/TextAnalysisFormat.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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"
Expand All @@ -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 {
Expand All @@ -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)
}
}

Expand Down

0 comments on commit 32da54b

Please sign in to comment.