diff --git a/compile/inc/src/main/scala/sbt/CompileSetup.scala b/compile/inc/src/main/scala/sbt/CompileSetup.scala index 59e9e2975..e45f85e58 100644 --- a/compile/inc/src/main/scala/sbt/CompileSetup.scala +++ b/compile/inc/src/main/scala/sbt/CompileSetup.scala @@ -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 } diff --git a/compile/integration/src/main/scala/sbt/compiler/AggressiveCompile.scala b/compile/integration/src/main/scala/sbt/compiler/AggressiveCompile.scala index fec36db56..2c711d14f 100644 --- a/compile/integration/src/main/scala/sbt/compiler/AggressiveCompile.scala +++ b/compile/integration/src/main/scala/sbt/compiler/AggressiveCompile.scala @@ -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) } diff --git a/compile/persist/src/main/scala/sbt/inc/AnalysisFormats.scala b/compile/persist/src/main/scala/sbt/inc/AnalysisFormats.scala index 5f2c7b9c6..73b619e0f 100644 --- a/compile/persist/src/main/scala/sbt/inc/AnalysisFormats.scala +++ b/compile/persist/src/main/scala/sbt/inc/AnalysisFormats.scala @@ -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) diff --git a/compile/persist/src/main/scala/sbt/inc/TextAnalysisFormat.scala b/compile/persist/src/main/scala/sbt/inc/TextAnalysisFormat.scala index 3bd28190c..f3e13d23a 100644 --- a/compile/persist/src/main/scala/sbt/inc/TextAnalysisFormat.scala +++ b/compile/persist/src/main/scala/sbt/inc/TextAnalysisFormat.scala @@ -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) } }