diff options
author | Paul Phillips <paulp@improving.org> | 2011-02-08 08:47:29 +0000 |
---|---|---|
committer | Paul Phillips <paulp@improving.org> | 2011-02-08 08:47:29 +0000 |
commit | 3467ad57e4c2b56cdd02ce0c75af4716b5631a10 (patch) | |
tree | af5d632276e79a449e9971e94c25bec0c1fc73e4 | |
parent | c89ea6e3ae82d9b6bacda25dceb74a958d2fa4f6 (diff) | |
download | scala-3467ad57e4c2b56cdd02ce0c75af4716b5631a10.tar.gz scala-3467ad57e4c2b56cdd02ce0c75af4716b5631a10.tar.bz2 scala-3467ad57e4c2b56cdd02ce0c75af4716b5631a10.zip |
Working on fsc.
for me anyway, with this commit scripts will occasionally reuse a
compiler instance, instead of never. Since any tests I write will fail
on platforms which aren't mine, there are no tests. I might have to
start a platform-specific testing area to break some ice around these
huge untested zones. No review.
9 files changed, 136 insertions, 134 deletions
diff --git a/src/compiler/scala/tools/nsc/CompileClient.scala b/src/compiler/scala/tools/nsc/CompileClient.scala index 41b96bfcdd..3102a2ae44 100644 --- a/src/compiler/scala/tools/nsc/CompileClient.scala +++ b/src/compiler/scala/tools/nsc/CompileClient.scala @@ -9,87 +9,44 @@ import java.io.{ BufferedReader, File, InputStreamReader, PrintWriter } import Properties.fileEndings import scala.tools.util.PathResolver import io.Path -import util.ClassPath +import settings.FscSettings /** The client part of the fsc offline compiler. Instead of compiling * things itself, it send requests to a CompileServer. */ -class StandardCompileClient extends CompileSocketShared { +class StandardCompileClient extends HasCompileSocket { lazy val compileSocket: CompileSocket = CompileSocket val versionMsg = "Fast " + Properties.versionMsg - var verbose = false - var version = false - var shutdown = false - - /** Convert a filename to an absolute path */ - def absFileName(path: String) = new File(path).getAbsolutePath() - - /** Convert a sequence of filenames, separated by <code>File.pathSeparator</code>, - * into absolute filenames. - */ - def absFileNames(paths: String) = ClassPath.map(paths, absFileName) - - protected def normalize(args: Array[String]): (String, String) = { - var i = 0 - val vmArgs = new StringBuilder - var serverAdr = "" - - while (i < args.length) { - val arg = args(i) - if (fileEndings exists(arg endsWith _)) { - args(i) = Path(arg).toAbsolute.path - } else if (arg startsWith "-J") { - //see http://java.sun.com/j2se/1.5.0/docs/tooldocs/solaris/javac.html#J - vmArgs append " "+arg.substring(2) - args(i) = "" - } else if (arg == "-verbose") { - verbose = true - } else if (arg == "-version") { - version = true - } else if (arg == "-shutdown") { - shutdown = true - } - i += 1 - - if (i < args.length) { - arg match { - case "-classpath" | "-sourcepath" | "-bootclasspath" | "-extdirs" | "-d" => - args(i) = PathResolver.makeAbsolute(args(i)) - i += 1 - case "-server" => - serverAdr = args(i) - args(i-1) = "" - args(i) = "" - case _ => - } - } - } - (vmArgs.toString, serverAdr) - } - - // used by class ant.FastScalac to skip exit statement in Ant. - def main0(args0: Array[String]): Int = { - val args = if (args0 contains "-d") args0 else Array("-d", ".") ++ args0 - val (vmArgs, serverAdr) = normalize(args) - - if (version) { + var verbose = false + + def main0(argsIn: Array[String]): Int = { + // TODO: put -J -and -D options back. Right now they are lost + // because bash parses them out and they don't arrive. + val (vmArgs, fscArgs) = (Nil, argsIn.toList) + val settings = new FscSettings + val command = new CompilerCommand(fscArgs, settings) + verbose = settings.verbose.value + val shutdown = settings.shutdown.value + + if (settings.version.value) { Console println versionMsg return 0 } if (verbose) { - Console println args.mkString("[Server arguments: ", " ", "]") - Console println "[VM arguments: %s]".format(vmArgs) + Console println fscArgs.mkString("[Given arguments: ", " ", "]") + Console println vmArgs.mkString("[VM arguments: ", " ", "]") } val socket = - if (serverAdr == "") compileSocket.getOrCreateSocket(vmArgs, !shutdown) - else Some(compileSocket.getSocket(serverAdr)) + if (settings.server.value == "") compileSocket.getOrCreateSocket(vmArgs mkString " ", !shutdown) + else Some(compileSocket.getSocket(settings.server.value)) val success = socket match { - case Some(sock) => fscCompile(sock, args) + case Some(sock) => compileOnServer(sock, fscArgs) case _ => Console.println( - if (shutdown) "[No compilation server running.]" else "Compilation failed." + if (shutdown) "[No compilation server running.]" + else "Compilation failed." ) shutdown } @@ -98,6 +55,9 @@ class StandardCompileClient extends CompileSocketShared { } object CompileClient extends StandardCompileClient { - def main(args: Array[String]): Unit = - sys.exit(try main0(args) catch { case e: Exception => 1 }) + def main(args: Array[String]): Unit = sys exit { + try main0(args) + catch { case _: Exception => 1 } + } } + diff --git a/src/compiler/scala/tools/nsc/CompileServer.scala b/src/compiler/scala/tools/nsc/CompileServer.scala index 5296432519..e33545d3c9 100644 --- a/src/compiler/scala/tools/nsc/CompileServer.scala +++ b/src/compiler/scala/tools/nsc/CompileServer.scala @@ -6,11 +6,10 @@ package scala.tools.nsc import java.io.{ BufferedOutputStream, FileOutputStream, PrintStream, File => JFile } -import io.File - import scala.tools.nsc.reporters.{Reporter, ConsoleReporter} import scala.tools.nsc.util.FakePos //Position import scala.tools.util.SocketServer +import settings.FscSettings /** * The server part of the fsc offline compiler. It awaits compilation @@ -29,9 +28,9 @@ class StandardCompileServer extends SocketServer { val MaxCharge = 0.8 - var shutDown: Boolean = false - private var compiler: Global = null + var reporter: ConsoleReporter = _ + var shutdown = false private def exit(code: Int): Nothing = { System.err.close() @@ -42,8 +41,6 @@ class StandardCompileServer extends SocketServer { private val runtime = Runtime.getRuntime() import runtime.{ totalMemory, freeMemory, maxMemory } - var reporter: ConsoleReporter = _ - /** Create a new compiler instance */ def newGlobal(settings: Settings, reporter: Reporter) = new Global(settings, reporter) { @@ -69,11 +66,29 @@ class StandardCompileServer extends SocketServer { protected def newOfflineCompilerCommand(arguments: List[String], settings: Settings) = new OfflineCompilerCommand(arguments, settings) + /** Problematically, Settings are only considered equal if every setting + * is exactly equal. In fsc this immediately breaks down because the randomly + * chosen temporary outdirs differ between client and server. Among other + * things. Long term we could use a meaningful equality; short term I'm just + * ignoring options which I can see causing a new compiler instance every time + * and which do not interestingly influence compilation products. + */ + def unequalSettings(s1: Settings, s2: Settings): Set[Settings#Setting] = { + val ignoreSettings = Set("-d", "-encoding", "-verbose") + def trim (s: Settings): Set[Settings#Setting] = ( + s.userSetSettings.toSet[Settings#Setting] filterNot (ss => ignoreSettings exists (ss respondsTo _)) + ) + val ss1 = trim(s1) + val ss2 = trim(s2) + + (ss1 union ss2) -- (ss1 intersect ss2) + } + def session() { printMemoryStats() - val password = compileSocket getPassword port + val password = compileSocket getPassword port val guessedPassword = in.readLine() - val input = in.readLine() + val input = in.readLine() def fscError(msg: String): Unit = out println ( FakePos("fsc"), @@ -83,13 +98,17 @@ class StandardCompileServer extends SocketServer { return val args = input.split("\0", -1).toList - val command = newOfflineCompilerCommand(args, new Settings(fscError)) - - if (command.fscShutdown.value) { - shutDown = true + val settings = new FscSettings(fscError) + def logVerbose(msg: String) = + if (settings.verbose.value) + out println msg + + val command = newOfflineCompilerCommand(args, settings) + if (settings.shutdown.value) { + shutdown = true return out.println("[Compile server exited]") } - if (command.fscReset.value) { + if (settings.reset.value) { compiler = null return out.println("[Compile server was reset]") } @@ -98,45 +117,48 @@ class StandardCompileServer extends SocketServer { // disable prompts, so that compile server cannot block override def displayPrompt = () } + def isCompilerReusable: Boolean = { + if (compiler == null) { + logVerbose("[Creating compiler instance for compile server.]") + return false + } + val unequal = unequalSettings(command.settings, compiler.settings) + if (unequal.nonEmpty) { + logVerbose("[Replacing compiler with new instance because settings are unequal.]") + logVerbose("[Asymmetric settings: " + unequal.mkString(", ") + "]") + } + unequal.isEmpty + } if (command.shouldStopWithInfo) reporter.info(null, command.getInfoMessage(newGlobal(command.settings, reporter)), true) else if (command.files.isEmpty) reporter.info(null, command.usageMsg, true) else { - try { - if (compiler != null && command.settings == compiler.settings) { - compiler.settings = command.settings - compiler.reporter = reporter - } - else { - if (command.verbose) { - val reason = if (compiler == null) "compiler is null" else "settings not equal" - out.println("[Starting new compile server instance because %s]".format(reason)) - } - compiler = newGlobal(command.settings, reporter) - } - val c = compiler - val run = new c.Run() - run compile command.files + if (isCompilerReusable) { + compiler.settings = command.settings + compiler.reporter = reporter } + else { + compiler = newGlobal(command.settings, reporter) + } + val c = compiler + val run = new c.Run() + try run compile command.files catch { case ex @ FatalError(msg) => - if (command.debug) - ex.printStackTrace(out) - reporter.error(null, "fatal error: " + msg) compiler = null - case ex: Throwable => - ex.printStackTrace(out); - reporter.error(null, "fatal error (server aborted): " + ex.getMessage()) - shutDown = true + case ex => + shutdown = true + throw ex } } - reporter.printSummary() - if (isMemoryFullEnough) + if (isMemoryFullEnough) { + logVerbose("Nulling out compiler due to memory utilization.") compiler = null + } } } diff --git a/src/compiler/scala/tools/nsc/CompileSocket.scala b/src/compiler/scala/tools/nsc/CompileSocket.scala index 1e4b37b3bc..4f71a3da87 100644 --- a/src/compiler/scala/tools/nsc/CompileSocket.scala +++ b/src/compiler/scala/tools/nsc/CompileSocket.scala @@ -16,9 +16,9 @@ import scala.util.control.Exception.catching import scala.tools.util.StringOps.splitWhere import scala.sys.process._ -trait CompileSocketShared { +trait HasCompileSocket { def compileSocket: CompileSocket - def fscCompile(sock: Socket, args: Seq[String]): Boolean = { + def compileOnServer(sock: Socket, args: Seq[String]): Boolean = { var noErrors = true sock.applyReaderAndWriter { (in, out) => diff --git a/src/compiler/scala/tools/nsc/OfflineCompilerCommand.scala b/src/compiler/scala/tools/nsc/OfflineCompilerCommand.scala index c04ce45d6c..786bcb1eb5 100644 --- a/src/compiler/scala/tools/nsc/OfflineCompilerCommand.scala +++ b/src/compiler/scala/tools/nsc/OfflineCompilerCommand.scala @@ -9,19 +9,6 @@ package scala.tools.nsc * * @author Martin Odersky and Lex Spoon */ -class OfflineCompilerCommand(arguments: List[String], _settings: Settings) - extends CompilerCommand(arguments, _settings) -{ +class OfflineCompilerCommand(arguments: List[String], settings: Settings) extends CompilerCommand(arguments, settings) { override def cmdName = "fsc" - import settings._ - - disable(prompt) - disable(resident) - - def verbose = settings.verbose.value - def debug = settings.debug.value - - val fscReset = BooleanSetting("-reset", "Reset compile server caches") - val fscShutdown = BooleanSetting("-shutdown", "Shutdown compile server") - val fscServer = StringSetting ("-server", "hostname:portnumber", "Specify compile server socket", "") } diff --git a/src/compiler/scala/tools/nsc/ScriptRunner.scala b/src/compiler/scala/tools/nsc/ScriptRunner.scala index fcfb827089..569dcfa01e 100644 --- a/src/compiler/scala/tools/nsc/ScriptRunner.scala +++ b/src/compiler/scala/tools/nsc/ScriptRunner.scala @@ -47,7 +47,7 @@ import util.Exceptional.unwrap * @todo It would be better if error output went to stderr instead * of stdout... */ -class ScriptRunner extends CompileSocketShared { +class ScriptRunner extends HasCompileSocket { lazy val compileSocket = CompileSocket /* While I'm chasing down the fsc and script bugs. */ @@ -135,7 +135,7 @@ class ScriptRunner extends CompileSocketShared { val compArgs = coreCompArgs ++ List("-Xscript", scriptMain(settings), scriptFile) CompileSocket getOrCreateSocket "" match { - case Some(sock) => fscCompile(sock, compArgs) + case Some(sock) => compileOnServer(sock, compArgs) case _ => false } } @@ -276,4 +276,4 @@ class ScriptRunner extends CompileSocketShared { } } -object ScriptRunner extends ScriptRunner { }
\ No newline at end of file +object ScriptRunner extends ScriptRunner { } diff --git a/src/compiler/scala/tools/nsc/settings/AbsSettings.scala b/src/compiler/scala/tools/nsc/settings/AbsSettings.scala index f09bc3edd1..a797cefef3 100644 --- a/src/compiler/scala/tools/nsc/settings/AbsSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/AbsSettings.scala @@ -34,7 +34,7 @@ trait AbsSettings { // two AbsSettings objects are equal if their visible settings are equal. override def hashCode() = visibleSettings.hashCode override def equals(that: Any) = that match { - case s: AbsSettings => this.visibleSettings == s.visibleSettings + case s: AbsSettings => this.userSetSettings == s.userSetSettings case _ => false } override def toString() = "Settings {\n%s}\n" format (userSetSettings map (" " + _ + "\n") mkString) diff --git a/src/compiler/scala/tools/nsc/settings/FscSettings.scala b/src/compiler/scala/tools/nsc/settings/FscSettings.scala new file mode 100644 index 0000000000..b7f0c553f6 --- /dev/null +++ b/src/compiler/scala/tools/nsc/settings/FscSettings.scala @@ -0,0 +1,29 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2011 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools +package nsc +package settings + +import util.ClassPath + +class FscSettings(error: String => Unit) extends Settings(error) { + outer => + + def this() = this(Console.println) + + val reset = BooleanSetting("-reset", "Reset compile server caches") + val shutdown = BooleanSetting("-shutdown", "Shutdown compile server") + val server = StringSetting ("-server", "hostname:portnumber", "Specify compile server socket", "") + + disable(prompt) + disable(resident) + + // Make all paths absolute since we may be going from client to server + userSetSettings foreach { + case x: PathSetting => x.value = ClassPath.makeAbsolute(x.value) + case _ => () + } +} diff --git a/src/compiler/scala/tools/nsc/util/ClassPath.scala b/src/compiler/scala/tools/nsc/util/ClassPath.scala index 0a8c54f297..d64eb78cc5 100644 --- a/src/compiler/scala/tools/nsc/util/ClassPath.scala +++ b/src/compiler/scala/tools/nsc/util/ClassPath.scala @@ -79,6 +79,9 @@ object ClassPath { /** Split the classpath and map them into Paths */ def toPaths(cp: String): List[Path] = split(cp) map (x => Path(x).toAbsolute) + /** Make all classpath components absolute. */ + def makeAbsolute(cp: String): String = fromPaths(toPaths(cp): _*) + /** Join the paths as a classpath */ def fromPaths(paths: Path*): String = join(paths map (_.path): _*) diff --git a/src/compiler/scala/tools/util/SocketServer.scala b/src/compiler/scala/tools/util/SocketServer.scala index 5f5cb28c4c..57ebf96656 100644 --- a/src/compiler/scala/tools/util/SocketServer.scala +++ b/src/compiler/scala/tools/util/SocketServer.scala @@ -32,14 +32,14 @@ abstract class SocketServer { // therefore unable to contact this server instance, // the process will just eventually terminate by itself. def fscIdleMinutes = { - sys.props("scala.fsc.idle.minutes") match { + sys.props("scala.config.fsc.idle-minutes") match { case null => 30 case str => try str.toInt catch { case _: Exception => 30 } } } def fscIdleMillis = fscIdleMinutes * 60 * 1000 - def shutDown: Boolean + def shutdown: Boolean def session() var out: PrintWriter = _ @@ -68,14 +68,15 @@ abstract class SocketServer { // @todo: this is going to be a prime candidate for ARM def doSession(clientSocket: Socket) = { out = new PrintWriter(clientSocket.getOutputStream(), true) - in = bufferedReader(clientSocket) + in = bufferedReader(clientSocket) val bufout = bufferedOutput(clientSocket) - scala.Console.withOut(bufout) { session() } - - bufout.close() - out.close() - in.close() + try scala.Console.withOut(bufout)(session()) + finally { + bufout.close() + out.close() + in.close() + } } def run() { @@ -86,12 +87,12 @@ abstract class SocketServer { } try { - while (!shutDown) { + while (!shutdown) { val clientSocket = try serverSocket.accept() catch { case e: IOException => fail("Accept on port %d failed; exiting.") } - doSession(clientSocket) - clientSocket.close() + try doSession(clientSocket) + finally clientSocket.close() } } catch { |