scala.tools.nsc.ScriptRunner.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of scala-compiler Show documentation
Show all versions of scala-compiler Show documentation
Compiler for the Scala Programming Language
The newest version!
/*
* Scala (https://www.scala-lang.org)
*
* Copyright EPFL and Lightbend, Inc.
*
* Licensed under Apache License 2.0
* (http://www.apache.org/licenses/LICENSE-2.0).
*
* See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*/
package scala.tools.nsc
import scala.reflect.io.{ AbstractFile, Directory, File, Path }
import scala.tools.nsc.classpath.ClassPathFactory
import scala.tools.nsc.io.Jar
import scala.tools.nsc.reporters.{ ConsoleReporter, Reporter }
import scala.util.chaining._
import scala.util.control.NonFatal
import java.io.IOException
/** An object that runs Scala code in script files.
*
* For example, here is a complete Scala script on Unix:
* {{{
* #!/bin/sh
* exec scala "\$0" "\$@"
* !#
* Console.println("Hello, world!")
* args.toList foreach Console.println
* }}}
* And here is a batch file example on Windows XP:
* {{{
* ::#!
* @echo off
* call scala %0 %*
* goto :eof
* ::!#
* Console.println("Hello, world!")
* args.toList foreach Console.println
* }}}
*
* @author Lex Spoon
* @todo It would be better if error output went to stderr instead
* of stdout...
*/
trait ScriptRunner {
/** Run a script file by name, with the given arguments.
* @return optionally an error, None for success
*/
def runScript(script: String, scriptArgs: List[String]): Option[Throwable]
/** Run the script text as supplied, with the given arguments.
* @return optionally an error, None for success
*/
def runScriptText(script: String, scriptArgs: List[String]): Option[Throwable]
}
class DefaultScriptRunner(settings: GenericRunnerSettings) extends AbstractScriptRunner(settings) {
protected def doCompile(scriptFile: String) = {
val reporter = new ConsoleReporter(settings)
val compiler = newGlobal(settings, reporter)
if (settings.pastefiles.value.nonEmpty) new compiler.Run().compile(settings.pastefiles.value)
// Setting settings.script.value informs the compiler this is not a self-contained compilation unit.
settings.script.value = mainClass
new compiler.Run().compile(scriptFile :: Nil)
!reporter.hasErrors
}
protected def newGlobal(settings: Settings, reporter: Reporter) = Global(settings, reporter)
}
abstract class AbstractScriptRunner(settings: GenericRunnerSettings) extends ScriptRunner {
/** Do compile the given script file, returning true for success. */
protected def doCompile(scriptFile: String): Boolean
protected final def mainClass: String = settings.script.value
/** Compile a script and then run the specified closure with
* a classpath for the compiled script.
*
* @return true if compilation and the handler succeeds, false otherwise.
*/
private def withCompiledScript(scriptFile: String)(handler: String => Option[Throwable]): Option[Throwable] = {
/* Compiles the script file, with the output set to either
* the user-specified location (jar or dir), or a temp dir.
* Returns the output location on success.
*/
def compile: Option[Path] = {
val outpath =
if (settings.outdir.isSetByUser)
Path(settings.outdir.value)
else
Directory.makeTemp("scalascript").tap { tmp =>
// delete the directory after the user code has finished
Runtime.getRuntime.addShutdownHook(new Thread(() => tmp.deleteRecursively()))
settings.outdir.value = tmp.path
}
if (doCompile(scriptFile)) Some(outpath) else None
}
def hasClassToRun(location: Path): Boolean = {
val cp = ClassPathFactory.newClassPath(AbstractFile.getDirectory(location), settings)
cp.findClass(mainClass).isDefined
}
// under -save, compile to a jar, specified either by -d or based on script name.
// if -d specifies a dir, assemble the jar by hand.
def withLatestJar(): Option[Throwable] = {
val outputToJar = settings.outdir.value.endsWith(".jar")
def stripped = List(".scala", ".sc").find(scriptFile.endsWith).map(scriptFile.stripSuffix).getOrElse(scriptFile)
val jarFile = File(
if (outputToJar) settings.outdir.value
else s"$stripped.jar".tap(j => if (!settings.outdir.isSetByUser) settings.outdir.value = j)
)
def jarOK = jarFile.canRead && jarFile.isFresher(File(scriptFile))
def recompile(): Option[Throwable] = {
jarFile.delete()
compile match {
case Some(compiledPath) =>
if (hasClassToRun(compiledPath)) {
// user -d mydir -save means assemble script.jar, don't delete mydir
if (!Jar.isJarOrZip(compiledPath)) {
try {
Jar.create(jarFile, compiledPath.toDirectory, mainClass)
None
} catch {
case NonFatal(e) => jarFile.delete() ; Some(e)
}
} else None
} else Some(NoScriptError)
case _ => Some(ScriptCompileError)
}
}
val err = if (!jarOK) recompile else None
err orElse handler(jarFile.toAbsolute.path) filterNot { case NoScriptError => true case _ => false }
}
/* The script runner calls System.exit to communicate a return value, but this must
* not take place until there are no non-daemon threads running. Tickets #1955, #2006.
*/
util.waitingForThreads {
// either update the jar or don't use a cache jar at all, just use the class files, if they exist
if (settings.save) withLatestJar()
else {
compile match {
case Some(cp) if hasClassToRun(cp) => handler(cp.path)
case Some(_) => None
case _ => Some(ScriptCompileError)
}
}
}
}
/** Run a script after it has been compiled. Prints any exceptions.
*
* @return true if execution succeeded, false otherwise
*/
private def runCompiled(compiledLocation: String, scriptArgs: List[String]): Option[Throwable] = {
val cp = File(compiledLocation).toURL +: settings.classpathURLs
ObjectRunner.runAndCatch(cp, mainClass, scriptArgs)
}
final def runScript(scriptFile: String, scriptArgs: List[String]): Option[Throwable] = {
val f = File(scriptFile)
if (!f.exists) Some(new IOException(s"no such file: $scriptFile"))
else if (!f.canRead) Some(new IOException(s"can't read: $scriptFile"))
else if (f.isDirectory) Some(new IOException(s"can't compile a directory: $scriptFile"))
else if (!settings.nc && !f.isFile) Some(new IOException(s"compile server requires a regular file: $scriptFile"))
else withCompiledScript(scriptFile) { runCompiled(_, scriptArgs) }
}
final def runScriptText(command: String, scriptArgs: List[String]): Option[Throwable] = {
val scriptFile = File.makeTemp("scalacmd", ".scala")
// save the command to the file
scriptFile writeAll command
try withCompiledScript(scriptFile.path) { runCompiled(_, scriptArgs) }
catch {
case NonFatal(e) => Some(e)
}
finally scriptFile.delete() // in case there was a compilation error
}
}
object ScriptRunner {
import scala.reflect.internal.util.ScalaClassLoader
def apply(settings: GenericRunnerSettings): ScriptRunner =
settings.Yscriptrunner.value match {
case "default" => new DefaultScriptRunner(settings)
case "resident" => new fsc.ResidentScriptRunner(settings)
case "shutdown" => new fsc.DaemonKiller(settings)
case custom =>
val loader = new ClassLoader(getClass.getClassLoader) with ScalaClassLoader
loader.create[ScriptRunner](custom, settings.errorFn)(settings)
}
}
object ScriptCompileError extends scala.util.control.ControlThrowable
object NoScriptError extends scala.util.control.ControlThrowable
© 2015 - 2024 Weber Informatics LLC | Privacy Policy