diff --git a/compile/api/src/main/scala/xsbt/api/APIUtil.scala b/compile/api/src/main/scala/xsbt/api/APIUtil.scala index d8c4e88cc..4d5f3db53 100644 --- a/compile/api/src/main/scala/xsbt/api/APIUtil.scala +++ b/compile/api/src/main/scala/xsbt/api/APIUtil.scala @@ -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 diff --git a/compile/inc/src/main/scala/sbt/inc/APIs.scala b/compile/inc/src/main/scala/sbt/inc/APIs.scala index 08de5b88d..f7802a651 100644 --- a/compile/inc/src/main/scala/sbt/inc/APIs.scala +++ b/compile/inc/src/main/scala/sbt/inc/APIs.scala @@ -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]) = { diff --git a/compile/inc/src/main/scala/sbt/inc/Compile.scala b/compile/inc/src/main/scala/sbt/inc/Compile.scala index 41338e7bd..96fde63f6 100644 --- a/compile/inc/src/main/scala/sbt/inc/Compile.scala +++ b/compile/inc/src/main/scala/sbt/inc/Compile.scala @@ -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)]) diff --git a/compile/inc/src/main/scala/sbt/inc/IncrementalAntStyle.scala b/compile/inc/src/main/scala/sbt/inc/IncrementalAntStyle.scala index c1d392c47..bcca579e0 100644 --- a/compile/inc/src/main/scala/sbt/inc/IncrementalAntStyle.scala +++ b/compile/inc/src/main/scala/sbt/inc/IncrementalAntStyle.scala @@ -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 diff --git a/compile/inc/src/main/scala/sbt/inc/IncrementalCommon.scala b/compile/inc/src/main/scala/sbt/inc/IncrementalCommon.scala index e38c68e2c..a73faf0f9 100644 --- a/compile/inc/src/main/scala/sbt/inc/IncrementalCommon.scala +++ b/compile/inc/src/main/scala/sbt/inc/IncrementalCommon.scala @@ -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. diff --git a/compile/inc/src/main/scala/sbt/inc/IncrementalDefaultImpl.scala b/compile/inc/src/main/scala/sbt/inc/IncrementalDefaultImpl.scala index c43fd5b90..94be1c66f 100644 --- a/compile/inc/src/main/scala/sbt/inc/IncrementalDefaultImpl.scala +++ b/compile/inc/src/main/scala/sbt/inc/IncrementalDefaultImpl.scala @@ -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)) diff --git a/compile/inc/src/main/scala/sbt/inc/IncrementalNameHashing.scala b/compile/inc/src/main/scala/sbt/inc/IncrementalNameHashing.scala index 49c0c1d4c..ef7ea1eb9 100644 --- a/compile/inc/src/main/scala/sbt/inc/IncrementalNameHashing.scala +++ b/compile/inc/src/main/scala/sbt/inc/IncrementalNameHashing.scala @@ -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)) diff --git a/compile/inc/src/test/scala/sbt/inc/TestCaseGenerators.scala b/compile/inc/src/test/scala/sbt/inc/TestCaseGenerators.scala index 7591183b1..92571984d 100644 --- a/compile/inc/src/test/scala/sbt/inc/TestCaseGenerators.scala +++ b/compile/inc/src/test/scala/sbt/inc/TestCaseGenerators.scala @@ -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)) diff --git a/interface/other b/interface/other index 68e4c3a50..b357435a7 100644 --- a/interface/other +++ b/interface/other @@ -5,6 +5,7 @@ Source apiHash: Int _internalOnly_nameHashes: _internalOnly_NameHashes hasMacro: Boolean + hasPackageObject: Boolean _internalOnly_NameHashes regularMembers: _internalOnly_NameHash* diff --git a/sbt/src/sbt-test/source-dependencies/package-object-name/pending b/sbt/src/sbt-test/source-dependencies/package-object-name/test similarity index 100% rename from sbt/src/sbt-test/source-dependencies/package-object-name/pending rename to sbt/src/sbt-test/source-dependencies/package-object-name/test