summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools
diff options
context:
space:
mode:
authorMartin Odersky <odersky@gmail.com>2006-06-06 12:10:18 +0000
committerMartin Odersky <odersky@gmail.com>2006-06-06 12:10:18 +0000
commit3be616edcfa5beaa3fd8fc632f25b340c578bbdc (patch)
treeb43fbb2280a0b3876aa679e2e816537734af9e60 /src/compiler/scala/tools
parent31adfc6cf4b34273a4283765dd1f865670bf9c10 (diff)
downloadscala-3be616edcfa5beaa3fd8fc632f25b340c578bbdc.tar.gz
scala-3be616edcfa5beaa3fd8fc632f25b340c578bbdc.tar.bz2
scala-3be616edcfa5beaa3fd8fc632f25b340c578bbdc.zip
added scala compile server
Diffstat (limited to 'src/compiler/scala/tools')
-rwxr-xr-xsrc/compiler/scala/tools/nsc/CompileClient.scala75
-rwxr-xr-xsrc/compiler/scala/tools/nsc/CompileServer.scala159
-rwxr-xr-xsrc/compiler/scala/tools/nsc/CompileSocket.scala148
-rw-r--r--src/compiler/scala/tools/nsc/Global.scala9
-rw-r--r--src/compiler/scala/tools/nsc/Settings.scala5
-rw-r--r--src/compiler/scala/tools/nsc/symtab/SymbolTable.scala11
-rw-r--r--src/compiler/scala/tools/nsc/symtab/Types.scala5
-rwxr-xr-xsrc/compiler/scala/tools/util/SocketConnection.scala37
-rwxr-xr-xsrc/compiler/scala/tools/util/SocketServer.scala54
-rwxr-xr-xsrc/compiler/scala/tools/util/StringOps.scala19
10 files changed, 511 insertions, 11 deletions
diff --git a/src/compiler/scala/tools/nsc/CompileClient.scala b/src/compiler/scala/tools/nsc/CompileClient.scala
new file mode 100755
index 0000000000..fb47fbf90a
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/CompileClient.scala
@@ -0,0 +1,75 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2006 LAMP/EPFL
+ * @author Martin Odersky
+ */
+// $Id: Main.scala 7679 2006-06-02 14:36:18 +0000 (Fri, 02 Jun 2006) odersky $
+package scala.tools.nsc
+
+import scala.tools.util.StringOps
+import java.io._
+
+/** The main class for NSC, a compiler for the programming
+ * language Scala.
+ */
+object CompileClient {
+
+ def normalize(args: Array[String]): Pair[String, String] = {
+ def absFileName(path: String) = new File(path).getAbsolutePath()
+ def absFileNames(paths: String) = {
+ def afns(sep: char): String =
+ StringOps.decompose(paths, sep)
+ .map(absFileName)
+ .mkString("", String.valueOf(sep), "")
+ if (paths.indexOf(';') > 0) afns(';')
+ else if (paths.indexOf(':') > 0) afns(':')
+ else absFileName(paths)
+ }
+ var i = 0
+ val vmArgs = new StringBuffer
+ var serverAdr = ""
+ while (i < args.length) {
+ val arg = args(i)
+ if (arg endsWith ".scala") {
+ args(i) = absFileName(arg)
+ } else if (arg startsWith "-J") {
+ vmArgs append " -"+arg.substring(2)
+ args(i) = ""
+ }
+ i = i + 1
+ if (i < args.length) {
+ if (arg == "-classpath" ||
+ arg == "-sourcepath" ||
+ arg == "-bootclasspath" ||
+ arg == "-extdirs" ||
+ arg == "-d") {
+ args(i) = absFileNames(args(i))
+ i = i + 1
+ } else if (arg == "-server") {
+ serverAdr = args(i)
+ args(i-1) = ""
+ args(i) = ""
+ }
+ }
+ }
+ Pair(vmArgs.toString, serverAdr)
+ }
+
+ def main(args: Array[String]): unit = {
+ val Pair(vmArgs, serverAdr) = normalize(args)
+ if (args exists ("-verbose" ==))
+ System.out.println("[Server arguments: "+args.mkString("", " ", "]"))
+ val socket = if (serverAdr == "") CompileSocket.getOrCreateSocket(vmArgs)
+ else CompileSocket.getSocket(serverAdr)
+ val out = new PrintWriter(socket.getOutputStream(), true)
+ val in = new BufferedReader(new InputStreamReader(socket.getInputStream()))
+ out.println(args.mkString("", " ", ""))
+ var fromServer = in.readLine()
+ while (fromServer != null) {
+ System.out.println(fromServer)
+ fromServer = in.readLine()
+ }
+ in.close()
+ out.close()
+ socket.close()
+ }
+}
diff --git a/src/compiler/scala/tools/nsc/CompileServer.scala b/src/compiler/scala/tools/nsc/CompileServer.scala
new file mode 100755
index 0000000000..3f058d7392
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/CompileServer.scala
@@ -0,0 +1,159 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2006 LAMP/EPFL
+ * @author Martin Odersky
+ */
+// $Id: Main.scala 7679 2006-06-02 14:36:18 +0000 (Fri, 02 Jun 2006) odersky $
+package scala.tools.nsc
+
+import scala.tools.util.{SocketServer, StringOps}
+import scala.tools.nsc.util.Position
+import scala.tools.nsc.reporters.{Reporter, ConsoleReporter}
+import scala.tools.nsc.doc.DocGenerator
+import scala.concurrent.Process.spawn
+import java.io._
+
+/** The main class for NSC, a compiler for the programming
+ * language Scala.
+ */
+object CompileServer extends SocketServer {
+
+ val PRODUCT: String =
+ System.getProperty("scala.tool.name", "scalac")
+ val VERSION: String =
+ System.getProperty("scala.tool.version", "unknown version")
+ val COPYRIGHT: String =
+ System.getProperty("scala.copyright", "(c) 2002-2006 LAMP/EPFL")
+ val versionMsg = PRODUCT + " " + VERSION + " -- " + COPYRIGHT
+
+ val MaxCharge = 0.8
+
+ var compiler: Global = null
+ var shutDown: boolean = false
+
+ private def settingsAreCompatible(s1: Settings, s2: Settings) =
+ s1.encoding.value == s2.encoding.value &&
+ s1.classpath.value == s2.classpath.value &&
+ s1.sourcepath.value == s2.sourcepath.value &&
+ s1.outdir.value == s2.outdir.value &&
+ s1.bootclasspath.value == s2.bootclasspath.value &&
+ s1.extdirs.value == s2.extdirs.value
+
+ private def exit(code: int): Nothing = {
+ System.err.close()
+ System.out.close()
+ Predef.exit(code)
+ }
+
+ private def spawnWatchDog(): unit = spawn {
+ try {
+ while (true) {
+ Thread.sleep(10000)
+ if (!CompileSocket.portFile(port).exists()) {
+ System.err.println("port file no longer exists; exiting")
+ exit(1)
+ }
+ }
+ } catch {
+ case ex: Throwable =>
+ ex.printStackTrace()
+ System.err.println("exiting")
+ exit(1)
+ }
+ }
+
+ private val runtime = Runtime.getRuntime()
+
+ def session(): unit = {
+ System.out.println("New session, total memory = "+runtime.totalMemory()+
+ ", max memory = "+runtime.maxMemory()+
+ ", free memory = "+runtime.freeMemory)
+ val input = in.readLine()
+ if (input != null) {
+ val args = StringOps.words(input)
+ if (args contains "-shutdown") {
+ out.println("[Scala compile server exited]")
+ shutDown = true
+ return
+ }
+ if (args contains "-reset") {
+ out.println("[Scala compile server was reset]")
+ compiler = null
+ }
+ val reporter = new ConsoleReporter(in, out) {
+ // disable prompts, so that compile server cannot block
+ override def displayPrompt = {}
+ }
+ def error(msg: String): unit =
+ reporter.error(new Position(PRODUCT),
+ msg + "\n " + PRODUCT + " -help gives more information")
+ val command = new CompilerCommand(args, error, false) {
+ override val cmdName = "fsc"
+ settings.disable(settings.prompt)
+ settings.disable(settings.resident)
+ new settings.BooleanSetting("-reset", "Reset compile server caches")
+ new settings.BooleanSetting("-shutdown", "Shutdown compile server")
+ new settings.StringSetting("-server", "hostname:portnumber",
+ "Specify compile server socket", "")
+ new settings.BooleanSetting("-J<flag>", "Pass <flag> directly to runtime system")
+ }
+
+ reporter.prompt = command.settings.prompt.value;
+ if (command.settings.version.value)
+ reporter.info(null, versionMsg, true)
+ else if (command.settings.help.value)
+ reporter.info(null, command.usageMsg, true)
+ else if (command.files.isEmpty)
+ reporter.info(null, command.usageMsg, true)
+ else {
+ try {scala.tools.nsc.CompileServer
+ if (compiler != null && settingsAreCompatible(command.settings, compiler.settings)) {
+ compiler.settings = command.settings
+ compiler.reporter = reporter
+ } else {
+ if (args exists ("-verbose" ==))
+ out.println("[Starting new Scala compile server instance]")
+ compiler = new Global(command.settings, reporter) {
+ override def inform(msg: String) = out.println(msg)
+ }
+ }
+ val c = compiler
+ val run = new c.Run
+ run compile command.files
+ } catch {
+ case ex @ FatalError(msg) =>
+ if (command.settings.debug.value)
+ 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
+ }
+ reporter.printSummary()
+ runtime.gc()
+ if ((runtime.totalMemory() - runtime.freeMemory()).toDouble /
+ runtime.maxMemory().toDouble > MaxCharge) compiler = null
+ }
+ }
+ }
+
+ def redirect(setter: PrintStream => unit, filename: String): unit =
+ setter(
+ new PrintStream(
+ new BufferedOutputStream(
+ new FileOutputStream(
+ new File(System.getProperty("java.io.tmpdir"), filename)))))
+
+ def main(args: Array[String]): unit = {
+ redirect(System.setOut, "scala-compile-server-out.log")
+ redirect(System.setErr, "scala-compile-server-err.log")
+ System.err.println("...starting server on socket "+port+"...")
+ System.err.flush()
+ spawnWatchDog()
+ CompileSocket.setPort(port)
+ run()
+ CompileSocket.deletePort(port)
+ exit(0)
+ }
+}
diff --git a/src/compiler/scala/tools/nsc/CompileSocket.scala b/src/compiler/scala/tools/nsc/CompileSocket.scala
new file mode 100755
index 0000000000..213c65e677
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/CompileSocket.scala
@@ -0,0 +1,148 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2006 LAMP/EPFL
+ * @author Martin Odersky
+ */
+// $Id: Main.scala 7679 2006-06-02 14:36:18 +0000 (Fri, 02 Jun 2006) odersky $
+package scala.tools.nsc
+
+import java.io._
+import java.net._
+
+object CompileSocket {
+
+ /** The prefix of the port identification file, which is followed by the port number */
+ private val dirName = "scalac-compile-server-port"
+
+ /** The vm-part of the command to start a new scala compile server */
+ private val vmCommand = "java"
+
+ /** The class name of the scala compile server */
+ private val serverClass = "scala.tools.nsc.CompileServer"
+
+ /** The temporary directory in which the port identification file is stored */
+ private val tmpDir = {
+ val d = new File(System.getProperty("java.io.tmpdir"), dirName)
+ if (!d.isDirectory())
+ if (!d.mkdir()) {
+ System.err.println("cannot create directory "+dirName+"; exiting")
+ exit(1)
+ }
+ d
+ }
+
+ /** Maximum number of polls for an available port */
+ private val MaxAttempts = 100
+
+ /** Time (in ms) to sleep between two polls */
+ private val sleepTime = 20
+
+ /** The command which starts the compile server, given vm arguments
+ * @param vmArgs the argument string to be passed to the java or scala command
+ * the string must be either empty or start with a ' '.
+ */
+ def serverCommand(vmArgs: String): String = vmCommand+vmArgs+" "+serverClass
+
+ /** The port identification file */
+ def portFile(port: int) = new File(tmpDir, port.toString())
+
+ /** Poll for a server port number; return -1 if none exists yet */
+ private def pollPort(): int = {
+ val hits = tmpDir.listFiles()
+ if (hits.length == 0) -1
+ else
+ try {
+ for (val i <- 1 until hits.length) hits(i).delete()
+ Integer.parseInt(hits(0).getName)
+ } catch {
+ case ex: Throwable =>
+ System.err.println(ex)
+ System.err.println("bad file in temp directory: "+hits(0).getAbsolutePath())
+ System.err.println("please remove the file and try again")
+ exit(1)
+ }
+ }
+
+ /** Get the port number to which a scala compile server is connected;
+ * If no server is running yet, create one
+ */
+ def getPort(vmArgs: String): int = {
+ var attempts = 0;
+ var port = pollPort()
+ if (port < 0) {
+ Runtime.getRuntime().exec(serverCommand(vmArgs))
+ }
+ while (port < 0 && attempts < MaxAttempts) {
+ attempts = attempts + 1
+ Thread.sleep(sleepTime)
+ port = pollPort()
+ }
+ if (port < 0) {
+ System.err.println("cannot start server with command")
+ System.err.println(serverCommand(vmArgs))
+ exit(1)
+ }
+ port
+ }
+
+ /** Set the port number to which a scala compile server is connected */
+ def setPort(port: int): unit =
+ try {
+ val f = new FileOutputStream(portFile(port));
+ f.close()
+ } catch {
+ case ex: IOException =>
+ System.err.println("cannot create file: "+portFile(port).getAbsolutePath()+"; exiting")
+ throw new Error()
+ }
+
+ /** Delete the port number to which a scala compile server was connected */
+ def deletePort(port: int): unit = portFile(port).delete()
+
+ def getOrCreateSocket(vmArgs: String): Socket = {
+ def getsock(attempts: int): Socket =
+ if (attempts == 0) {
+ System.err.println("unable to establish connection to server; exiting");
+ exit(1)
+ } else {
+ val port = getPort(vmArgs)
+ val hostName = InetAddress.getLocalHost().getHostName()
+ try {
+ new Socket(hostName, port)
+ } catch {
+ case e: IOException =>
+ System.err.println(e)
+ System.err.println("...connection attempt to server at port "+port+" failed; re-trying...")
+ getsock(attempts - 1)
+ }
+ }
+ getsock(3)
+ }
+
+ def getSocket(serverAdr: String): Socket = {
+ val cpos = serverAdr indexOf ':'
+ if (cpos < 0) {
+ System.err.println("malformed server address: "+serverAdr+"; exiting")
+ exit(1)
+ } else {
+ val hostName = serverAdr.substring(0, cpos)
+ val port = try {
+ Integer.parseInt(serverAdr.substring(cpos+1))
+ } catch {
+ case ex: Throwable =>
+ System.err.println("malformed server address: "+serverAdr+"; exiting")
+ exit(1)
+ }
+ getSocket(hostName, port)
+ }
+ }
+
+ def getSocket(hostName: String, port: int): Socket =
+ try {
+ new Socket(hostName, port)
+ } catch {
+ case e: IOException =>
+ System.err.println(
+ "unable to establish connection to server "+hostName+":"+port+"; exiting")
+ exit(1)
+ }
+}
diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala
index a0d537b8cf..e81b210e23 100644
--- a/src/compiler/scala/tools/nsc/Global.scala
+++ b/src/compiler/scala/tools/nsc/Global.scala
@@ -29,7 +29,7 @@ import backend.jvm.GenJVM
import backend.opt.{Inliners, ClosureElimination, DeadCodeElimination}
import backend.icode.analysis._
-class Global(val settings: Settings, val reporter: Reporter) extends SymbolTable
+class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable
with Trees
with CompilationUnits
{
@@ -163,7 +163,7 @@ class Global(val settings: Settings, val reporter: Reporter) extends SymbolTable
settings.extdirs.value)
if (settings.verbose.value) {
- System.err.println("classpath = " + classPath)
+ inform("[Classpath = " + classPath+"]")
}
def getSourceFile(f: AbstractFile): SourceFile =
@@ -500,7 +500,7 @@ class Global(val settings: Settings, val reporter: Reporter) extends SymbolTable
if (fileset == null) {
val msg = "No class file for " + file +
" was found\n(This file cannot be loaded as a source file)"
- System.err.println(msg)
+ inform(msg)
throw new FatalError(msg)
}
else if (!(fileset contains file)) {
@@ -552,8 +552,7 @@ class Global(val settings: Settings, val reporter: Reporter) extends SymbolTable
}
}
val sym = getSym(name, module)
- System.err.println("" + sym.name + ":" +
- (if (module) sym.tpe.symbol.info else sym.info))
+ inform("" + sym.name + ":" +(if (module) sym.tpe.symbol.info else sym.info))
}
/** Returns the file with the given suffix for the given class. */
diff --git a/src/compiler/scala/tools/nsc/Settings.scala b/src/compiler/scala/tools/nsc/Settings.scala
index 8721d377e0..a75002249c 100644
--- a/src/compiler/scala/tools/nsc/Settings.scala
+++ b/src/compiler/scala/tools/nsc/Settings.scala
@@ -123,6 +123,11 @@ class Settings(error: String => unit) {
/** A list of all settings */
def allSettings: List[Setting] = allsettings.reverse
+ /** Disable a setting */
+ def disable(s: Setting) = {
+ allsettings = allsettings filter (s !=)
+ }
+
/** A base class for settings of all types.
* Subclasses each define a `value' field of the appropriate type.
*/
diff --git a/src/compiler/scala/tools/nsc/symtab/SymbolTable.scala b/src/compiler/scala/tools/nsc/symtab/SymbolTable.scala
index eb9fb486e5..8556e54cef 100644
--- a/src/compiler/scala/tools/nsc/symtab/SymbolTable.scala
+++ b/src/compiler/scala/tools/nsc/symtab/SymbolTable.scala
@@ -35,7 +35,7 @@ abstract class SymbolTable extends Names
final val NoRunId = 0
private var ph: Phase = NoPhase
- private var period = NoPeriod
+ private var per = NoPeriod
def phase: Phase = ph
@@ -43,7 +43,7 @@ abstract class SymbolTable extends Names
//System.out.println("setting phase to " + p)
assert(p != null && p != NoPhase)
ph = p
- period = (currentRunId << 8) + p.id
+ per = (currentRunId << 8) + p.id
}
/** The current compiler run identifier. */
@@ -57,10 +57,13 @@ abstract class SymbolTable extends Names
/** The current period */
def currentPeriod: Period = {
- //assert(period == (currentRunId << 8) + phase.id)
- period
+ //assert(per == (currentRunId << 8) + phase.id)
+ per
}
+ final def period(rid: RunId, pid: Phase#Id): Period =
+ (currentRunId << 8) + pid
+
/** Perform given operation at given phase */
def atPhase[T](ph: Phase)(op: => T): T = {
val current = phase
diff --git a/src/compiler/scala/tools/nsc/symtab/Types.scala b/src/compiler/scala/tools/nsc/symtab/Types.scala
index bd36f74ccb..07026d5fdb 100644
--- a/src/compiler/scala/tools/nsc/symtab/Types.scala
+++ b/src/compiler/scala/tools/nsc/symtab/Types.scala
@@ -335,10 +335,11 @@ trait Types requires SymbolTable {
/** If this is a lazy type, assign a new type to `sym'. */
def complete(sym: Symbol): unit = {
- if (sym == NoSymbol || sym.isPackageClass) sym.validTo = currentPeriod
+ if (sym == NoSymbol || sym.isPackageClass)
+ sym.validTo = period(currentRunId, phaseId(sym.validTo))
else {
val this1 = adaptToNewRunMap(this)
- if (this1 eq this) sym.validTo = currentPeriod
+ if (this1 eq this) sym.validTo = period(currentRunId, phaseId(sym.validTo))
else {
//System.out.println("new type of " + sym + "=" + this1);//DEBUG
sym.setInfo(this1)
diff --git a/src/compiler/scala/tools/util/SocketConnection.scala b/src/compiler/scala/tools/util/SocketConnection.scala
new file mode 100755
index 0000000000..531caaf30d
--- /dev/null
+++ b/src/compiler/scala/tools/util/SocketConnection.scala
@@ -0,0 +1,37 @@
+package scala.tools.util
+
+import java.io._
+import java.net._
+
+class SocketConnection(hostname: String, port: int) {
+
+ def this(port: int) = this(InetAddress.getLocalHost().getHostName(), port)
+
+ private var socket: Socket = _
+ var out: PrintWriter = _
+ var in: BufferedReader = _
+ var errorMessage: String = _
+
+ def open(): boolean = {
+ try {
+ socket = new Socket(hostname, port)
+ out = new PrintWriter(socket.getOutputStream(), true)
+ in = new BufferedReader(new InputStreamReader(socket.getInputStream()))
+ true
+ } catch {
+ case e: UnknownHostException =>
+ errorMessage = "Don't know about host: "+hostname+"."
+ false
+ case e: IOException =>
+ errorMessage = "Couldn't get I/O for the connection to: "+hostname+"."
+ false
+ }
+ }
+
+ def close() = {
+ in.close()
+ out.close()
+ socket.close()
+ }
+}
+
diff --git a/src/compiler/scala/tools/util/SocketServer.scala b/src/compiler/scala/tools/util/SocketServer.scala
new file mode 100755
index 0000000000..7a65290dcc
--- /dev/null
+++ b/src/compiler/scala/tools/util/SocketServer.scala
@@ -0,0 +1,54 @@
+package scala.tools.util
+
+import java.net._
+import java.io._
+
+abstract class SocketServer {
+
+ def shutDown: boolean
+ def session(): unit
+
+ var out: PrintWriter = _
+ var in: BufferedReader = _
+
+ val port: int = try {
+ val s = new ServerSocket(0)
+ val p = s.getLocalPort()
+ s.close()
+ p
+ } catch {
+ case e: IOException =>
+ System.err.println("Could not listen on any port; exiting.")
+ exit(1)
+ }
+
+ def run(): unit = {
+ while (!shutDown) {
+ val serverSocket = try {
+ new ServerSocket(port)
+ } catch {
+ case e: IOException =>
+ System.err.println("Could not listen on port: "+port+"; exiting.")
+ exit(1)
+ }
+ val clientSocket = try {
+ serverSocket.accept()
+ } catch {
+ case e: IOException =>
+ System.err.println("Accept on port "+port+" failed; exiting.")
+ exit(1)
+ }
+
+ out = new PrintWriter(clientSocket.getOutputStream(), true)
+ in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()))
+
+ session()
+
+ out.close()
+ in.close()
+ clientSocket.close()
+ serverSocket.close()
+ }
+ }
+}
+
diff --git a/src/compiler/scala/tools/util/StringOps.scala b/src/compiler/scala/tools/util/StringOps.scala
new file mode 100755
index 0000000000..0c2333ca22
--- /dev/null
+++ b/src/compiler/scala/tools/util/StringOps.scala
@@ -0,0 +1,19 @@
+package scala.tools.util
+
+object StringOps {
+
+ def decompose(str: String, sep: char): List[String] = {
+ def ws(start: int): List[String] =
+ if (start == str.length) List()
+ else if (str.charAt(start) == sep) ws(start + 1)
+ else {
+ val end = str.indexOf(sep, start)
+ if (end < 0) List(str.substring(start))
+ else str.substring(start, end) :: ws(end + 1)
+ }
+ ws(0)
+ }
+
+ def words(str: String): List[String] = decompose(str, ' ')
+}
+