diff --git a/compile/inc/src/main/scala/sbt/inc/Relations.scala b/compile/inc/src/main/scala/sbt/inc/Relations.scala index f244c431f..6a60ca418 100644 --- a/compile/inc/src/main/scala/sbt/inc/Relations.scala +++ b/compile/inc/src/main/scala/sbt/inc/Relations.scala @@ -175,9 +175,67 @@ trait Relations { * Relation between source files and _unqualified_ term and type names used in given source file. */ private[inc] def names: Relation[File, String] + + /** + * Lists of all the pairs (header, relation) that sbt knows of. + * Used by TextAnalysisFormat to persist relations. + * This cannot be stored as a Map because the order is important. + */ + private[inc] def allRelations: List[(String, Relation[File, _])] } object Relations { + + /** + * Represents all the relations that sbt knows of along with a way to recreate each + * of their elements from their string representation. + */ + private[inc] val existingRelations = { + val string2File: String => File = new File(_) + List( + ("products", string2File), + ("binary dependencies", string2File), + ("direct source dependencies", string2File), + ("direct external dependencies", identity[String] _), + ("public inherited source dependencies", string2File), + ("public inherited external dependencies", identity[String] _), + ("member reference internal dependencies", string2File), + ("member reference external dependencies", identity[String] _), + ("inheritance internal dependencies", string2File), + ("inheritance external dependencies", identity[String] _), + ("class names", identity[String] _), + ("used names", identity[String] _)) + } + /** + * Reconstructs a Relations from a list of Relation + * The order in which the relations are read matters and is defined by `existingRelations`. + */ + def construct(nameHashing: Boolean, relations: List[Relation[_, _]]) = + relations match { + case p :: bin :: di :: de :: pii :: pie :: mri :: mre :: ii :: ie :: cn :: un :: Nil => + val srcProd = p.asInstanceOf[Relation[File, File]] + val binaryDep = bin.asInstanceOf[Relation[File, File]] + val directSrcDeps = makeSource(di.asInstanceOf[Relation[File, File]], de.asInstanceOf[Relation[File, String]]) + val publicInheritedSrcDeps = makeSource(pii.asInstanceOf[Relation[File, File]], pie.asInstanceOf[Relation[File, String]]) + val memberRefSrcDeps = makeSourceDependencies(mri.asInstanceOf[Relation[File, File]], mre.asInstanceOf[Relation[File, String]]) + val inheritanceSrcDeps = makeSourceDependencies(ii.asInstanceOf[Relation[File, File]], ie.asInstanceOf[Relation[File, String]]) + val classes = cn.asInstanceOf[Relation[File, String]] + val names = un.asInstanceOf[Relation[File, String]] + + // 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(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.") + + if (nameHashing) + Relations.make(srcProd, binaryDep, memberRefSrcDeps, inheritanceSrcDeps, classes, names) + else { + assert(names.all.isEmpty, s"When `nameHashing` is disabled `names` relation should be empty: $names") + Relations.make(srcProd, binaryDep, directSrcDeps, publicInheritedSrcDeps, classes) + } + case _ => throw new java.io.IOException(s"Expected to read ${existingRelations.length} relations but read ${relations.length}.") + } + /** Tracks internal and external source dependencies for a specific dependency type, such as direct or inherited.*/ final class Source private[sbt] (val internal: Relation[File, File], val external: Relation[File, String]) { def addInternal(source: File, dependsOn: Iterable[File]): Source = new Source(internal + (source, dependsOn), external) @@ -403,6 +461,23 @@ private class MRelationsDefaultImpl(srcProd: Relation[File, File], binaryDep: Re case _ => false } + def allRelations = { + val rels = List( + srcProd, + binaryDep, + direct.internal, + direct.external, + publicInherited.internal, + publicInherited.external, + Relations.emptySourceDependencies.internal, // Default implementation doesn't provide memberRef source deps + Relations.emptySourceDependencies.external, // Default implementation doesn't provide memberRef source deps + Relations.emptySourceDependencies.internal, // Default implementation doesn't provide inheritance source deps + Relations.emptySourceDependencies.external, // Default implementation doesn't provide inheritance source deps + classes, + Relation.empty[File, String]) // Default implementation doesn't provide used names relation + Relations.existingRelations map (_._1) zip rels + } + override def hashCode = (srcProd :: binaryDep :: direct :: publicInherited :: classes :: Nil).hashCode override def toString = ( @@ -490,6 +565,23 @@ private class MRelationsNameHashing(srcProd: Relation[File, File], binaryDep: Re case _ => false } + def allRelations = { + val rels = List( + srcProd, + binaryDep, + Relations.emptySource.internal, // NameHashing doesn't provide direct dependencies + Relations.emptySource.external, // NameHashing doesn't provide direct dependencies + Relations.emptySource.internal, // NameHashing doesn't provide public inherited dependencies + Relations.emptySource.external, // NameHashing doesn't provide public inherited dependencies + memberRef.internal, + memberRef.external, + inheritance.internal, + inheritance.external, + classes, + names) + Relations.existingRelations map (_._1) zip rels + } + override def hashCode = (srcProd :: binaryDep :: memberRef :: inheritance :: classes :: Nil).hashCode override def toString = ( diff --git a/compile/persist/src/main/scala/sbt/inc/TextAnalysisFormat.scala b/compile/persist/src/main/scala/sbt/inc/TextAnalysisFormat.scala index 7f3fd6f3b..3749298ea 100644 --- a/compile/persist/src/main/scala/sbt/inc/TextAnalysisFormat.scala +++ b/compile/persist/src/main/scala/sbt/inc/TextAnalysisFormat.scala @@ -122,7 +122,13 @@ object TextAnalysisFormat { } def write(out: Writer, relations: Relations) { - def writeRelation[T](header: String, rel: Relation[File, T])(implicit ord: Ordering[T]) { + // This ordering is used to persist all values in order. Since all values will be + // persisted using their string representation, it makes sense to sort them using + // their string representation. + val toStringOrd = new Ordering[Any] { + def compare(a: Any, b: Any) = a.toString compare b.toString + } + def writeRelation[T](header: String, rel: Relation[File, T]) { writeHeader(out, header) writeSize(out, rel.size) // We sort for ease of debugging and for more efficient reconstruction when reading. @@ -131,38 +137,15 @@ object TextAnalysisFormat { rel.forwardMap.toSeq.sortBy(_._1).foreach { case (k, vs) => val kStr = k.toString - vs.toSeq.sorted foreach { v => + vs.toSeq.sorted(toStringOrd) foreach { v => out.write(kStr); out.write(" -> "); out.write(v.toString); out.write("\n") } } } - val nameHashing = relations.nameHashing - writeRelation(Headers.srcProd, relations.srcProd) - writeRelation(Headers.binaryDep, relations.binaryDep) - - val direct = if (nameHashing) Relations.emptySource else relations.direct - val publicInherited = if (nameHashing) - Relations.emptySource else relations.publicInherited - - val memberRef = if (nameHashing) - relations.memberRef else Relations.emptySourceDependencies - val inheritance = if (nameHashing) - relations.inheritance else Relations.emptySourceDependencies - val names = if (nameHashing) relations.names else Relation.empty[File, String] - - writeRelation(Headers.directSrcDep, direct.internal) - writeRelation(Headers.directExternalDep, direct.external) - writeRelation(Headers.internalSrcDepPI, publicInherited.internal) - writeRelation(Headers.externalDepPI, publicInherited.external) - - writeRelation(Headers.memberRefInternalDep, memberRef.internal) - writeRelation(Headers.memberRefExternalDep, memberRef.external) - writeRelation(Headers.inheritanceInternalDep, inheritance.internal) - writeRelation(Headers.inheritanceExternalDep, inheritance.external) - - writeRelation(Headers.classes, relations.classes) - writeRelation(Headers.usedNames, names) + relations.allRelations.foreach { + case (header, rel) => writeRelation(header, rel) + } } def read(in: BufferedReader, nameHashing: Boolean): Relations = { @@ -186,56 +169,9 @@ object TextAnalysisFormat { Relation.reconstruct(forward.toMap) } - def readFileRelation(expectedHeader: String) = readRelation(expectedHeader, { new File(_) }) - def readStringRelation(expectedHeader: String) = readRelation(expectedHeader, identity[String]) + val relations = Relations.existingRelations map { case (header, fun) => readRelation(header, fun) } - val srcProd = readFileRelation(Headers.srcProd) - val binaryDep = readFileRelation(Headers.binaryDep) - - import sbt.inc.Relations.{ - Source, - SourceDependencies, - makeSourceDependencies, - emptySource, - makeSource, - emptySourceDependencies - } - val directSrcDeps: Source = { - val internalSrcDep = readFileRelation(Headers.directSrcDep) - val externalDep = readStringRelation(Headers.directExternalDep) - makeSource(internalSrcDep, externalDep) - } - val publicInheritedSrcDeps: Source = { - val internalSrcDepPI = readFileRelation(Headers.internalSrcDepPI) - val externalDepPI = readStringRelation(Headers.externalDepPI) - makeSource(internalSrcDepPI, externalDepPI) - } - val memberRefSrcDeps: SourceDependencies = { - val internalMemberRefDep = readFileRelation(Headers.memberRefInternalDep) - val externalMemberRefDep = readStringRelation(Headers.memberRefExternalDep) - makeSourceDependencies(internalMemberRefDep, externalMemberRefDep) - } - val inheritanceSrcDeps: SourceDependencies = { - val internalInheritanceDep = readFileRelation(Headers.inheritanceInternalDep) - val externalInheritanceDep = readStringRelation(Headers.inheritanceExternalDep) - makeSourceDependencies(internalInheritanceDep, externalInheritanceDep) - } - // 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(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) - - if (nameHashing) - Relations.make(srcProd, binaryDep, memberRefSrcDeps, inheritanceSrcDeps, classes, names) - else { - assert(names.all.isEmpty, "When `nameHashing` is disabled `names` relation " + - s"should be empty: $names") - Relations.make(srcProd, binaryDep, directSrcDeps, publicInheritedSrcDeps, classes) - } + Relations.construct(nameHashing, relations) } }