Skip to content

Commit

Permalink
Implemented functionality to invalidate classpath entries, to enable …
Browse files Browse the repository at this point in the history
…incremental compiles.
  • Loading branch information
odersky committed May 28, 2012
1 parent e490b02 commit ace051f
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 6 deletions.
102 changes: 100 additions & 2 deletions src/compiler/scala/tools/nsc/Global.scala
Expand Up @@ -72,7 +72,10 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb
if (forMSIL) new { val global: Global.this.type = Global.this } with MSILPlatform if (forMSIL) new { val global: Global.this.type = Global.this } with MSILPlatform
else new { val global: Global.this.type = Global.this } with JavaPlatform else new { val global: Global.this.type = Global.this } with JavaPlatform


def classPath: ClassPath[platform.BinaryRepr] = platform.classPath type PlatformClassPath = ClassPath[platform.BinaryRepr]

def classPath: PlatformClassPath = platform.classPath

def rootLoader: LazyType = platform.rootLoader def rootLoader: LazyType = platform.rootLoader


// sub-components -------------------------------------------------- // sub-components --------------------------------------------------
Expand Down Expand Up @@ -840,6 +843,97 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb
def printAfterEachPhase[T](op: => T): Unit = def printAfterEachPhase[T](op: => T): Unit =
describeAfterEachPhase(op) foreach (m => println(" " + m)) describeAfterEachPhase(op) foreach (m => println(" " + m))


// ------------ Invalidations ---------------------------------

/** Is given package class a system package class that cannot be invalidated?
*/
private def isSystemPackageClass(pkg: Symbol) =
pkg == definitions.RootClass ||
pkg == definitions.ScalaPackageClass || {
val pkgname = pkg.fullName
(pkgname startsWith "scala.") && !(pkgname startsWith "scala.tools")
}

/** Invalidates packages that contain classes defined in a classpath entry, and
* rescans that entry.
* @param path A fully qualified name that refers to a directory or jar file that's
* an entry on the classpath.
* First, causes the classpath entry referred to by `path` to be rescanned, so that
* any new files or deleted files or changes in subpackages are picked up.
* Second, invalidates any packages for which one of the following considitions is met:
* - the classpath entry contained during the last compilation run classfiles
* that represent a member in the package
* - the classpath entry now contains classfiles
* that represent a member in the package
* - the set of subpackages has changed.
*
* The invalidated packages are reset in their entirety; all member classes and member packages
* are re-accessed using the new classpath.
* Not invalidated are system packages that the compiler needs to access as parts
* of standard definitions. The criterion what is a system package is currently:
* any package rooted in "scala", with the exception of packages rooted in "scala.tools".
* This can be refined later.
* @return A pair consisting of
* - a list of invalidated packages
* - a list of of packages that should have been invalidated but were not because
* they are system packages.
*/
def invalidateClassPathEntry(path: String): (List[Symbol], List[Symbol]) = {
val invalidated, failed = new mutable.ListBuffer[Symbol]
classPath match {
case cp: util.MergedClassPath[_] =>
val dir = AbstractFile getDirectory path
val canonical = Some(dir.canonicalPath)
cp.entries find (_.origin == canonical) match {
case Some(oldEntry) =>
val newEntry = cp.context.newClassPath(dir)
platform.updateClassPath(oldEntry, newEntry)
informProgress(s"classpath updated to $classPath")
reSync(definitions.RootClass, classPath, oldEntry, newEntry, invalidated, failed)
case None =>
error(s"cannot invalidate: no entry named $path in classpath $classPath")
}
}
def show(msg: String, syms: collection.Traversable[Symbol]) =
if (syms.nonEmpty)
informProgress(s"$msg: ${syms map (_.fullName) mkString ","}")
show("invalidated packages", invalidated)
show("could not invalidate system packages", failed)
(invalidated.toList, failed.toList)
}

private def reSync(root: Symbol, all: PlatformClassPath,
oldEntry: PlatformClassPath, newEntry: PlatformClassPath,
invalidated: mutable.ListBuffer[Symbol], failed: mutable.ListBuffer[Symbol]) {
ifDebug(informProgress(s"syncing $root, $oldEntry -> $newEntry"))
val getName: ClassPath[platform.BinaryRepr] => String = (_.name)
val oldPackages = oldEntry.packages sortBy getName
val newPackages = newEntry.packages sortBy getName
val hasChanged =
oldEntry.classes.nonEmpty ||
newEntry.classes.nonEmpty ||
(oldPackages map getName) != (newPackages map getName)
if (hasChanged && !isSystemPackageClass(root)) {
root setInfo new loaders.PackageLoader(all)
invalidated += root
} else {
if (hasChanged) failed += root
for ((oldNested, newNested) <- oldPackages zip newPackages) {
val pkgname = newNested.name
val pkg = root.info decl newTermName(pkgname)
val allNested = (all.packages find (_.name == pkgname)).get
reSync(pkg.moduleClass, allNested, oldNested, newNested, invalidated, failed)
}
}
}

