summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDavid MacIver <david.maciver@gmail.com>2009-02-15 17:00:16 +0000
committerDavid MacIver <david.maciver@gmail.com>2009-02-15 17:00:16 +0000
commitdd368937571ef308989e1388b08e04f55b0b6cd4 (patch)
tree234564d9c14ff4489e131602540ce444c145f059 /src
parent266df9f05ecd11e67166e57cc58236646f2f50bb (diff)
downloadscala-dd368937571ef308989e1388b08e04f55b0b6cd4.tar.gz
scala-dd368937571ef308989e1388b08e04f55b0b6cd4.tar.bz2
scala-dd368937571ef308989e1388b08e04f55b0b6cd4.zip
Added -make flag and supporting functionality f...
Added -make flag and supporting functionality for recompilation of only changed files and their dependencies. An email to scala-internals will follow shortly to explain how this works.
Diffstat (limited to 'src')
-rw-r--r--src/compiler/scala/tools/nsc/Global.scala17
-rw-r--r--src/compiler/scala/tools/nsc/Settings.scala2
-rw-r--r--src/compiler/scala/tools/nsc/dependencies/DependencyAnalysis.scala91
-rw-r--r--src/compiler/scala/tools/nsc/dependencies/Files.scala205
4 files changed, 314 insertions, 1 deletions
diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala
index 2a4e024a21..bb17780dab 100644
--- a/src/compiler/scala/tools/nsc/Global.scala
+++ b/src/compiler/scala/tools/nsc/Global.scala
@@ -18,6 +18,7 @@ import scala.collection.mutable.{HashSet, HashMap, ListBuffer}
import symtab._
import symtab.classfile.{PickleBuffer, Pickler}
+import dependencies.DependencyAnalysis
import util.Statistics
import plugins.Plugins
import ast._
@@ -442,6 +443,12 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable
val runsRightAfter = None
} with GenJVM
+ object dependencyAnalysis extends {
+ val global: Global.this.type = Global.this
+ val runsAfter = List("jvm")
+ val runsRightAfter = None
+ } with DependencyAnalysis
+
// phaseName = "msil"
object genMSIL extends {
val global: Global.this.type = Global.this
@@ -519,6 +526,12 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable
if (forJVM) {
phasesSet += liftcode // generate reified trees
phasesSet += genJVM // generate .class files
+ if (!dependencyAnalysis.off){
+ if(settings.debug.value){
+ println("Adding dependency analysis phase");
+ }
+ phasesSet += dependencyAnalysis
+ }
}
if (forMSIL) {
phasesSet += genMSIL // generate .msil files
@@ -661,7 +674,7 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable
else false
def compileSources(_sources: List[SourceFile]) {
- val sources = _sources.removeDuplicates // bug #1268, scalac confused by duplicated filenames
+ val sources = dependencyAnalysis.filter(_sources.removeDuplicates) // bug #1268, scalac confused by duplicated filenames
if (reporter.hasErrors)
return // there is a problem already, e.g. a
// plugin was passed a bad option
@@ -721,6 +734,8 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable
}
for ((sym, file) <- symSource.elements) resetPackageClass(sym.owner)
informTime("total", startTime)
+
+ dependencyAnalysis.writeToFile();
}
def compileLate(file: AbstractFile) {
diff --git a/src/compiler/scala/tools/nsc/Settings.scala b/src/compiler/scala/tools/nsc/Settings.scala
index 3b2a8c501d..479e0e81f0 100644
--- a/src/compiler/scala/tools/nsc/Settings.scala
+++ b/src/compiler/scala/tools/nsc/Settings.scala
@@ -96,6 +96,8 @@ class Settings(error: String => Unit) {
val explaintypes = BooleanSetting ("-explaintypes", "Explain type errors in more detail").hideToIDE
val uniqid = BooleanSetting ("-uniqid", "Print identifiers with unique names for debugging").hideToIDE
val version = BooleanSetting ("-version", "Print product version and exit").hideToIDE
+ val dependenciesFile = StringSetting ("-dependencyfile", "file", "Specify the file in which dependencies are tracked", ".scala_dependencies")
+ val make = ChoiceSetting ("-make", "Specify the behaviour for selecting which files need to be recompiled", List("all", "changed", "immediate", "transitive"), "all")
val help = BooleanSetting ("-help", "Print a synopsis of standard options").hideToIDE
val Xhelp = BooleanSetting ("-X", "Print a synopsis of advanced options").hideToIDE
val argfiles = BooleanSetting ("@<file>", "A text file containing compiler arguments (options and source files)") // only for the help message
diff --git a/src/compiler/scala/tools/nsc/dependencies/DependencyAnalysis.scala b/src/compiler/scala/tools/nsc/dependencies/DependencyAnalysis.scala
new file mode 100644
index 0000000000..5004086c1a
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/dependencies/DependencyAnalysis.scala
@@ -0,0 +1,91 @@
+package scala.tools.nsc.dependencies;
+import util.SourceFile;
+
+trait DependencyAnalysis extends SubComponent with Files{
+ import global._
+
+ val phaseName = "dependencyAnalysis";
+
+ def off = settings.make.value == "all"
+
+ def newPhase(prev : Phase) = new AnalysisPhase(prev)
+
+ lazy val maxDepth = settings.make.value match {
+ case "changed" => 0
+ case "transitive" => Int.MaxValue
+ case "immediate" => 1
+ }
+
+ def nameToFile(name : Any) =
+ settings.outdir.value / (name.toString.replace(".", java.io.File.separator) + ".class")
+
+ lazy val dependenciesFile : Option[File] = settings.dependenciesFile.value match {
+ case "none" => None
+ case x => Some(toFile(x))
+ }
+
+ def classpath = settings.classpath.value
+ def newDeps = new FileDependencies(classpath);
+
+ lazy val dependencies =
+ dependenciesFile match {
+ case Some(f) if f.exists => {
+ val fd = FileDependencies.readFrom(f);
+ if (fd.classpath != classpath) {
+ if(settings.debug.value){
+ println("Classpath has changed. Nuking dependencies");
+ }
+ newDeps
+ }
+ else fd
+ }
+ case _ => newDeps;
+ }
+
+ def writeToFile() = if(!off){
+ dependenciesFile.foreach(dependencies.writeTo(_));
+ }
+
+ def filter(files : List[SourceFile]) : List[SourceFile] =
+ if (off) files
+ else if (dependencies.isEmpty){
+ if(settings.debug.value){
+ println("No known dependencies. Compiling everything");
+ }
+ files
+ }
+ else {
+ val (direct, indirect) = dependencies.invalidatedFiles(maxDepth);
+ val filtered = files.filter(x => {
+ val f = x.path.absolute;
+ direct(f) || indirect(f) || !dependencies.containsFile(f);
+ })
+ filtered match {
+ case Nil => println("No changes to recompile");
+ case x => println("Recompiling " + (
+ if(settings.debug.value) x.mkString(", ")
+ else x.length + " files")
+ )
+ }
+ filtered
+ }
+
+ class AnalysisPhase(prev : Phase) extends StdPhase(prev){
+ def apply(unit : global.CompilationUnit) {
+ val f = unit.source.file.file;
+ // When we're passed strings by the interpreter
+ // they have no source file. We simply ignore this case
+ // as irrelevant to dependency analysis.
+ if (f != null){
+ val source : File = unit.source.file.file;
+ for (d <- unit.icode){
+ dependencies.emits(source, nameToFile(d))
+ }
+
+ for (d <- unit.depends; if (d.sourceFile != null)){
+ dependencies.depends(source, d.sourceFile.file);
+ }
+ }
+ }
+ }
+}
diff --git a/src/compiler/scala/tools/nsc/dependencies/Files.scala b/src/compiler/scala/tools/nsc/dependencies/Files.scala
new file mode 100644
index 0000000000..96e9e0f4be
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/dependencies/Files.scala
@@ -0,0 +1,205 @@
+package scala.tools.nsc.dependencies;
+
+import java.io.{File => JFile, _}
+
+import scala.collection.mutable._;
+
+trait Files{
+
+ implicit def toFile(name : String) : File = toFile(new JFile(name));
+ implicit def toFile(jf : JFile) : File = new File(jf);
+
+ class FileDependencies(val classpath : String){
+ class Tracker extends OpenHashMap[File, Set[File]]{
+ override def default(key : File) = {
+ this(key) = new HashSet[File];
+ this(key);
+ }
+ }
+
+ val dependencies = new Tracker
+ val targets = new Tracker;
+
+ def isEmpty = dependencies.isEmpty && targets.isEmpty
+
+ def emits(source : File, result : File) = targets(source.absolute) += result.absolute;
+ def depends(from : File, on : File) = dependencies(from.absolute) += on.absolute;
+
+ def reset(file : File) = dependencies -= file;
+
+ def cleanEmpty() = {
+ dependencies.foreach({case (key, value) => value.retain(_.exists)})
+ dependencies.retain((key, value) => key.exists && !value.isEmpty)
+ }
+
+ def containsFile(f : File) = targets.contains(f.absolute)
+
+ def invalidatedFiles(maxDepth : Int) = {
+ val direct = new HashSet[File];
+
+ for ((file, products) <- targets) {
+ // This looks a bit odd. It may seem like one should invalidate a file
+ // if *any* of its dependencies are older than it. The forall is there
+ // to deal with the fact that a) Some results might have been orphaned
+ // and b) Some files might not need changing.
+ direct(file) ||= products.forall(d => d.lastModified < file.lastModified)
+ }
+
+
+ val seen = new HashSet[File];
+ val indirect = new HashSet[File];
+ val newInvalidations = new HashSet[File];
+
+ def invalid(file : File) = indirect(file) || direct(file);
+
+ def go(i : Int) : Unit = if(i > 0){
+ newInvalidations.clear;
+ for((target, depends) <- dependencies;
+ if !invalid(target);
+ d <- depends){
+ newInvalidations(target) ||= invalid(d)
+ }
+ indirect ++= newInvalidations;
+ if(!newInvalidations.isEmpty) go(i - 1);
+ else ()
+ }
+
+ go(maxDepth)
+
+ indirect --= direct
+
+ for ((source, targets) <- targets; if (invalid(source))){
+ targets.foreach(_.rm);
+ targets -= source;
+ }
+
+ (direct, indirect);
+ }
+
+ def writeTo(file : File) : Unit = file.writeTo(out => writeTo(new PrintStream(out)));
+ def writeTo(print : PrintStream) : Unit = {
+ cleanEmpty();
+ def emit(tracker : Tracker){
+ for ((f, ds) <- tracker;
+ d <- ds){
+ print.println(f + " -> " + d);
+ }
+ }
+
+ print.println(classpath);
+ print.println(FileDependencies.Separator)
+ emit(dependencies);
+ print.println(FileDependencies.Separator)
+ emit(targets);
+
+ }
+ }
+
+ object FileDependencies{
+ val Separator = "-------";
+
+ def readFrom(file : File) = file.readFrom(in => {
+ val reader = new BufferedReader(new InputStreamReader(in));
+ val it = new FileDependencies(reader.readLine);
+ reader.readLine;
+ var line : String = null;
+ while ({line = reader.readLine; (line != null) && (line != Separator)}){
+ line.split(" -> ") match {
+ case Array(from, on) => it.depends(from, on);
+ case x => error("Parse error: Unrecognised string " + line);
+ };
+ }
+
+ while ({line = reader.readLine; (line != null) && (line != Separator)}){
+ line.split(" -> ") match {
+ case Array(source, target) => it.emits(source, target);
+ case x => error("Parse error: Unrecognised string " + line);
+ };
+ }
+
+ it;
+ })
+ }
+
+
+ def currentDirectory = new File(new JFile("."))
+
+ case class File private[Files](val underlying : JFile){
+ if (underlying == null) throw new NullPointerException();
+
+ def absolute : File = underlying.getAbsoluteFile;
+ def canonical : File = underlying.getCanonicalFile
+
+ def assertDirectory =
+ if (exists && !isDirectory) error(this + " is not a directory")
+ else this;
+
+ def assertExists =
+ if (!exists) error(this + " does not exist")
+ else this;
+
+ def lastModified = underlying.lastModified
+
+ def list : Iterable[File] =
+ assertExists.assertDirectory.underlying.listFiles.projection.map(toFile)
+
+ def / (file : File) : File =
+ new JFile(assertDirectory.toString,
+ file.toString)
+
+ override def toString = {
+ val it = underlying.getPath;
+ if (it.length == 0) "."
+ else it
+ }
+
+ def exists = underlying.exists
+ def isDirectory = underlying.isDirectory;
+
+ def parent : File = {
+ val x = underlying.getParentFile;
+ if (x == null) currentDirectory
+ else x
+ }
+
+ def create : Boolean = {parent.mkdir; underlying.createNewFile }
+ def mkdir : Boolean =
+ if (exists) { assertDirectory; false }
+ else {parent.mkdir; underlying.mkdir; }
+
+ def rm : Boolean = {
+ if (isDirectory) list.foreach(_.rm);
+ underlying.delete;
+ }
+
+ def descendants : Iterable[File] =
+ list.flatMap(x => if (x.isDirectory) x.descendants else List(x))
+
+ def extension = {
+ val name = toString;
+ val i = name.lastIndexOf('.');
+ if (i == -1) "" else name.substring(i + 1)
+ }
+
+ def writeTo[T](f : OutputStream => T) : T = {
+ val out = new FileOutputStream(underlying);
+ try {
+ f(out);
+ } finally {
+ out.close;
+ }
+ }
+
+ def readFrom[T](f : InputStream => T) : T = {
+ val in = new FileInputStream(underlying);
+ try{
+ f(in);
+ } finally {
+ in.close;
+ }
+ }
+ }
+
+}
+
+object Files extends Files;