From c7f435026ff8591085f427b8ee4d192e1f10f783 Mon Sep 17 00:00:00 2001 From: Grzegorz Kossakowski Date: Mon, 17 Feb 2014 17:00:19 +0100 Subject: [PATCH] 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. --- .../inc/src/main/scala/sbt/CompileSetup.scala | 12 +++++---- .../sbt/compiler/AggressiveCompile.scala | 9 ++++++- .../main/scala/sbt/inc/AnalysisFormats.scala | 4 +-- .../scala/sbt/inc/TextAnalysisFormat.scala | 27 ++++++++++++------- 4 files changed, 34 insertions(+), 18 deletions(-) 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) } }