Do not rely on filename of package objects

Add a new field in `Source` that indicates whether this `Source`
represents a package object. This field is used in the incrmental
compiler to perform recompilation of package objects rather than relying
on the file name being `package.scala`.

Mark pending test `source-dependencies/package-object-name` as passing.
This commit is contained in:
Martin Duhem 2016-02-03 16:32:35 +01:00
parent 09bf5255ad
commit b7b1f2453d
10 changed files with 43 additions and 13 deletions

View File

@ -17,6 +17,26 @@ object APIUtil {
def isScalaSourceName(name: String): Boolean = name.endsWith(".scala")
def hasPackageObject(s: SourceAPI): Boolean =
{
val check = new HasPackageObject
check.visitAPI(s)
check.hasPackageObject
}
private[this] class HasPackageObject extends Visit {
var hasPackageObject = false
private def isPackageObject(c: ClassLike): Boolean = {
import xsbti.api.DefinitionType.{ Module, PackageModule }
c.definitionType == PackageModule || (c.definitionType == Module && c.name.endsWith(".package"))
}
override def visitClass0(c: ClassLike): Unit = {
hasPackageObject ||= isPackageObject(c)
}
}
def hasMacro(s: SourceAPI): Boolean =
{
val check = new HasMacro

View File

@ -38,6 +38,8 @@ trait APIs {
def internal: Map[File, Source]
def external: Map[String, Source]
def hasPackageObject(src: File): Boolean
}
object APIs {
def apply(internal: Map[File, Source], external: Map[String, Source]): APIs = new MAPIs(internal, external)
@ -46,7 +48,7 @@ object APIs {
val emptyAPI = new xsbti.api.SourceAPI(Array(), Array())
val emptyCompilation = new xsbti.api.Compilation(-1, Array())
val emptyNameHashes = new xsbti.api._internalOnly_NameHashes(Array.empty, Array.empty)
val emptySource = new xsbti.api.Source(emptyCompilation, Array(), emptyAPI, 0, emptyNameHashes, false)
val emptySource = new xsbti.api.Source(emptyCompilation, Array(), emptyAPI, 0, emptyNameHashes, false, false)
def getAPI[T](map: Map[T, Source], src: T): Source = map.getOrElse(src, emptySource)
}
@ -71,6 +73,8 @@ private class MAPIs(val internal: Map[File, Source], val external: Map[String, S
def internalAPI(src: File) = getAPI(internal, src)
def externalAPI(ext: String) = getAPI(external, ext)
override def hasPackageObject(src: File): Boolean = internalAPI(src).hasPackageObject
override def equals(other: Any): Boolean = other match {
case o: MAPIs => {
def areEqual[T](x: Map[T, Source], y: Map[T, Source])(implicit ord: math.Ordering[T]) = {

View File

@ -110,6 +110,8 @@ private final class AnalysisCallback(internalMap: File => Option[File], external
private[this] val binaryClassName = new HashMap[File, String]
// source files containing a macro def.
private[this] val macroSources = Set[File]()
// source files defining a package object
private[this] val packageObjectSources = Set[File]()
private def add[A, B](map: Map[A, Set[B]], a: A, b: B): Unit =
map.getOrElseUpdate(a, new HashSet[B]) += b
@ -186,7 +188,10 @@ private final class AnalysisCallback(internalMap: File => Option[File], external
def api(sourceFile: File, source: SourceAPI): Unit = {
import xsbt.api.{ APIUtil, HashAPI }
if (APIUtil.isScalaSourceName(sourceFile.getName) && APIUtil.hasMacro(source)) macroSources += sourceFile
if (APIUtil.isScalaSourceName(sourceFile.getName)) {
if (APIUtil.hasMacro(source)) macroSources += sourceFile
if (APIUtil.hasPackageObject(source)) packageObjectSources += sourceFile
}
publicNameHashes(sourceFile) = {
if (nameHashing)
(new xsbt.api.NameHashing).nameHashes(source)
@ -218,7 +223,8 @@ private final class AnalysisCallback(internalMap: File => Option[File], external
val hash = stamp match { case h: Hash => h.value; case _ => new Array[Byte](0) }
// TODO store this in Relations, rather than Source.
val hasMacro: Boolean = macroSources.contains(src)
val s = new xsbti.api.Source(compilation, hash, api._2, api._1, publicNameHashes(src), hasMacro)
val hasPackageObject = packageObjectSources.contains(src)
val s = new xsbti.api.Source(compilation, hash, api._2, api._1, publicNameHashes(src), hasMacro, hasPackageObject)
val info = SourceInfos.makeInfo(getOrNil(reporteds, src), getOrNil(unreporteds, src))
val binaries = binaryDeps.getOrElse(src, Nil: Iterable[File])
val prods = classes.getOrElse(src, Nil: Iterable[(File, String)])

View File

@ -7,7 +7,7 @@ import xsbti.api.Source
private final class IncrementalAntStyle(log: Logger, options: IncOptions) extends IncrementalCommon(log, options) {
/** Ant-style mode doesn't do anything special with package objects */
override protected def invalidatedPackageObjects(invalidated: Set[File], relations: Relations): Set[File] = Set.empty
override protected def invalidatedPackageObjects(invalidated: Set[File], relations: Relations, apis: APIs): Set[File] = Set.empty
/** In Ant-style mode we don't need to compare APIs because we don't perform any invalidation */
override protected def sameAPI[T](src: T, a: Source, b: Source): Option[APIChange[T]] = None

View File

@ -24,7 +24,7 @@ private[inc] abstract class IncrementalCommon(log: Logger, options: IncOptions)
else {
val wrappedLog = new Incremental.PrefixingLogger("[inv] ")(log)
def debug(s: => String) = if (incDebug(options)) wrappedLog.debug(s) else ()
val withPackageObjects = invalidatedRaw ++ invalidatedPackageObjects(invalidatedRaw, previous.relations)
val withPackageObjects = invalidatedRaw ++ invalidatedPackageObjects(invalidatedRaw, previous.relations, previous.apis)
val invalidated = expand(withPackageObjects, allSources)
val pruned = Incremental.prune(invalidated, previous, classfileManager)
debug("********* Pruned: \n" + pruned.relations + "\n*********")
@ -54,7 +54,7 @@ private[inc] abstract class IncrementalCommon(log: Logger, options: IncOptions)
} else invalidated
}
protected def invalidatedPackageObjects(invalidated: Set[File], relations: Relations): Set[File]
protected def invalidatedPackageObjects(invalidated: Set[File], relations: Relations, apis: APIs): Set[File]
/**
* Logs API changes using debug-level logging. The API are obtained using the APIDiff class.

View File

@ -9,8 +9,8 @@ private final class IncrementalDefaultImpl(log: Logger, options: IncOptions) ext
// Package objects are fragile: if they inherit from an invalidated source, get "class file needed by package is missing" error
// This might be too conservative: we probably only need package objects for packages of invalidated sources.
override protected def invalidatedPackageObjects(invalidated: Set[File], relations: Relations): Set[File] =
invalidated flatMap relations.publicInherited.internal.reverse filter { _.getName == "package.scala" }
override protected def invalidatedPackageObjects(invalidated: Set[File], relations: Relations, apis: APIs): Set[File] =
invalidated flatMap relations.publicInherited.internal.reverse filter apis.hasPackageObject
override protected def sameAPI[T](src: T, a: Source, b: Source): Option[SourceAPIChange[T]] = {
if (SameAPI(a, b))

View File

@ -17,10 +17,8 @@ private final class IncrementalNameHashing(log: Logger, options: IncOptions) ext
// Package objects are fragile: if they inherit from an invalidated source, get "class file needed by package is missing" error
// This might be too conservative: we probably only need package objects for packages of invalidated sources.
override protected def invalidatedPackageObjects(invalidated: Set[File], relations: Relations): Set[File] =
transitiveDeps(invalidated)(relations.inheritance.internal.reverse).filter {
_.getName == "package.scala"
}
override protected def invalidatedPackageObjects(invalidated: Set[File], relations: Relations, apis: APIs): Set[File] =
transitiveDeps(invalidated)(relations.inheritance.internal.reverse) filter apis.hasPackageObject
override protected def sameAPI[T](src: T, a: Source, b: Source): Option[APIChange[T]] = {
if (SameAPI(a, b))

View File

@ -110,8 +110,9 @@ object TestCaseGenerators {
hash <- Gen.containerOfN[Array, Byte](hashLen, arbitrary[Byte])
apiHash <- arbitrary[Int]
hasMacro <- arbitrary[Boolean]
hasPackageObject <- arbitrary[Boolean]
nameHashes <- genNameHashes(defns)
} yield new Source(new Compilation(startTime, Array()), hash, new SourceAPI(Array(), Array(defns map makeDefinition: _*)), apiHash, nameHashes, hasMacro)
} yield new Source(new Compilation(startTime, Array()), hash, new SourceAPI(Array(), Array(defns map makeDefinition: _*)), apiHash, nameHashes, hasMacro, hasPackageObject)
def genSources(all_defns: Seq[Seq[String]]): Gen[Seq[Source]] = Gen.sequence[List, Source](all_defns.map(genSource))

View File

@ -5,6 +5,7 @@ Source
apiHash: Int
_internalOnly_nameHashes: _internalOnly_NameHashes
hasMacro: Boolean
hasPackageObject: Boolean
_internalOnly_NameHashes
regularMembers: _internalOnly_NameHash*