summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/nsc/ScriptRunner.scala
diff options
context:
space:
mode:
authorPaul Phillips <paulp@improving.org>2009-08-18 18:11:24 +0000
committerPaul Phillips <paulp@improving.org>2009-08-18 18:11:24 +0000
commit1d28a77bf349e8e03a0eb53da554959c80864220 (patch)
tree42a4c13621da73f601d284300e1831e7bcd2f1b9 /src/compiler/scala/tools/nsc/ScriptRunner.scala
parent917101fd0de9580e1fd18b69778022f01cb6d29d (diff)
downloadscala-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.scala449
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
}
}