diff options
Diffstat (limited to 'src/compiler/scala')
12 files changed, 147 insertions, 132 deletions
diff --git a/src/compiler/scala/tools/nsc/GenericRunnerCommand.scala b/src/compiler/scala/tools/nsc/GenericRunnerCommand.scala index 68689a4109..86ab76b59c 100644 --- a/src/compiler/scala/tools/nsc/GenericRunnerCommand.scala +++ b/src/compiler/scala/tools/nsc/GenericRunnerCommand.scala @@ -5,6 +5,8 @@ package scala.tools.nsc +import GenericRunnerCommand._ + /** A command for ScriptRunner */ class GenericRunnerCommand( args: List[String], @@ -24,30 +26,31 @@ extends CompilerCommand(args, settings) { // change CompilerCommand behavior override def shouldProcessArguments: Boolean = false - /** thingToRun: What to run. If it is None, then the interpreter should be started - * arguments: Arguments to pass to the object or script to run - */ - val (_ok, thingToRun, arguments) = { - val (ok, remaining) = settings.processArguments(args, false) - val mainClass = - if (settings.jarfile.isDefault) None - else new io.Jar(settings.jarfile.value).mainClass - - // If there is a jar with a main class, the remaining args are passed to that. - // Otherwise, the first remaining argument is the program to run, and the rest - // of the arguments go to it. If remaining is empty, we'll start the repl. - mainClass match { - case Some(name) => (ok, Some(name), remaining) - case _ => (ok, remaining.headOption, remaining drop 1) + private lazy val (_ok, targetAndArguments) = settings.processArguments(args, false) + override def ok = _ok + private def guessHowToRun(target: String): GenericRunnerCommand.HowToRun = { + if (!ok) Error + else if (io.Jar.isJarOrZip(target)) AsJar + else if (util.ScalaClassLoader.classExists(settings.classpathURLs, target)) AsObject + else { + val f = io.File(target) + if (!f.hasExtension("class", "jar", "zip") && f.canRead) AsScript + else sys.error("Cannot figure out how to run target: " + target) } } - override def ok = _ok - + /** String with either the jar file, class name, or script file name. */ + def thingToRun = targetAndArguments.headOption getOrElse "" + /** Arguments to thingToRun. */ + def arguments = targetAndArguments drop 1 + + val howToRun = targetAndArguments match { + case Nil => AsRepl + case hd :: _ => waysToRun find (_.name == settings.howtorun.value) getOrElse guessHowToRun(hd) + } private def interpolate(s: String) = s.trim.replaceAll("@cmd@", cmdName).replaceAll("@compileCmd@", compCmdName) + "\n" def shortUsageMsg = interpolate(""" -Usage: @cmd@ <options> [<script|class|object> <arguments>] - or @cmd@ <options> [-jar <jarfile> <arguments>] +Usage: @cmd@ <options> [<script|class|object|jar> <arguments>] or @cmd@ -help All options to @compileCmd@ are also allowed. See @compileCmd@ -help. @@ -59,26 +62,37 @@ what to run. Runnable targets are: - a file containing scala source - the name of a compiled class - - a runnable jar file with a Main-Class attribute (if -jar is given) - - if no argument is given, the repl (interactive shell) is started + - a runnable jar file with a valid Main-Class attribute + - or if no argument is given, the repl (interactive shell) is started -Options to the runner which reach the java runtime: +Options to @cmd@ which reach the java runtime: -Dname=prop passed directly to java to set system properties -J<arg> -J is stripped and <arg> passed to java as-is -nobootcp do not put the scala jars on the boot classpath (slower) -Other scala startup options: +Other startup options: - -howtorun specify what to run <script|object|guess> (default: guess) - -i <file> preload <file> before starting the repl - -e <string> execute <string> as if entered in the repl - -nc no compilation daemon: do not use the fsc offline compiler - -savecompiled save the compiled script in a jar for future use + -howtorun what to run <script|object|jar|guess> (default: guess) + -i <file> preload <file> before starting the repl + -e <string> execute <string> as if entered in the repl + -save save the compiled script in a jar for future use + -nc no compilation daemon: do not use the fsc offline compiler -A file argument will be run as a scala script unless it contains only top -level classes and objects, and exactly one runnable main method. In that -case the file will be compiled and the main method invoked. This provides -a bridge between scripts and standard scala source. +A file argument will be run as a scala script unless it contains only +self-contained compilation units (classes and objects) and exactly one +runnable main method. In that case the file will be compiled and the +main method invoked. This provides a bridge between scripts and standard +scala source. """) + "\n" } + +object GenericRunnerCommand { + sealed abstract class HowToRun(val name: String) { } + case object AsJar extends HowToRun("jar") + case object AsObject extends HowToRun("object") + case object AsScript extends HowToRun("script") + case object AsRepl extends HowToRun("repl") + case object Error extends HowToRun("<error>") + val waysToRun = List(AsJar, AsObject, AsScript, AsRepl) +} diff --git a/src/compiler/scala/tools/nsc/GenericRunnerSettings.scala b/src/compiler/scala/tools/nsc/GenericRunnerSettings.scala index bd0ea4a4ba..45f8d1af5f 100644 --- a/src/compiler/scala/tools/nsc/GenericRunnerSettings.scala +++ b/src/compiler/scala/tools/nsc/GenericRunnerSettings.scala @@ -5,24 +5,17 @@ package scala.tools.nsc +import scala.tools.util.PathResolver + class GenericRunnerSettings(error: String => Unit) extends Settings(error) { - // A -jar option means the remainder of the command line should be - // passed to the main program in the jar. If -jar is given, jarfile - // will be set, but we also need to prepend the jar to the classpath - // since it may not be there. - val jarfile = - StringSetting( - "-jar", - "jar", - "Specify the jarfile in which to look for the main class", - "").stopProcessing() withPostSetHook (classpath prepend _.value) + def classpathURLs = new PathResolver(this) asURLs val howtorun = ChoiceSetting( "-howtorun", "how", "how to run the specified code", - List("guess", "object", "script"), + List("object", "script", "jar", "guess"), "guess") val loadfiles = @@ -38,14 +31,15 @@ class GenericRunnerSettings(error: String => Unit) extends Settings(error) { "execute a single command", "") - val savecompiled = + val save = BooleanSetting( - "-savecompiled", - "save the compiled script (assumes the code is a script)") + "-save", + "save the compiled script (assumes the code is a script)") withAbbreviation "-savecompiled" val nc = BooleanSetting( "-nc", "do not use the fsc compilation daemon") withAbbreviation "-nocompdaemon" @deprecated("Use `nc` instead") def nocompdaemon = nc + @deprecated("Use `save` instead") def savecompiled = save } diff --git a/src/compiler/scala/tools/nsc/MainGenericRunner.scala b/src/compiler/scala/tools/nsc/MainGenericRunner.scala index e40fdea3b9..7ead4cce4a 100644 --- a/src/compiler/scala/tools/nsc/MainGenericRunner.scala +++ b/src/compiler/scala/tools/nsc/MainGenericRunner.scala @@ -13,6 +13,7 @@ import io.{ File } import util.{ ClassPath, ScalaClassLoader } import Properties.{ versionString, copyrightString } import interpreter.{ ILoop } +import GenericRunnerCommand._ /** An object that runs Scala code. It has three possible * sources for the code to run: pre-compiled code, a script file, @@ -35,7 +36,7 @@ object MainGenericRunner { def process(args: Array[String]): Boolean = { val command = new GenericRunnerCommand(args.toList, (x: String) => errorFn(x)) - import command.settings + import command.{ settings, howToRun, thingToRun } def sampleCompiler = new Global(settings) // def so its not created unless needed if (!command.ok) return errorFn("\n" + command.shortUsageMsg) @@ -55,44 +56,35 @@ object MainGenericRunner { files ++ str mkString "\n\n" } - val classpath: List[URL] = new PathResolver(settings) asURLs + def runTarget(): Either[Throwable, Boolean] = howToRun match { + case AsObject => + ObjectRunner.runAndCatch(settings.classpathURLs, thingToRun, command.arguments) + case AsScript => + ScriptRunner.runScriptAndCatch(settings, thingToRun, command.arguments) + case AsJar => + ObjectRunner.runAndCatch( + File(thingToRun).toURL +: settings.classpathURLs, + new io.Jar(thingToRun).mainClass getOrElse sys.error("Cannot find main class for jar: " + thingToRun), + command.arguments + ) + case _ => + // We start the repl when no arguments are given. + Right(new ILoop process settings) + } - /** Was code given in a -e argument? */ + /** If -e and -i were both given, we want to execute the -e code after the + * -i files have been included, so they are read into strings and prepended to + * the code given in -e. The -i option is documented to only make sense + * interactively so this is a pretty reasonable assumption. + * + * This all needs a rewrite though. + */ if (isE) { - /** If a -i argument was also given, we want to execute the code after the - * files have been included, so they are read into strings and prepended to - * the code given in -e. The -i option is documented to only make sense - * interactively so this is a pretty reasonable assumption. - * - * This all needs a rewrite though. - */ - val fullArgs = command.thingToRun.toList ::: command.arguments - - return ScriptRunner.runCommand(settings, combinedCode, fullArgs) + ScriptRunner.runCommand(settings, combinedCode, thingToRun +: command.arguments) } - else command.thingToRun match { - case None => - // We start the repl when no arguments are given. - new ILoop process settings - - case Some(thingToRun) => - val isObjectName = - settings.howtorun.value match { - case "object" => true - case "script" => false - case "guess" => ScalaClassLoader.classExists(classpath, thingToRun) - } - - val result = try { - if (isObjectName) ObjectRunner.runAndCatch(classpath, thingToRun, command.arguments) - else ScriptRunner.runScriptAndCatch(settings, thingToRun, command.arguments) - } - catch { case ex => Left(ex) } - - result match { - case Left(ex) => errorFn(ex) - case Right(b) => b - } + else runTarget() match { + case Left(ex) => errorFn(ex) + case Right(b) => b } } } diff --git a/src/compiler/scala/tools/nsc/ScriptRunner.scala b/src/compiler/scala/tools/nsc/ScriptRunner.scala index 07aa62d164..96b7bce885 100644 --- a/src/compiler/scala/tools/nsc/ScriptRunner.scala +++ b/src/compiler/scala/tools/nsc/ScriptRunner.scala @@ -61,13 +61,10 @@ class ScriptRunner extends HasCompileSocket { def isScript(settings: Settings) = settings.script.value != "" /** Choose a jar filename to hold the compiled version of a script. */ - private def jarFileFor(scriptFile: String): File = { - val name = - if (scriptFile endsWith ".jar") scriptFile - else scriptFile + ".jar" - - File(name) - } + private def jarFileFor(scriptFile: String)= File( + if (scriptFile endsWith ".jar") scriptFile + else scriptFile.stripSuffix(".scala") + ".jar" + ) /** Read the entire contents of a file as a String. */ private def contentsOfFile(filename: String) = File(filename).slurp() @@ -140,7 +137,7 @@ class ScriptRunner extends HasCompileSocket { * not take place until there are no non-daemon threads running. Tickets #1955, #2006. */ waitingForThreads { - if (settings.savecompiled.value) { + if (settings.save.value) { val jarFile = jarFileFor(scriptFile) def jarOK = jarFile.canRead && (jarFile isFresher File(scriptFile)) @@ -179,10 +176,8 @@ class ScriptRunner extends HasCompileSocket { compiledLocation: String, scriptArgs: List[String]): Boolean = { - val pr = new PathResolver(settings) - val classpath = File(compiledLocation).toURL +: pr.asURLs - - ObjectRunner.runAndCatch(classpath, scriptMain(settings), scriptArgs) match { + val cp = File(compiledLocation).toURL +: settings.classpathURLs + ObjectRunner.runAndCatch(cp, scriptMain(settings), scriptArgs) match { case Left(ex) => ex.printStackTrace() ; false case _ => true } diff --git a/src/compiler/scala/tools/nsc/interpreter/ILoop.scala b/src/compiler/scala/tools/nsc/interpreter/ILoop.scala index 3d4ff7a20b..584b8867b2 100644 --- a/src/compiler/scala/tools/nsc/interpreter/ILoop.scala +++ b/src/compiler/scala/tools/nsc/interpreter/ILoop.scala @@ -38,7 +38,7 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) with LoopCommands { def this(in0: BufferedReader, out: PrintWriter) = this(Some(in0), out) - def this() = this(None, new PrintWriter(Console.out)) + def this() = this(None, new PrintWriter(Console.out, true)) var in: InteractiveReader = _ // the input stream from which commands come var settings: Settings = _ @@ -162,8 +162,9 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) |Type in expressions to have them evaluated. |Type :help for more information.""" . stripMargin.format(versionString, javaVmName, javaVersion) + val addendum = if (isReplDebug) "\n" + new java.util.Date else "" - plushln(welcomeMsg) + plushln(welcomeMsg + addendum) } /** Show the history */ @@ -748,7 +749,12 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) } @deprecated("Use `process` instead") - def main(args: Array[String]): Unit = process(args) + def main(args: Array[String]): Unit = { + if (isReplDebug) + System.out.println(new java.util.Date) + + process(args) + } @deprecated("Use `process` instead") def main(settings: Settings): Unit = process(settings) } @@ -765,7 +771,7 @@ object ILoop { stringFromStream { ostream => Console.withOut(ostream) { val input = new BufferedReader(new StringReader(code)) - val output = new PrintWriter(new OutputStreamWriter(ostream)) + val output = new PrintWriter(new OutputStreamWriter(ostream), true) val repl = new ILoop(input, output) val settings = new Settings settings.classpath.value = sys.props("java.class.path") diff --git a/src/compiler/scala/tools/nsc/interpreter/IMain.scala b/src/compiler/scala/tools/nsc/interpreter/IMain.scala index 527a9b153a..d74aa5e6df 100644 --- a/src/compiler/scala/tools/nsc/interpreter/IMain.scala +++ b/src/compiler/scala/tools/nsc/interpreter/IMain.scala @@ -87,6 +87,8 @@ class IMain(val settings: Settings, protected val out: PrintWriter) { // not sure if we have some motivation to print directly to console private def echo(msg: String) { Console println msg } + // protected def defaultImports: List[String] = List("_root_.scala.sys.exit") + /** We're going to go to some trouble to initialize the compiler asynchronously. * It's critical that nothing call into it until it's been initialized or we will * run into unrecoverable issues, but the perceived repl startup time goes @@ -114,6 +116,7 @@ class IMain(val settings: Settings, protected val out: PrintWriter) { // Can't use printMessage here, it deadlocks Console.println("Repl compiler initialized.") } + // addImports(defaultImports: _*) true } catch { @@ -609,6 +612,10 @@ class IMain(val settings: Settings, protected val out: PrintWriter) { quietRun("val %s = %s".format(tempName, name)) quietRun("val %s = %s.asInstanceOf[%s]".format(name, tempName, newType)) } + def quietImport(ids: String*): IR.Result = beQuietDuring(addImports(ids: _*)) + def addImports(ids: String*): IR.Result = + if (ids.isEmpty) IR.Success + else interpret("import " + ids.mkString(", ")) def quietBind(p: NamedParam): IR.Result = beQuietDuring(bind(p)) def bind(p: NamedParam): IR.Result = bind(p.name, p.tpe, p.value) diff --git a/src/compiler/scala/tools/nsc/io/Jar.scala b/src/compiler/scala/tools/nsc/io/Jar.scala index 0796742fb6..a9f0acaa00 100644 --- a/src/compiler/scala/tools/nsc/io/Jar.scala +++ b/src/compiler/scala/tools/nsc/io/Jar.scala @@ -65,8 +65,29 @@ class JarWriter(file: File, val manifest: Manifest = new Manifest()) { } object Jar { - // CLASS_PATH - // CONTENT_TYPE + // See http://download.java.net/jdk7/docs/api/java/nio/file/Path.html + // for some ideas. + private val ZipMagicNumber = List[Byte](80, 75, 3, 4) + private def magicNumberIsZip(f: Path) = f.isFile && (f.toFile.bytes().take(4).toList == ZipMagicNumber) + + def isJarOrZip(f: Path): Boolean = isJarOrZip(f, true) + def isJarOrZip(f: Path, examineFile: Boolean): Boolean = + f.hasExtension("zip", "jar") || (examineFile && magicNumberIsZip(f)) + + def locateByClass(clazz: Class[_]): Option[File] = { + try Some(File(clazz.getProtectionDomain().getCodeSource().getLocation().toURI().getPath())) + catch { case _: Exception => None } + } + /** Walks upward from wherever the scala library jar is searching for + * the given jar name. This approach finds the scala library jar in the + * release layout and in trunk builds going up from pack. + */ + def locateByName(name: String): Option[File] = { + def toSrc(d: Directory) = d.dirs.toList map (_ / name) + def walk(d: Directory) = d.parents flatMap toSrc find (_.isFile) map (_.toFile) + + locateByClass(classOf[ScalaObject]) flatMap (x => walk(x.parent)) + } def create(file: File, sourceDir: Directory, mainClass: String): File = { val writer = new Jar(file).jarWriter() diff --git a/src/compiler/scala/tools/nsc/io/Path.scala b/src/compiler/scala/tools/nsc/io/Path.scala index 68e9867dd7..3cfab55aaa 100644 --- a/src/compiler/scala/tools/nsc/io/Path.scala +++ b/src/compiler/scala/tools/nsc/io/Path.scala @@ -28,37 +28,12 @@ import scala.util.Random.alphanumeric */ object Path { - // See http://download.java.net/jdk7/docs/api/java/nio/file/Path.html - // for some ideas. - private val ZipMagicNumber = List[Byte](80, 75, 3, 4) - private def magicNumberIsZip(f: Path) = f.isFile && (f.toFile.bytes().take(4).toList == ZipMagicNumber) - - /** If examineFile is true, it will look at the first four bytes of the file - * and see if the magic number indicates it may be a jar or zip. - */ - def isJarOrZip(f: Path): Boolean = isJarOrZip(f, true) - def isJarOrZip(f: Path, examineFile: Boolean): Boolean = - f.hasExtension("zip", "jar") || (examineFile && magicNumberIsZip(f)) + def isJarOrZip(f: Path, examineFile: Boolean = true) = Jar.isJarOrZip(f, examineFile) // not certain these won't be problematic, but looks good so far implicit def string2path(s: String): Path = apply(s) implicit def jfile2path(jfile: JFile): Path = apply(jfile) - def locateJarByClass(clazz: Class[_]): Option[File] = { - try Some(File(clazz.getProtectionDomain().getCodeSource().getLocation().toURI().getPath())) - catch { case _: Exception => None } - } - /** Walks upward from wherever the scala library jar is searching for - * the given jar name. This approach finds the scala library jar in the - * release layout and in trunk builds going up from pack. - */ - def locateJarByName(name: String): Option[File] = { - def toSrc(d: Directory) = d.dirs.toList map (_ / name) - def walk(d: Directory) = d.parents flatMap toSrc find (_.isFile) map (_.toFile) - - locateJarByClass(classOf[ScalaObject]) flatMap (x => walk(x.parent)) - } - // java 7 style, we don't use it yet // object AccessMode extends Enumeration("AccessMode") { // val EXECUTE, READ, WRITE = Value diff --git a/src/compiler/scala/tools/nsc/io/Sources.scala b/src/compiler/scala/tools/nsc/io/Sources.scala index 48682e4ad4..c763b04511 100644 --- a/src/compiler/scala/tools/nsc/io/Sources.scala +++ b/src/compiler/scala/tools/nsc/io/Sources.scala @@ -4,7 +4,7 @@ package io import util.ClassPath import java.util.concurrent.{ Future, ConcurrentHashMap, ExecutionException } import java.util.zip.ZipException -import Path.{ isJarOrZip, locateJarByName } +import Jar.{ isJarOrZip, locateByClass } import collection.JavaConverters._ import Properties.{ envOrElse, propOrElse } @@ -62,7 +62,6 @@ trait LowPrioritySourcesImplicits { implicit def fallbackSources: Sources = defaultSources } - object Sources extends LowPrioritySourcesImplicits { // Examples of what libraryJar might be, each of which we'd like to find // the source files automatically: @@ -70,7 +69,7 @@ object Sources extends LowPrioritySourcesImplicits { // /scala/trunk/build/pack/lib/scala-library.jar // /scala/trunk/build/quick/classes/library // /scala/inst/scala-2.9.0.r24213-b20110206233447/lib/scala-library.jar - private def libraryJar = Path.locateJarByClass(classOf[ScalaObject]) map (_.toAbsolute.path) + private def libraryJar = locateByClass(classOf[ScalaObject]) map (_.toAbsolute.path) private def autoSourcePaths: List[String] = libraryJar.toList flatMap { lib => val markers = List("build/pack/lib", "build/quick/classes", "scala-library.jar") markers filter (lib contains _) flatMap { m => diff --git a/src/compiler/scala/tools/nsc/settings/MutableSettings.scala b/src/compiler/scala/tools/nsc/settings/MutableSettings.scala index 6a357cc08a..b485d4725a 100644 --- a/src/compiler/scala/tools/nsc/settings/MutableSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/MutableSettings.scala @@ -527,10 +527,14 @@ class MutableSettings(val errorFn: String => Unit) extends AbsSettings with Scal protected var v: String = default def indexOfChoice: Int = choices indexOf value - def tryToSet(args: List[String]) = { value = default ; Some(args) } + private def usageErrorMessage = { + "Usage: %s:<%s>\n where <%s> choices are %s (default: %s)\n".format( + name, helpArg, helpArg, choices mkString ", ", default) + } + def tryToSet(args: List[String]) = errorAndValue(usageErrorMessage, None) override def tryToSetColon(args: List[String]) = args match { - case Nil => errorAndValue("missing " + helpArg, None) + case Nil => errorAndValue(usageErrorMessage, None) case List(x) if choices contains x => value = x ; Some(Nil) case List(x) => errorAndValue("'" + x + "' is not a valid choice for '" + name + "'", None) case xs => errorAndValue("'" + name + "' does not accept multiple arguments.", None) diff --git a/src/compiler/scala/tools/nsc/util/ClassPath.scala b/src/compiler/scala/tools/nsc/util/ClassPath.scala index 1e29dd4fae..bb404480a9 100644 --- a/src/compiler/scala/tools/nsc/util/ClassPath.scala +++ b/src/compiler/scala/tools/nsc/util/ClassPath.scala @@ -83,6 +83,7 @@ object ClassPath { /** Join the paths as a classpath */ def fromPaths(paths: Path*): String = join(paths map (_.path): _*) + def fromURLs(urls: URL*): String = fromPaths(urls map (x => Path(x.getPath)) : _*) /** Split the classpath and map them into URLs */ def toURLs(cp: String): List[URL] = toPaths(cp) map (_.toURL) diff --git a/src/compiler/scala/tools/nsc/util/ScalaClassLoader.scala b/src/compiler/scala/tools/nsc/util/ScalaClassLoader.scala index ee71d04827..be69c39547 100644 --- a/src/compiler/scala/tools/nsc/util/ScalaClassLoader.scala +++ b/src/compiler/scala/tools/nsc/util/ScalaClassLoader.scala @@ -93,13 +93,20 @@ object ScalaClassLoader { with ScalaClassLoader { private var classloaderURLs = urls.toList + private def classpathString = ClassPath.fromURLs(urls: _*) /** Override to widen to public */ override def addURL(url: URL) = { classloaderURLs +:= url super.addURL(url) } - + override def run(objectName: String, arguments: Seq[String]) { + try super.run(objectName, arguments) + catch { case x: ClassNotFoundException => + throw new ClassNotFoundException(objectName + + " (args = %s, classpath = %s)".format(arguments mkString ", ", classpathString)) + } + } override def toString = urls.mkString("URLClassLoader(\n ", "\n ", "\n)\n") } |