diff options
author | Paul Phillips <paulp@improving.org> | 2009-08-18 18:11:24 +0000 |
---|---|---|
committer | Paul Phillips <paulp@improving.org> | 2009-08-18 18:11:24 +0000 |
commit | 1d28a77bf349e8e03a0eb53da554959c80864220 (patch) | |
tree | 42a4c13621da73f601d284300e1831e7bcd2f1b9 /src/compiler/scala/tools/nsc/ScriptRunner.scala | |
parent | 917101fd0de9580e1fd18b69778022f01cb6d29d (diff) | |
download | scala-1d28a77bf349e8e03a0eb53da554959c80864220.tar.gz scala-1d28a77bf349e8e03a0eb53da554959c80864220.tar.bz2 scala-1d28a77bf349e8e03a0eb53da554959c80864220.zip |
A bunch of cleanup on scriptrunner and fsc perf...
A bunch of cleanup on scriptrunner and fsc performed in a quest to fix
#1889. I understand why #1889 happens now but I believe fixing it is
going to require adjusting the logic in SymbolLoaders.
Diffstat (limited to 'src/compiler/scala/tools/nsc/ScriptRunner.scala')
-rw-r--r-- | src/compiler/scala/tools/nsc/ScriptRunner.scala | 449 |
1 files changed, 194 insertions, 255 deletions
diff --git a/src/compiler/scala/tools/nsc/ScriptRunner.scala b/src/compiler/scala/tools/nsc/ScriptRunner.scala index e31dcd38f9..0a7b0aeeec 100644 --- a/src/compiler/scala/tools/nsc/ScriptRunner.scala +++ b/src/compiler/scala/tools/nsc/ScriptRunner.scala @@ -6,12 +6,19 @@ package scala.tools.nsc -import java.io.{BufferedReader, File, FileInputStream, FileOutputStream, - FileReader, InputStreamReader, PrintWriter, - FileWriter, IOException} +import java.io.{ + InputStream, OutputStream, + BufferedReader, FileInputStream, FileOutputStream, + FileReader, InputStreamReader, PrintWriter, FileWriter, + IOException +} +import scala.io.File +// import scala.io.arm.ManagedResource +import java.io.{ File => JFile } import java.lang.reflect.InvocationTargetException import java.net.URL -import java.util.jar.{JarEntry, JarOutputStream} +import java.util.jar.{ JarEntry, JarOutputStream } +import java.util.regex.Pattern import scala.tools.nsc.io.PlainFile import scala.tools.nsc.reporters.{Reporter,ConsoleReporter} @@ -43,136 +50,123 @@ import scala.tools.nsc.util.{ClassPath, CompoundSourceFile, BatchSourceFile, Sou * @todo It would be better if error output went to stderr instead * of stdout... */ -object ScriptRunner { +object ScriptRunner +{ + /* While I'm chasing down the fsc and script bugs. */ + def DBG(msg: Any) { + System.err.println(msg.toString) + System.err.flush() + } + /** Default name to use for the wrapped script */ val defaultScriptMain = "Main" + private def addShutdownHook(body: => Unit) = + Runtime.getRuntime addShutdownHook new Thread { override def run { body } } + /** Pick a main object name from the specified settings */ - def scriptMain(settings: Settings) = - if (settings.script.value == "") - defaultScriptMain - else - settings.script.value - - /** Choose a jar filename to hold the compiled version - * of a script - */ - private def jarFileFor(scriptFile: String): File = { - val filename = - if (scriptFile.matches(".*\\.[^.\\\\/]*")) - scriptFile.replaceFirst("\\.[^.\\\\/]*$", ".jar") - else - scriptFile + ".jar" - - new File(filename) + def scriptMain(settings: Settings) = settings.script.value match { + case "" => defaultScriptMain + case x => x + } + + /** Choose a jar filename to hold the compiled version of a script. */ + private def jarFileFor(scriptFile: String): JFile = { + val name = + if (scriptFile endsWith ".jar") scriptFile + else scriptFile + ".jar" + + File(name).file + } + + def copyStreams(in: InputStream, out: OutputStream) = { + val buf = new Array[Byte](10240) + + def loop: Unit = in.read(buf, 0, buf.length) match { + case -1 => in.close() + case n => out.write(buf, 0, n) ; loop + } + + loop } /** Try to create a jar file out of all the contents * of the directory <code>sourcePath</code>. */ - private def tryMakeJar(jarFile: File, sourcePath: File) = { - try { - val jarFileStream = new FileOutputStream(jarFile) - val jar = new JarOutputStream(jarFileStream) - val buf = new Array[Byte](10240) - - def addFromDir(dir: File, prefix: String) { - for (entry <- dir.listFiles) { - if (entry.isFile) { - jar.putNextEntry(new JarEntry(prefix + entry.getName)) - - val input = new FileInputStream(entry) - var n = input.read(buf, 0, buf.length) - while (n >= 0) { - jar.write (buf, 0, n) - n = input.read(buf, 0, buf.length) - } - jar.closeEntry - input.close - } else { - addFromDir(entry, prefix + entry.getName + "/") - } - } + private def tryMakeJar(jarFile: JFile, sourcePath: JFile) = { + def addFromDir(jar: JarOutputStream, dir: JFile, prefix: String) { + def addFileToJar(entry: JFile) = { + jar putNextEntry new JarEntry(prefix + entry.getName) + copyStreams(new FileInputStream(entry), jar) + jar.closeEntry } - addFromDir(sourcePath, "") + dir.listFiles foreach { entry => + if (entry.isFile) addFileToJar(entry) + else addFromDir(jar, entry, prefix + entry.getName + "/") + } + } + + try { + val jar = new JarOutputStream(File(jarFile).outputStream()) + addFromDir(jar, sourcePath, "") jar.close - } catch { - case _:Error => jarFile.delete // XXX what errors to catch? + } + catch { + case _: Error => jarFile.delete() // XXX what errors to catch? } } - /** Read the entire contents of a file as a String. */ - private def contentsOfFile(filename: String): String = { - val strbuf = new StringBuilder - val reader = new FileReader(filename) - val cbuf = new Array[Char](1024) - while(true) { - val n = reader.read(cbuf) - if (n <= 0) - return strbuf.toString - strbuf.append(cbuf, 0, n) - } - throw new Error("impossible") - } + private def contentsOfFile(filename: String) = File(filename).toSource().mkString /** Find the length of the header in the specified file, if * there is one. The header part starts with "#!" or "::#!" * and ends with a line that begins with "!#" or "::!#". */ private def headerLength(filename: String): Int = { - import java.util.regex._ - + val headerPattern = Pattern.compile("""^(::)?!#.*(\r|\n|\r\n)""", Pattern.MULTILINE) val fileContents = contentsOfFile(filename) + def isValid = List("#!", "::#!") exists (fileContents startsWith _) - if (!(fileContents.startsWith("#!") || fileContents.startsWith("::#!"))) - return 0 - - val matcher = - (Pattern.compile("^(::)?!#.*(\\r|\\n|\\r\\n)", Pattern.MULTILINE) - .matcher(fileContents)) - if (!matcher.find) - throw new IOException("script file does not close its header with !# or ::!#") - return matcher.end + if (!isValid) 0 else { + val matcher = headerPattern matcher fileContents + if (matcher.find) matcher.end + else throw new IOException("script file does not close its header with !# or ::!#") + } } /** Split a fully qualified object name into a * package and an unqualified object name */ - private def splitObjectName(fullname: String): - (Option[String],String) = - { - val idx = fullname.lastIndexOf('.') - if (idx < 0) - (None, fullname) - else - (Some(fullname.substring(0,idx)), fullname.substring(idx+1)) - } + private def splitObjectName(fullname: String): (Option[String], String) = + (fullname lastIndexOf '.') match { + case -1 => (None, fullname) + case idx => (Some(fullname take idx), fullname drop (idx + 1)) + } /** Code that is added to the beginning of a script file to make * it a complete Scala compilation unit. */ - protected def preambleCode(objectName: String) = { - val (maybePack, objName) = splitObjectName(objectName) - - val packageDecl = - maybePack match { - case Some(pack) => "package " + pack + "\n" - case None => "" - } - - (packageDecl + - "object " + objName + " {\n" + - " def main(argv: Array[String]): Unit = {\n" + - " val args = argv;\n" + - " new AnyRef {\n") + protected def preambleCode(objectName: String): String = { + val (maybePack, objName) = splitObjectName(objectName) + val packageDecl = maybePack map ("package %s\n" format _) getOrElse ("") + + return """| + | object %s { + | def main(argv: Array[String]): Unit = { + | val args = argv + | new AnyRef { + |""".stripMargin.format(objName) } /** Code that is added to the end of a script file to make * it a complete Scala compilation unit. */ - val endCode = "\n} \n} }\n" - + val endCode = """ + | } + | } + | } + |""".stripMargin /** Wrap a script file into a runnable object named * <code>scala.scripting.Main</code>. @@ -180,20 +174,12 @@ object ScriptRunner { def wrappedScript( objectName: String, filename: String, - getSourceFile: PlainFile => SourceFile): SourceFile = + getSourceFile: PlainFile => BatchSourceFile): SourceFile = { - val preamble = - new BatchSourceFile("<script preamble>", - preambleCode(objectName).toCharArray) - + val preamble = new BatchSourceFile("<script preamble>", preambleCode(objectName).toCharArray) val middle = { - val f = new File(filename) - val bsf = getSourceFile(new PlainFile(f)).asInstanceOf[BatchSourceFile] - new SourceFileFragment( - bsf, - headerLength(filename), - bsf.length) -// f.length.asInstanceOf[Int]) + val bsf = getSourceFile(PlainFile fromPath filename) + new SourceFileFragment(bsf, headerLength(filename), bsf.length) } val end = new BatchSourceFile("<script trailer>", endCode.toCharArray) @@ -210,53 +196,42 @@ object ScriptRunner { settings: GenericRunnerSettings, scriptFileIn: String): Boolean = { - val scriptFile = CompileClient.absFileName(scriptFileIn) - for (setting <- List( - settings.classpath, - settings.sourcepath, - settings.bootclasspath, - settings.extdirs, - settings.outdir)) - setting.value = CompileClient.absFileNames(setting.value) - - val compSettingNames = - (new Settings(error)).allSettings.map(_.name) - - val compSettings = - settings.allSettings.filter(stg => - compSettingNames.contains(stg.name)) - - val coreCompArgs = - compSettings.foldLeft[List[String]](Nil)((args, stg) => - stg.unparse ::: args) - - val compArgs = - (coreCompArgs ::: - List("-Xscript", scriptMain(settings), scriptFile)) - - val socket = CompileSocket.getOrCreateSocket("") - if (socket eq null) - return false - - val out = new PrintWriter(socket.getOutputStream(), true) - val in = new BufferedReader(new InputStreamReader(socket.getInputStream())) + val scriptFile = CompileClient absFileName scriptFileIn - out.println(CompileSocket.getPassword(socket.getPort)) - out.println(compArgs.mkString("", "\0", "")) - - var compok = true - - var fromServer = in.readLine() - while (fromServer ne null) { - Console.err.println(fromServer) - if (CompileSocket.errorPattern.matcher(fromServer).matches) - compok = false + { + import settings._ + for (setting <- List(classpath, sourcepath, bootclasspath, extdirs, outdir)) { + // DBG("%s = %s".format(setting.name, setting.value)) + setting.value = CompileClient absFileName setting.value + } + } - fromServer = in.readLine() + val compSettingNames = new Settings(error).allSettings map (_.name) + val compSettings = settings.allSettings filter (compSettingNames contains _.name) + val coreCompArgs = compSettings flatMap (_.unparse) + val compArgs = coreCompArgs ::: List("-Xscript", scriptMain(settings), scriptFile) + var compok = true + + // XXX temporary as I started using ManagedResource not remembering it wasn't checked in. + def ManagedResource[T](x: => T) = Some(x) + + for { + socket <- ManagedResource(CompileSocket getOrCreateSocket "") + val _ = if (socket == null) return false + out <- ManagedResource(new PrintWriter(socket.getOutputStream(), true)) + in <- ManagedResource(new BufferedReader(new InputStreamReader(socket.getInputStream()))) + } { + out println (CompileSocket getPassword socket.getPort) + out println (compArgs mkString "\0") + + for (fromServer <- (Iterator continually in.readLine()) takeWhile (_ != null)) { + Console.err println fromServer + if (CompileSocket.errorPattern matcher fromServer matches) + compok = false + } + // XXX temp until managed resource is available + in.close() ; out.close() ; socket.close() } - in.close() - out.close() - socket.close() compok } @@ -269,101 +244,82 @@ object ScriptRunner { * * @returns true if compilation and the handler succeeds, false otherwise. */ - private def withCompiledScript - (settings: GenericRunnerSettings, scriptFile: String) - (handler: String => Boolean) - : Boolean = { + private def withCompiledScript( + settings: GenericRunnerSettings, + scriptFile: String) + (handler: String => Boolean): Boolean = + { import Interpreter.deleteRecursively - /** Compiles the script file, and returns - * the directory with the compiled class files, - * if the compilation succeeded. - */ - def compile: Option[File] = { - val compiledPath = File.createTempFile("scalascript", "") - compiledPath.delete // the file is created as a file; make it a directory - compiledPath.mkdirs + /** Compiles the script file, and returns the directory with the compiled + * class files, if the compilation succeeded. + */ + def compile: Option[JFile] = { + val compiledPath = File tempdir "scalascript" // delete the directory after the user code has finished - Runtime.getRuntime.addShutdownHook(new Thread { - override def run { deleteRecursively(compiledPath) }}) + addShutdownHook(deleteRecursively(compiledPath.file)) - settings.outdir.value = compiledPath.getPath + settings.outdir.value = compiledPath.path if (settings.nocompdaemon.value) { val reporter = new ConsoleReporter(settings) val compiler = newGlobal(settings, reporter) val cr = new compiler.Run - val wrapped = - wrappedScript( - scriptMain(settings), - scriptFile, - compiler.getSourceFile _) - cr.compileSources(List(wrapped)) - if (!reporter.hasErrors) - Some(compiledPath) - else - None - } else { - if (compileWithDaemon(settings, scriptFile)) - Some(compiledPath) - else - None + val wrapped = wrappedScript(scriptMain(settings), scriptFile, compiler getSourceFile _) + + cr compileSources List(wrapped) + if (reporter.hasErrors) None else Some(compiledPath.file) } + else if (compileWithDaemon(settings, scriptFile)) Some(compiledPath.file) + else None } if (settings.savecompiled.value) { - val jarFile = jarFileFor(scriptFile) - - def jarOK = (jarFile.canRead && - (jarFile.lastModified > new File(scriptFile).lastModified)) + val jarFile = File(jarFileFor(scriptFile)) + def jarOK = jarFile.canRead && (jarFile isFresher File(scriptFile)) - if (jarOK) { - // pre-compiled jar is current - handler(jarFile.getAbsolutePath) - } else { - // The pre-compiled jar is old. Recompile the script. + def recompile() = { jarFile.delete compile match { case Some(compiledPath) => - tryMakeJar(jarFile, compiledPath) + tryMakeJar(jarFile.file, compiledPath) if (jarOK) { - deleteRecursively(compiledPath) // may as well do it now - handler(jarFile.getAbsolutePath) - } else { - // jar failed; run directly from the class files - handler(compiledPath.getPath) + deleteRecursively(compiledPath) + handler(jarFile.absolutePath) } - case None => false + // jar failed; run directly from the class files + else handler(compiledPath.getPath) + case _ => false } } - } else { - // don't use a cache jar at all--just use the class files - compile match { - case Some(compiledPath) => handler(compiledPath.getPath) - case None => false - } + + if (jarOK) handler(jarFile.absolutePath) // pre-compiled jar is current + else recompile() // jar old - recompile the script. } + // don't use a cache jar at all--just use the class files + else compile map (cp => handler(cp.getPath)) getOrElse false } - /** Run a script after it has been compiled * * @returns true if execution succeeded, false otherwise */ - private def runCompiled(settings: GenericRunnerSettings, - compiledLocation: String, - scriptArgs: List[String]) : Boolean = { - def fileToURL(f: File): Option[URL] = - try { Some(f.toURL) } - catch { case e => Console.err.println(e); None } + private def runCompiled( + settings: GenericRunnerSettings, + compiledLocation: String, + scriptArgs: List[String]): Boolean = + { + def fileToURL(f: JFile): Option[URL] = + try Some(f.toURL) catch { case _: Exception => None } def paths(str: String, expandStar: Boolean): List[URL] = - for ( - file <- ClassPath.expandPath(str, expandStar) map (new File(_)) if file.exists; - val url = fileToURL(file); if !url.isEmpty - ) yield url.get + for { + file <- ClassPath.expandPath(str, expandStar) map (new JFile(_)) + if file.exists + url <- fileToURL(file) + } yield url val classpath = (paths(settings.bootclasspath.value, true) ::: @@ -376,64 +332,47 @@ object ScriptRunner { scriptMain(settings), scriptArgs.toArray) true - } catch { - case e: ClassNotFoundException => - Console.println(e) - false - case e: NoSuchMethodException => - Console.println(e) + } + catch { + case e @ (_: ClassNotFoundException | _: NoSuchMethodException) => + Console println e false - case e:InvocationTargetException => + case e: InvocationTargetException => e.getCause.printStackTrace false } } - /** Run a script file with the specified arguments and compilation * settings. * * @returns true if compilation and execution succeeded, false otherwise. */ - def runScript(settings: GenericRunnerSettings, + def runScript( + settings: GenericRunnerSettings, scriptFile: String, - scriptArgs: List[String]) : Boolean = { - val f = new File(scriptFile) - if (!f.isFile) { - throw new IOException("no such file: " + scriptFile) - } else { - try { - withCompiledScript(settings, scriptFile){compiledLocation => - runCompiled(settings, compiledLocation, scriptArgs) - } - } catch { - case e => throw e - } - } + scriptArgs: List[String]): Boolean = + { + if (File(scriptFile).isFile) + withCompiledScript(settings, scriptFile) { runCompiled(settings, _, scriptArgs) } + else + throw new IOException("no such file: " + scriptFile) } /** Run a command * * @returns true if compilation and execution succeeded, false otherwise. */ - def runCommand(settings: GenericRunnerSettings, - command: String, - scriptArgs: List[String]) : Boolean = { - val scriptFile = File.createTempFile("scalacmd", ".scala") - + def runCommand( + settings: GenericRunnerSettings, + command: String, + scriptArgs: List[String]) : Boolean = + { + val scriptFile = File.tempfile("scalacmd", ".scala") // save the command to the file - { - val str = new FileWriter(scriptFile) - str.write(command) - str.close() - } + scriptFile writeAll List(command) - try { - withCompiledScript(settings, scriptFile.getPath){compiledLocation => - runCompiled(settings, compiledLocation, scriptArgs) - } - } catch { - case e => throw e - } finally scriptFile.delete() // in case there was a compilation error + try withCompiledScript(settings, scriptFile.path) { runCompiled(settings, _, scriptArgs) } + finally scriptFile.delete() // in case there was a compilation error } } |