/** Invalidate contents of setting -Yinvalidate */
def doInvalidation() = settings.Yinvalidate.value match {
case "" =>
case entry => invalidateClassPathEntry(entry)
}

// ----------- Runs --------------------------------------- // ----------- Runs ---------------------------------------


private var curRun: Run = null private var curRun: Run = null
Expand All @@ -859,6 +953,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb
* *
* @param sym A class symbol, object symbol, package, or package class. * @param sym A class symbol, object symbol, package, or package class.
*/ */
@deprecated("use invalidateClassPathEntry instead")
def clearOnNextRun(sym: Symbol) = false def clearOnNextRun(sym: Symbol) = false
/* To try out clearOnNext run on the scala.tools.nsc project itself /* To try out clearOnNext run on the scala.tools.nsc project itself
* replace `false` above with the following code * replace `false` above with the following code
Expand All @@ -870,7 +965,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb
} }
}} }}
* Then, fsc -Xexperimental clears the nsc porject between successive runs of `fsc`. * Then, fsc -Xexperimental clears the nsc project between successive runs of `fsc`.
*/ */


/** Remove the current run when not needed anymore. Used by the build /** Remove the current run when not needed anymore. Used by the build
Expand Down Expand Up @@ -1115,6 +1210,7 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb
/** Reset all classes contained in current project, as determined by /** Reset all classes contained in current project, as determined by
* the clearOnNextRun hook * the clearOnNextRun hook
*/ */
@deprecated("use invalidateClassPathEntry instead")
def resetProjectClasses(root: Symbol): Unit = try { def resetProjectClasses(root: Symbol): Unit = try {
def unlink(sym: Symbol) = def unlink(sym: Symbol) =
if (sym != NoSymbol) root.info.decls.unlink(sym) if (sym != NoSymbol) root.info.decls.unlink(sym)
Expand Down Expand Up @@ -1353,6 +1449,8 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb
} }


