Skip to content

Commit

Permalink
Introduce binaryClassName relation.
Browse files Browse the repository at this point in the history
The `binaryClassName` relation maintains mapping between source and binary
class names. This mapping is needed to map binary dependencies back to
source dependencies in case of separate compilation (where we see
dependencies on class files). You can see that mapping being used in
`binaryDependency` method implementation of Analysis callback. Previously,
we would map class file to a source file it was produced from and then
assume that dependency is on any (all) of classes declared in that class.
Introduction of `binaryClassName` lets us map dependency back to source
class name directly and remove that imprecision of dependency tracking.

We maintain mapping between source and binary class names just for
non-local classes. Check this
sbt#1104 (comment) for the
discussion of local and non-local classes.

We also rework tracking of products in Analysis by introducing explicitly
the concept of local and non-local products corresponding to local and
non-local classes. This helps us to clarify for which classes we track
source and binary class names.
  • Loading branch information
gkossakowski committed Jan 30, 2016
1 parent 87fd296 commit b32166b
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 70 deletions.
28 changes: 20 additions & 8 deletions compile/inc/src/main/scala/sbt/inc/Analysis.scala
Expand Up @@ -4,6 +4,7 @@
package sbt
package inc

import sbt.inc.Analysis.{ LocalProduct, NonLocalProduct }
import xsbti.api.Source
import xsbti.DependencyContext._
import java.io.File
Expand Down Expand Up @@ -53,8 +54,8 @@ trait Analysis {
compilations: Compilations = compilations): Analysis

def addSource(src: File, api: Source, stamp: Stamp, info: SourceInfo,
products: Iterable[(File, Stamp)],
classes: Iterable[(String, String)],
nonLocalProducts: Iterable[NonLocalProduct],
localProducts: Iterable[LocalProduct],
internalDeps: Iterable[InternalDependency],
externalDeps: Iterable[ExternalDependency],
binaryDeps: Iterable[(File, String, Stamp)]): Analysis
Expand All @@ -66,6 +67,8 @@ trait Analysis {
}

