diff --git a/compile/inc/src/main/scala/sbt/inc/Analysis.scala b/compile/inc/src/main/scala/sbt/inc/Analysis.scala index 90e8c73fa..d9a9dee26 100644 --- a/compile/inc/src/main/scala/sbt/inc/Analysis.scala +++ b/compile/inc/src/main/scala/sbt/inc/Analysis.scala @@ -5,6 +5,7 @@ package sbt package inc import xsbti.api.Source +import xsbti.DependencyContext._ import java.io.File /** diff --git a/compile/inc/src/main/scala/sbt/inc/Compile.scala b/compile/inc/src/main/scala/sbt/inc/Compile.scala index dd2dc4223..29fccb034 100644 --- a/compile/inc/src/main/scala/sbt/inc/Compile.scala +++ b/compile/inc/src/main/scala/sbt/inc/Compile.scala @@ -10,6 +10,8 @@ import xsbti.{ Position, Problem, Severity } import Logger.{ m2o, problem } import java.io.File import xsbti.api.Definition +import xsbti.DependencyContext +import xsbti.DependencyContext.{ DependencyByInheritance, DependencyByMemberRef } object IncrementalCompile { def apply(sources: Set[File], entry: String => Option[File], @@ -61,7 +63,7 @@ private final class AnalysisCallback(internalMap: File => Option[File], external new Compilation(System.currentTimeMillis, outputSettings) } - override def toString = (List("APIs", "Binary deps", "Products", "Source deps") zip List(apis, binaryDeps, classes, sourceDeps)).map { case (label, map) => label + "\n\t" + map.mkString("\n\t") }.mkString("\n") + override def toString = (List("APIs", "Binary deps", "Products", "Source deps") zip List(apis, binaryDeps, classes, intSrcDeps)).map { case (label, map) => label + "\n\t" + map.mkString("\n\t") }.mkString("\n") import collection.mutable.{ HashMap, HashSet, ListBuffer, Map, Set } @@ -75,12 +77,10 @@ private final class AnalysisCallback(internalMap: File => Option[File], external private[this] val classes = new HashMap[File, Set[(File, String)]] // generated class file to its source file private[this] val classToSource = new HashMap[File, File] - // all internal source depenencies, including direct and inherited - private[this] val sourceDeps = new HashMap[File, Set[File]] - // inherited internal source dependencies - private[this] val inheritedSourceDeps = new HashMap[File, Set[File]] + // internal source dependencies + private[this] val intSrcDeps = new HashMap[File, Set[InternalDependency]] // external source dependencies - private[this] val extSrcDeps = new HashMap[File, Iterable[ExternalDependency]] + private[this] val extSrcDeps = new HashMap[File, Set[ExternalDependency]] private[this] val binaryClassName = new HashMap[File, String] // source files containing a macro def. private[this] val macroSources = Set[File]() @@ -96,46 +96,57 @@ private final class AnalysisCallback(internalMap: File => Option[File], external } } + def sourceDependency(dependsOn: File, source: File, context: DependencyContext) = { + add(intSrcDeps, source, InternalDependency(source, dependsOn, context)) + } + + @deprecated("Use `sourceDependency(File, File, DependencyContext)`.", "0.13.8") def sourceDependency(dependsOn: File, source: File, inherited: Boolean) = { - add(sourceDeps, source, dependsOn) - if (inherited) add(inheritedSourceDeps, source, dependsOn) + val context = if (inherited) DependencyByInheritance else DependencyByMemberRef + sourceDependency(dependsOn, source, context) } - def externalBinaryDependency(binary: File, className: String, source: File, inherited: Boolean) { + + private[this] def externalBinaryDependency(binary: File, className: String, source: File, context: DependencyContext) = { binaryClassName.put(binary, className) add(binaryDeps, source, binary) } - // The type corresponds to : (internal source, external source depended on, API of external dependency, true if an inheritance dependency) - def externalSourceDependency(t4: (File, String, Source, Boolean)) = { - val dependency = ExternalDependency(t4._1, t4._2, t4._3, if (t4._4) DependencyByInheritance else DependencyByMemberRef) - extSrcDeps += t4._1 -> (extSrcDeps.getOrElse(t4._1, Nil) ++ List(dependency)) + private[this] def externalSourceDependency(sourceFile: File, dependsOn: String, source: Source, context: DependencyContext) = { + val dependency = ExternalDependency(sourceFile, dependsOn, source, context) + add(extSrcDeps, sourceFile, dependency) } - def binaryDependency(classFile: File, name: String, source: File, inherited: Boolean) = + def binaryDependency(classFile: File, name: String, source: File, context: DependencyContext) = internalMap(classFile) match { case Some(dependsOn) => // dependency is a product of a source not included in this compilation - sourceDependency(dependsOn, source, inherited) + sourceDependency(dependsOn, source, 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) - sourceDependency(dependsOn, source, inherited) + sourceDependency(dependsOn, source, context) case None => - externalDependency(classFile, name, source, inherited) + externalDependency(classFile, name, source, context) } } - private[this] def externalDependency(classFile: File, name: String, source: File, inherited: Boolean): Unit = + @deprecated("Use `binaryDependency(File, String, File, DependencyContext)`.", "0.13.8") + def binaryDependency(classFile: File, name: String, source: File, inherited: Boolean) = { + val context = if (inherited) DependencyByInheritance else DependencyByMemberRef + binaryDependency(classFile, name, source, context) + } + + private[this] def externalDependency(classFile: File, name: String, source: File, context: DependencyContext): Unit = externalAPI(classFile, name) match { case Some(api) => // dependency is a product of a source in another project - externalSourceDependency((source, name, api, inherited)) + externalSourceDependency(source, name, api, context) case None => // dependency is some other binary on the classpath - externalBinaryDependency(classFile, name, source, inherited) + externalBinaryDependency(classFile, name, source, context) } def generatedClass(source: File, module: File, name: String) = @@ -174,16 +185,6 @@ private final class AnalysisCallback(internalMap: File => Option[File], external (a /: names) { case (a, name) => a.copy(relations = a.relations.addUsedName(src, name)) } } - // This is no longer used by the new implementation relative to Dependency contexts - // See https://github.com/sbt/sbt/issues/1340 - def addAll[A, B](base: Analysis, m: Map[A, Set[B]])(f: (Analysis, A, B) => Analysis): Analysis = - (base /: m) { - case (outer, (a, bs)) => - (outer /: bs) { (inner, b) => - f(inner, a, b) - } - } - def addProductsAndDeps(base: Analysis): Analysis = (base /: apis) { case (a, (src, api)) => @@ -193,14 +194,12 @@ private final class AnalysisCallback(internalMap: File => Option[File], external val hasMacro: Boolean = macroSources.contains(src) val s = new xsbti.api.Source(compilation, hash, api._2, api._1, publicNameHashes(src), hasMacro) val info = SourceInfos.makeInfo(getOrNil(reporteds, src), getOrNil(unreporteds, src)) - val direct = sourceDeps.getOrElse(src, Nil: Iterable[File]) - val publicInherited = inheritedSourceDeps.getOrElse(src, Nil: Iterable[File]) val binaries = binaryDeps.getOrElse(src, Nil: Iterable[File]) val prods = classes.getOrElse(src, Nil: Iterable[(File, String)]) val products = prods.map { case (prod, name) => (prod, name, current product prod) } - val internalDeps = direct.map(InternalDependency(src, _, DependencyByMemberRef)) ++ publicInherited.map(InternalDependency(src, _, DependencyByInheritance)) - val externalDeps = extSrcDeps.getOrElse(src, Nil: Iterable[ExternalDependency]) + val internalDeps = intSrcDeps.getOrElse(src, Set.empty) + val externalDeps = extSrcDeps.getOrElse(src, Set.empty) val binDeps = binaries.map(d => (d, binaryClassName(d), current binary d)) a.addSource(src, s, stamp, info, products, internalDeps, externalDeps, binDeps) diff --git a/compile/inc/src/main/scala/sbt/inc/Dependency.scala b/compile/inc/src/main/scala/sbt/inc/Dependency.scala index a10d33d00..668c687fd 100644 --- a/compile/inc/src/main/scala/sbt/inc/Dependency.scala +++ b/compile/inc/src/main/scala/sbt/inc/Dependency.scala @@ -2,22 +2,7 @@ package sbt.inc import java.io.File import xsbti.api.Source - -/** - * Represents contextual information about particular depedency edge. See comments in - * subtypes for examples of particular contexts. - */ -private[inc] sealed abstract class DependencyContext - -/** - * Marks dependency edge introduced by referring to a class through inheritance as in - * - * class A extends B - * - * Each dependency by inheritance introduces corresponding dependency by member reference. - */ -private[inc] final case object DependencyByInheritance extends DependencyContext -private[inc] final case object DependencyByMemberRef extends DependencyContext +import xsbti.DependencyContext /** * Represents the kind of dependency that exists between `sourceFile` and either `targetFile` diff --git a/compile/inc/src/main/scala/sbt/inc/Relations.scala b/compile/inc/src/main/scala/sbt/inc/Relations.scala index 5db719311..6f7aaaf82 100644 --- a/compile/inc/src/main/scala/sbt/inc/Relations.scala +++ b/compile/inc/src/main/scala/sbt/inc/Relations.scala @@ -8,6 +8,8 @@ import java.io.File import Relations.Source import Relations.SourceDependencies import xsbti.api.{ Source => APISource } +import xsbti.DependencyContext +import xsbti.DependencyContext._ /** * Provides mappings between source files, generated classes (products), and binaries. diff --git a/compile/inc/src/test/scala/sbt/inc/AnalysisTest.scala b/compile/inc/src/test/scala/sbt/inc/AnalysisTest.scala index 9df812685..1b0450e25 100644 --- a/compile/inc/src/test/scala/sbt/inc/AnalysisTest.scala +++ b/compile/inc/src/test/scala/sbt/inc/AnalysisTest.scala @@ -7,6 +7,7 @@ import sbt.inc.TestCaseGenerators._ import org.scalacheck._ import Gen._ import Prop._ +import xsbti.DependencyContext._ object AnalysisTest extends Properties("Analysis") { // Merge and split a hard-coded trivial example. diff --git a/compile/inc/src/test/scala/sbt/inc/TestCaseGenerators.scala b/compile/inc/src/test/scala/sbt/inc/TestCaseGenerators.scala index d0628a7e4..2037dddda 100644 --- a/compile/inc/src/test/scala/sbt/inc/TestCaseGenerators.scala +++ b/compile/inc/src/test/scala/sbt/inc/TestCaseGenerators.scala @@ -10,6 +10,7 @@ import Gen._ import sbt.Relation import xsbti.api._ import xsbti.SafeLazy +import xsbti.DependencyContext._ /** * Scalacheck generators for Analysis objects and their substructures. diff --git a/compile/interface/src/main/scala/xsbt/Dependency.scala b/compile/interface/src/main/scala/xsbt/Dependency.scala index 5fb688c73..d7f7b570f 100644 --- a/compile/interface/src/main/scala/xsbt/Dependency.scala +++ b/compile/interface/src/main/scala/xsbt/Dependency.scala @@ -6,6 +6,8 @@ package xsbt import scala.tools.nsc.{ io, symtab, Phase } import io.{ AbstractFile, PlainFile, ZipArchive } import symtab.Flags +import xsbti.DependencyContext +import xsbti.DependencyContext._ import java.io.File @@ -41,22 +43,22 @@ final class Dependency(val global: CallbackGlobal) extends LocateClassFile { if (global.callback.nameHashing) { val dependenciesByMemberRef = extractDependenciesByMemberRef(unit) for (on <- dependenciesByMemberRef) - processDependency(on, inherited = false) + processDependency(on, context = DependencyByMemberRef) val dependenciesByInheritance = extractDependenciesByInheritance(unit) for (on <- dependenciesByInheritance) - processDependency(on, inherited = true) + processDependency(on, context = DependencyByInheritance) } else { - for (on <- unit.depends) processDependency(on, inherited = false) - for (on <- inheritedDependencies.getOrElse(sourceFile, Nil: Iterable[Symbol])) processDependency(on, inherited = true) + for (on <- unit.depends) processDependency(on, context = DependencyByMemberRef) + for (on <- inheritedDependencies.getOrElse(sourceFile, Nil: Iterable[Symbol])) processDependency(on, context = DependencyByInheritance) } /** * Handles dependency on given symbol by trying to figure out if represents a term * that is coming from either source code (not necessarily compiled in this compilation * run) or from class file and calls respective callback method. */ - def processDependency(on: Symbol, inherited: Boolean) { - def binaryDependency(file: File, className: String) = callback.binaryDependency(file, className, sourceFile, inherited) + def processDependency(on: Symbol, context: DependencyContext) { + def binaryDependency(file: File, className: String) = callback.binaryDependency(file, className, sourceFile, context) val onSource = on.sourceFile if (onSource == null) { classFile(on) match { @@ -70,7 +72,7 @@ final class Dependency(val global: CallbackGlobal) extends LocateClassFile { case None => () } } else if (onSource.file != sourceFile) - callback.sourceDependency(onSource.file, sourceFile, inherited) + callback.sourceDependency(onSource.file, sourceFile, context) } } } diff --git a/compile/interface/src/test/scala/xsbt/ScalaCompilerForUnitTesting.scala b/compile/interface/src/test/scala/xsbt/ScalaCompilerForUnitTesting.scala index 926be962f..f3ebd73e9 100644 --- a/compile/interface/src/test/scala/xsbt/ScalaCompilerForUnitTesting.scala +++ b/compile/interface/src/test/scala/xsbt/ScalaCompilerForUnitTesting.scala @@ -12,6 +12,7 @@ import xsbti.api.Definition import xsbti.api.Def import xsbt.api.SameAPI import sbt.ConsoleLogger +import xsbti.DependencyContext._ import ScalaCompilerForUnitTesting.ExtractedSourceDependencies @@ -68,11 +69,11 @@ class ScalaCompilerForUnitTesting(nameHashing: Boolean = false) { val memberRefFileDeps = testCallback.sourceDependencies collect { // false indicates that those dependencies are not introduced by inheritance - case (target, src, false) => (src, target) + case (target, src, DependencyByMemberRef) => (src, target) } val inheritanceFileDeps = testCallback.sourceDependencies collect { // true indicates that those dependencies are introduced by inheritance - case (target, src, true) => (src, target) + case (target, src, DependencyByInheritance) => (src, target) } def toSymbols(src: File, target: File): (Symbol, Symbol) = (fileToSymbol(src), fileToSymbol(target)) val memberRefDeps = memberRefFileDeps map { case (src, target) => toSymbols(src, target) } diff --git a/compile/persist/src/main/scala/sbt/inc/AnalysisFormats.scala b/compile/persist/src/main/scala/sbt/inc/AnalysisFormats.scala index 1af8278aa..d74b81645 100644 --- a/compile/persist/src/main/scala/sbt/inc/AnalysisFormats.scala +++ b/compile/persist/src/main/scala/sbt/inc/AnalysisFormats.scala @@ -7,6 +7,7 @@ package inc import xsbti.api.{ Source, Compilation } import xsbti.{ Position, Problem, Severity } import xsbti.compile.{ CompileOrder, Output => APIOutput, SingleOutput, MultipleOutput } +import xsbti.DependencyContext._ import MultipleOutput.OutputGroup import java.io.File import sbinary._ diff --git a/interface/src/main/java/xsbti/AnalysisCallback.java b/interface/src/main/java/xsbti/AnalysisCallback.java index 0e083d4eb..88b190e80 100644 --- a/interface/src/main/java/xsbti/AnalysisCallback.java +++ b/interface/src/main/java/xsbti/AnalysisCallback.java @@ -12,13 +12,29 @@ public interface AnalysisCallback * passed to this method. Dependencies on classes generated by sources not in the current compilation will * be passed as class dependencies to the classDependency method. * If publicInherited is true, this dependency is a result of inheritance by a - * template accessible outside of the source file. */ + * template accessible outside of the source file. + * @deprecated Use `sourceDependency(File dependsOn, File source, DependencyContext context)` instead. */ + @Deprecated public void sourceDependency(File dependsOn, File source, boolean publicInherited); + /** Called to indicate that the source file source depends on the source file + * dependsOn. Note that only source files included in the current compilation will + * passed to this method. Dependencies on classes generated by sources not in the current compilation will + * be passed as class dependencies to the classDependency method. + * context gives information about the context in which this dependency has been extracted. + * See xsbti.DependencyContext for the list of existing dependency contexts. */ + public void sourceDependency(File dependsOn, File source, DependencyContext context); /** Called to indicate that the source file source depends on the top-level * class named name from class or jar file binary. * If publicInherited is true, this dependency is a result of inheritance by a - * template accessible outside of the source file. */ + * template accessible outside of the source file. + * @deprecated Use `binaryDependency(File binary, String name, File source, DependencyContext context)` instead. */ + @Deprecated public void binaryDependency(File binary, String name, File source, boolean publicInherited); + /** Called to indicate that the source file source depends on the top-level + * class named name from class or jar file binary. + * context gives information about the context in which this dependency has been extracted. + * See xsbti.DependencyContext for the list of existing dependency contexts. */ + public void binaryDependency(File binary, String name, File source, DependencyContext context); /** Called to indicate that the source file source produces a class file at * module contain class name.*/ public void generatedClass(File source, File module, String name); diff --git a/interface/src/main/java/xsbti/DependencyContext.java b/interface/src/main/java/xsbti/DependencyContext.java new file mode 100644 index 000000000..15cfa76d1 --- /dev/null +++ b/interface/src/main/java/xsbti/DependencyContext.java @@ -0,0 +1,22 @@ +package xsbti; + +/** + * Enumeration of existing dependency contexts. + * Dependency contexts represent the various kind of dependencies that + * can exist between symbols. + */ +public enum DependencyContext { + /** + * Represents a direct dependency between two symbols : + * object Foo + * object Bar { def foo = Foo } + */ + DependencyByMemberRef, + + /** + * Represents an inheritance dependency between two symbols : + * class A + * class B extends A + */ + DependencyByInheritance +} diff --git a/interface/src/test/scala/xsbti/TestCallback.scala b/interface/src/test/scala/xsbti/TestCallback.scala index 3ea7e32e1..13b65df79 100644 --- a/interface/src/test/scala/xsbti/TestCallback.scala +++ b/interface/src/test/scala/xsbti/TestCallback.scala @@ -3,17 +3,26 @@ package xsbti import java.io.File import scala.collection.mutable.ArrayBuffer import xsbti.api.SourceAPI +import xsbti.DependencyContext._ class TestCallback(override val nameHashing: Boolean = false) extends AnalysisCallback { - val sourceDependencies = new ArrayBuffer[(File, File, Boolean)] - val binaryDependencies = new ArrayBuffer[(File, String, File, Boolean)] + val sourceDependencies = new ArrayBuffer[(File, File, DependencyContext)] + val binaryDependencies = new ArrayBuffer[(File, String, File, DependencyContext)] val products = new ArrayBuffer[(File, File, String)] val usedNames = scala.collection.mutable.Map.empty[File, Set[String]].withDefaultValue(Set.empty) val apis: scala.collection.mutable.Map[File, SourceAPI] = scala.collection.mutable.Map.empty - def sourceDependency(dependsOn: File, source: File, inherited: Boolean) { sourceDependencies += ((dependsOn, source, inherited)) } - def binaryDependency(binary: File, name: String, source: File, inherited: Boolean) { binaryDependencies += ((binary, name, source, inherited)) } + def sourceDependency(dependsOn: File, source: File, inherited: Boolean) { + val context = if(inherited) DependencyByInheritance else DependencyByMemberRef + sourceDependency(dependsOn, source, context) + } + def sourceDependency(dependsOn: File, source: File, context: DependencyContext) { sourceDependencies += ((dependsOn, source, context)) } + def binaryDependency(binary: File, name: String, source: File, inherited: Boolean) { + val context = if(inherited) DependencyByInheritance else DependencyByMemberRef + binaryDependency(binary, name, source, context) + } + def binaryDependency(binary: File, name: String, source: File, context: DependencyContext) { binaryDependencies += ((binary, name, source, context)) } def generatedClass(source: File, module: File, name: String) { products += ((source, module, name)) } def usedName(source: File, name: String) { usedNames(source) += name } diff --git a/util/classfile/src/main/scala/sbt/classfile/Analyze.scala b/util/classfile/src/main/scala/sbt/classfile/Analyze.scala index 0b12916b3..9df04f1f1 100644 --- a/util/classfile/src/main/scala/sbt/classfile/Analyze.scala +++ b/util/classfile/src/main/scala/sbt/classfile/Analyze.scala @@ -12,6 +12,8 @@ import java.lang.annotation.Annotation import java.lang.reflect.Method import java.lang.reflect.Modifier.{ STATIC, PUBLIC, ABSTRACT } import java.net.URL +import xsbti.DependencyContext +import xsbti.DependencyContext._ private[sbt] object Analyze { def apply[T](newClasses: Seq[File], sources: Seq[File], log: Logger)(analysis: xsbti.AnalysisCallback, loader: ClassLoader, readAPI: (File, Seq[Class[_]]) => Set[String]) { @@ -41,26 +43,26 @@ private[sbt] object Analyze { for ((source, classFiles) <- sourceToClassFiles) { val publicInherited = readAPI(source, classFiles.toSeq.flatMap(c => load(c.className, Some("Error reading API from class file")))) - def processDependency(tpe: String, inherited: Boolean) { + def processDependency(tpe: String, context: DependencyContext) { trapAndLog(log) { for (url <- Option(loader.getResource(tpe.replace('.', '/') + ClassExt)); file <- urlAsFile(url, log)) { if (url.getProtocol == "jar") - analysis.binaryDependency(file, tpe, source, inherited) + analysis.binaryDependency(file, tpe, source, context) else { assume(url.getProtocol == "file") productToSource.get(file) match { - case Some(dependsOn) => analysis.sourceDependency(dependsOn, source, inherited) - case None => analysis.binaryDependency(file, tpe, source, inherited) + case Some(dependsOn) => analysis.sourceDependency(dependsOn, source, context) + case None => analysis.binaryDependency(file, tpe, source, context) } } } } } - def processDependencies(tpes: Iterable[String], inherited: Boolean): Unit = tpes.foreach(tpe => processDependency(tpe, inherited)) + def processDependencies(tpes: Iterable[String], context: DependencyContext): Unit = tpes.foreach(tpe => processDependency(tpe, context)) val notInherited = classFiles.flatMap(_.types).toSet -- publicInherited - processDependencies(notInherited, false) - processDependencies(publicInherited, true) + processDependencies(notInherited, DependencyByMemberRef) + processDependencies(publicInherited, DependencyByInheritance) } for (source <- sources filterNot sourceToClassFiles.keySet) {