private def compileUnitsInternal(units: List[CompilationUnit], fromPhase: Phase) { private def compileUnitsInternal(units: List[CompilationUnit], fromPhase: Phase) {
doInvalidation()

units foreach addUnit units foreach addUnit
val startTime = currentTime val startTime = currentTime


Expand Down
14 changes: 12 additions & 2 deletions src/compiler/scala/tools/nsc/backend/JavaPlatform.scala
Expand Up @@ -7,7 +7,7 @@ package scala.tools.nsc
package backend package backend


import io.AbstractFile import io.AbstractFile
import util.{ClassPath,JavaClassPath} import util.{ClassPath,JavaClassPath,MergedClassPath,DeltaClassPath}
import util.ClassPath.{ JavaContext, DefaultJavaContext } import util.ClassPath.{ JavaContext, DefaultJavaContext }
import scala.tools.util.PathResolver import scala.tools.util.PathResolver


Expand All @@ -17,7 +17,17 @@ trait JavaPlatform extends Platform {


type BinaryRepr = AbstractFile type BinaryRepr = AbstractFile


lazy val classPath = new PathResolver(settings).result private var currentClassPath: Option[MergedClassPath[BinaryRepr]] = None

def classPath: ClassPath[BinaryRepr] = {
if (currentClassPath.isEmpty) currentClassPath = Some(new PathResolver(settings).result)
currentClassPath.get
}

/** Update classpath with a substituted subentry */
def updateClassPath(oldEntry: ClassPath[BinaryRepr], newEntry: ClassPath[BinaryRepr]) =
currentClassPath = Some(new DeltaClassPath(currentClassPath.get, oldEntry, newEntry))

def rootLoader = new loaders.PackageLoader(classPath.asInstanceOf[ClassPath[platform.BinaryRepr]]) def rootLoader = new loaders.PackageLoader(classPath.asInstanceOf[ClassPath[platform.BinaryRepr]])
// [Martin] Why do we need a cast here? // [Martin] Why do we need a cast here?
// The problem is that we cannot specify at this point that global.platform should be of type JavaPlatform. // The problem is that we cannot specify at this point that global.platform should be of type JavaPlatform.
Expand Down
6 changes: 5 additions & 1 deletion src/compiler/scala/tools/nsc/backend/MSILPlatform.scala
Expand Up @@ -30,7 +30,11 @@ trait MSILPlatform extends Platform {
lazy val classPath = MsilClassPath.fromSettings(settings) lazy val classPath = MsilClassPath.fromSettings(settings)
def rootLoader = new loaders.PackageLoader(classPath.asInstanceOf[ClassPath[platform.BinaryRepr]]) def rootLoader = new loaders.PackageLoader(classPath.asInstanceOf[ClassPath[platform.BinaryRepr]])
// See discussion in JavaPlatForm for why we need a cast here. // See discussion in JavaPlatForm for why we need a cast here.


/** Update classpath with a substituted subentry */
def updateClassPath(oldEntry: ClassPath[BinaryRepr], newEntry: ClassPath[BinaryRepr]) =
throw new UnsupportedOperationException("classpath invalidations not supported on MSIL")

def platformPhases = List( def platformPhases = List(
genMSIL // generate .msil files genMSIL // generate .msil files
) )
Expand Down
3 changes: 3 additions & 0 deletions src/compiler/scala/tools/nsc/backend/Platform.scala
Expand Up @@ -23,6 +23,9 @@ trait Platform {


/** The root symbol loader. */ /** The root symbol loader. */
def rootLoader: LazyType def rootLoader: LazyType

/** Update classpath with a substituted subentry */
def updateClassPath(oldEntry: ClassPath[BinaryRepr], newEntry: ClassPath[BinaryRepr])


/** Any platform-specific phases. */ /** Any platform-specific phases. */
def platformPhases: List[SubComponent] def platformPhases: List[SubComponent]
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/scala/tools/nsc/backend/icode/Members.scala
Expand Up @@ -156,7 +156,7 @@ trait Members {


def newBlock() = code.newBlock def newBlock() = code.newBlock
def startBlock = code.startBlock def startBlock = code.startBlock
def lastBlock = blocks.last def lastBlock = { assert(blocks.nonEmpty, symbol); blocks.last }
def blocks = code.blocksList def blocks = code.blocksList
def linearizedBlocks(lin: Linearizer = self.linearizer): List[BasicBlock] = lin linearize this def linearizedBlocks(lin: Linearizer = self.linearizer): List[BasicBlock] = lin linearize this


Expand Down
1 change: 1 addition & 0 deletions src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
Expand Up @@ -171,6 +171,7 @@ trait ScalaSettings extends AbsScalaSettings
val Ynotnull = BooleanSetting ("-Ynotnull", "Enable (experimental and incomplete) scala.NotNull.") val Ynotnull = BooleanSetting ("-Ynotnull", "Enable (experimental and incomplete) scala.NotNull.")
val YmethodInfer = BooleanSetting ("-Yinfer-argument-types", "Infer types for arguments of overriden methods.") val YmethodInfer = BooleanSetting ("-Yinfer-argument-types", "Infer types for arguments of overriden methods.")
val etaExpandKeepsStar = BooleanSetting ("-Yeta-expand-keeps-star", "Eta-expand varargs methods to T* rather than Seq[T]. This is a temporary option to ease transition.") val etaExpandKeepsStar = BooleanSetting ("-Yeta-expand-keeps-star", "Eta-expand varargs methods to T* rather than Seq[T]. This is a temporary option to ease transition.")
val Yinvalidate = StringSetting ("-Yinvalidate", "classpath-entry", "Invalidate classpath entry before run", "")
val noSelfCheck = BooleanSetting ("-Yno-self-type-checks", "Suppress check for self-type conformance among inherited members.") val noSelfCheck = BooleanSetting ("-Yno-self-type-checks", "Suppress check for self-type conformance among inherited members.")
val YvirtClasses = false // too embryonic to even expose as a -Y //BooleanSetting ("-Yvirtual-classes", "Support virtual classes") val YvirtClasses = false // too embryonic to even expose as a -Y //BooleanSetting ("-Yvirtual-classes", "Support virtual classes")


Expand Down
5 changes: 5 additions & 0 deletions src/compiler/scala/tools/nsc/util/ClassPath.scala
Expand Up @@ -312,6 +312,11 @@ class DirectoryClassPath(val dir: AbstractFile, val context: ClassPathContext[Ab
override def toString() = "directory classpath: "+ origin.getOrElse("?") override def toString() = "directory classpath: "+ origin.getOrElse("?")
} }


class DeltaClassPath[T](original: MergedClassPath[T], oldEntry: ClassPath[T], newEntry: ClassPath[T])
extends MergedClassPath[T](original.entries map (e => if (e == oldEntry) newEntry else e), original.context) {
require(original.entries contains oldEntry)
}

/** /**
* A classpath unifying multiple class- and sourcepath entries. * A classpath unifying multiple class- and sourcepath entries.
*/ */
Expand Down

0 comments on commit ace051f

Please sign in to comment.