object Analysis {
case class NonLocalProduct(className: String, binaryClassName: String, classFile: File, classFileStamp: Stamp)
case class LocalProduct(classFile: File, classFileStamp: Stamp)
lazy val Empty: Analysis = new MAnalysis(Stamps.empty, APIs.empty, Relations.empty, SourceInfos.empty, Compilations.empty)
private[sbt] def empty(nameHashing: Boolean): Analysis = new MAnalysis(Stamps.empty, APIs.empty,
Relations.empty(nameHashing = nameHashing), SourceInfos.empty, Compilations.empty)
Expand Down Expand Up @@ -159,18 +162,24 @@ private class MAnalysis(val stamps: Stamps, val apis: APIs, val relations: Relat
new MAnalysis(stamps, apis, relations, infos, compilations)

def addSource(src: File, api: Source, stamp: Stamp, info: SourceInfo,
products: Iterable[(File, Stamp)],
classes: Iterable[(String, String)],
nonLocalProducts: Iterable[NonLocalProduct],
localProducts: Iterable[LocalProduct],
internalDeps: Iterable[InternalDependency],
externalDeps: Iterable[ExternalDependency],
binaryDeps: Iterable[(File, String, Stamp)]): Analysis = {

val newStamps = {
val productStamps = products.foldLeft(stamps.markInternalSource(src, stamp)) {
case (tmpStamps, (toProduct, prodStamp)) => tmpStamps.markProduct(toProduct, prodStamp)
val nonLocalProductStamps = nonLocalProducts.foldLeft(stamps.markInternalSource(src, stamp)) {
case (tmpStamps, nonLocalProduct) =>
tmpStamps.markProduct(nonLocalProduct.classFile, nonLocalProduct.classFileStamp)
}

binaryDeps.foldLeft(productStamps) {
val allProductStamps = localProducts.foldLeft(nonLocalProductStamps) {
case (tmpStamps, localProduct) =>
tmpStamps.markProduct(localProduct.classFile, localProduct.classFileStamp)
}

binaryDeps.foldLeft(allProductStamps) {
case (tmpStamps, (toBinary, className, binStamp)) => tmpStamps.markBinary(toBinary, className, binStamp)
}
}
Expand All @@ -179,7 +188,10 @@ private class MAnalysis(val stamps: Stamps, val apis: APIs, val relations: Relat
case (tmpApis, ExternalDependency(_, toClassName, classApi, _)) => tmpApis.markExternalAPI(toClassName, classApi)
}

val newRelations = relations.addSource(src, products.map(_._1), classes, internalDeps, externalDeps, binaryDeps)
val allProducts = nonLocalProducts.map(_.classFile) ++ localProducts.map(_.classFile)
val classes = nonLocalProducts.map(p => p.className -> p.binaryClassName)

val newRelations = relations.addSource(src, allProducts, classes, internalDeps, externalDeps, binaryDeps)

copy(newStamps, newAPIs, newRelations, infos.add(src, info))
}
Expand Down
56 changes: 29 additions & 27 deletions compile/inc/src/main/scala/sbt/inc/Compile.scala
Expand Up @@ -4,6 +4,7 @@
package sbt
package inc

import sbt.inc.Analysis.{ LocalProduct, NonLocalProduct }
import xsbti.api._
import xsbti.compile.{ DependencyChanges, Output, SingleOutput, MultipleOutput }
import xsbti.{ Position, Problem, Severity }
Expand Down Expand Up @@ -47,12 +48,13 @@ object IncrementalCompile {
options: IncOptions): (Boolean, Analysis) =
{
val current = Stamps.initial(Stamp.lastModified, Stamp.hash, Stamp.lastModified)
val internalMap = (f: File) => previous.relations.produced(f).headOption
val internalBinaryToSourceClassName = (binaryClassName: String) =>
previous.relations.binaryClassName.reverse(binaryClassName).headOption
val internalSourceToClassNamesMap: File => Set[String] = (f: File) => previous.relations.declaredClassNames(f)
val externalAPI = getExternalAPI(entry, forEntry)
try {
Incremental.compile(sources, entry, previous, current, forEntry,
doCompile(compile, internalMap, internalSourceToClassNamesMap, externalAPI, current, output, options),
doCompile(compile, internalBinaryToSourceClassName, internalSourceToClassNamesMap, externalAPI, current, output, options),
log, options)
} catch {
case e: xsbti.CompileCancelled =>
Expand All @@ -62,11 +64,12 @@ object IncrementalCompile {
(false, previous)
}
}
def doCompile(compile: (Set[File], DependencyChanges, xsbti.AnalysisCallback) => Unit, internalMap: File => Option[File],
def doCompile(compile: (Set[File], DependencyChanges, xsbti.AnalysisCallback) => Unit,
internalBinaryToSourceClassName: String => Option[String],
internalSourceToClassNamesMap: File => Set[String],
externalAPI: (File, String) => Option[Source], current: ReadStamps, output: Output, options: IncOptions) =
(srcs: Set[File], changes: DependencyChanges) => {
val callback = new AnalysisCallback(internalMap, internalSourceToClassNamesMap, externalAPI, current, output, options)
val callback = new AnalysisCallback(internalBinaryToSourceClassName, internalSourceToClassNamesMap, externalAPI, current, output, options)
compile(srcs, changes, callback)
callback.get
}
Expand All @@ -83,9 +86,10 @@ object IncrementalCompile {
}
}
}
private final class AnalysisCallback(internalMap: File => Option[File],
private final class AnalysisCallback(internalBinaryToSourceClassName: String => Option[String],
internalSourceToClassNamesMap: File => Set[String],
externalAPI: (File, String) => Option[Source], current: ReadStamps, output: Output, options: IncOptions) extends xsbti.AnalysisCallback {
externalAPI: (File, String) => Option[Source], current: ReadStamps,
output: Output, options: IncOptions) extends xsbti.AnalysisCallback {
val compilation = {
val outputSettings = output match {
case single: SingleOutput => Array(new OutputSetting("/", single.outputDirectory.getAbsolutePath))
Expand All @@ -111,8 +115,8 @@ private final class AnalysisCallback(internalMap: File => Option[File],
private[this] val localClasses = new HashMap[File, Set[File]]
// mapping between src class name and binary (flat) class name for classes generated from src file
private[this] val classNames = new HashMap[File, Set[(String, String)]]
// generated class file to its source file
private[this] val classToSource = new HashMap[File, File]
// generated class file to its source class name
private[this] val classToSource = new HashMap[File, String]
// internal source dependencies
private[this] val intSrcDeps = new HashMap[String, Set[InternalDependency]]
// external source dependencies
Expand Down Expand Up @@ -147,25 +151,17 @@ private final class AnalysisCallback(internalMap: File => Option[File],
add(extSrcDeps, sourceClassName, dependency)
}

private[this] def dependencyOnSourceAsClassNameDepedency(targetFile: File, sourceClassName: String,
context: DependencyContext) = {
val classNames = internalSourceToClassNamesMap(targetFile) ++ nonLocalClasses.getOrElse(targetFile, Set.empty).map(_._2)
classNames foreach { targetClassName =>
classDependency(targetClassName, sourceClassName, context)
}
}

def binaryDependency(classFile: File, onBinaryClassName: String, fromClassName: String, fromSourceFile: File, context: DependencyContext) =
internalMap(classFile) match {
case Some(dependsOn) => // dependsOn is source
internalBinaryToSourceClassName(onBinaryClassName) match {
case Some(dependsOn) => // dependsOn is a source class name
// dependency is a product of a source not included in this compilation
dependencyOnSourceAsClassNameDepedency(dependsOn, fromClassName, context)
classDependency(dependsOn, fromClassName, context)
case None =>
classToSource.get(classFile) match {
case Some(dependsOn) =>
// dependency is a product of a source in this compilation step,
// but not in the same compiler run (as in javac v. scalac)
dependencyOnSourceAsClassNameDepedency(dependsOn, fromClassName, context)
classDependency(dependsOn, fromClassName, context)
case None =>
externalDependency(classFile, onBinaryClassName, fromClassName, fromSourceFile, context)
}
Expand All @@ -185,7 +181,7 @@ private final class AnalysisCallback(internalMap: File => Option[File],
def generatedNonLocalClass(source: File, classFile: File, binaryClassName: String, srcClassName: String) = {
add(nonLocalClasses, source, (classFile, binaryClassName))
add(classNames, source, (srcClassName, binaryClassName))
classToSource.put(classFile, source)
classToSource.put(classFile, srcClassName)
}

def generatedLocalClass(source: File, classFile: File) = {
Expand Down Expand Up @@ -241,18 +237,24 @@ private final class AnalysisCallback(internalMap: File => Option[File],
val s = new xsbti.api.Source(compilation, hash, api._2, api._1.toArray, publicNameHashes(src), hasMacro)
val info = SourceInfos.makeInfo(getOrNil(reporteds, src), getOrNil(unreporteds, src))
val binaries = binaryDeps.getOrElse(src, Nil: Iterable[File])
val nonLocalProds = nonLocalClasses.getOrElse(src, Nil: Iterable[(File, String)]).map(_._1)
val prods = nonLocalProds ++ localClasses.getOrElse(src, Nil: Iterable[File])
val localProds = localClasses.getOrElse(src, Nil: Iterable[File]) map {
classFile => LocalProduct(classFile, current product classFile)
}
val binaryToSrcClassName = (classNames.getOrElse(src, Set.empty) map {
case (srcClassName, binaryClassName) => (binaryClassName, srcClassName)
}).toMap
val nonLocalProds = nonLocalClasses.getOrElse(src, Nil: Iterable[(File, String)]) map {
case (classFile, binaryClassName) =>
val srcClassName = binaryToSrcClassName(binaryClassName)
NonLocalProduct(srcClassName, binaryClassName, classFile, current product classFile)
}

val products = prods.map { prod => (prod, current product prod) }
val classesInSrc = classNames.getOrElse(src, Set.empty).map(_._1)
val internalDeps = classesInSrc.flatMap(cls => intSrcDeps.getOrElse(cls, Set.empty))
val externalDeps = classesInSrc.flatMap(cls => extSrcDeps.getOrElse(cls, Set.empty))
val binDeps = binaries.map(d => (d, binaryClassName(d), current binary d))

// TODO: turn into (src class name, binary class name)
val classes = classNames.getOrElse(src, Iterable.empty)
a.addSource(src, s, stamp, info, products, classes, internalDeps, externalDeps, binDeps)
a.addSource(src, s, stamp, info, nonLocalProds, localProds, internalDeps, externalDeps, binDeps)

}
}

0 comments on commit b32166b

Please sign in to comment.