diff options
Diffstat (limited to 'src')
21 files changed, 581 insertions, 374 deletions
diff --git a/src/compiler/scala/tools/ant/Scaladoc.scala b/src/compiler/scala/tools/ant/Scaladoc.scala index 31463d620d..03dc99be68 100644 --- a/src/compiler/scala/tools/ant/Scaladoc.scala +++ b/src/compiler/scala/tools/ant/Scaladoc.scala @@ -520,18 +520,8 @@ class Scaladoc extends MatchingTask { docSettings.deprecation.value = deprecation docSettings.unchecked.value = unchecked log("Scaladoc params = '" + addParams + "'", Project.MSG_DEBUG) - var args = docSettings.splitParams(addParams) - - while (!args.isEmpty) { - if (args.head startsWith "-") { - val args0 = args - args = docSettings.parseParams(args) - if (args0 eq args) error("Parameter '" + args.head + "' is not recognised by Scaladoc.") - } - else if (args.head == "") args = args.tail - else error("Parameter '" + args.head + "' does not start with '-'.") - } + docSettings processArgumentString addParams Pair(docSettings, sourceFiles) } diff --git a/src/compiler/scala/tools/nsc/CompileSocket.scala b/src/compiler/scala/tools/nsc/CompileSocket.scala index 06f0f15d9d..bad1cc9d1c 100644 --- a/src/compiler/scala/tools/nsc/CompileSocket.scala +++ b/src/compiler/scala/tools/nsc/CompileSocket.scala @@ -14,6 +14,7 @@ import java.security.SecureRandom import io.{ File, Path, Process, Socket } import scala.util.control.Exception.catching +import scala.tools.util.StringOps.splitWhere /** This class manages sockets for the fsc offline compiler. */ class CompileSocket { @@ -176,18 +177,10 @@ class CompileSocket { try { Some(x.toInt) } catch { case _: NumberFormatException => None } - def getSocket(serverAdr: String): Socket = { - def fail = fatal("Malformed server address: %s; exiting" format serverAdr) - (serverAdr indexOf ':') match { - case -1 => fail - case cpos => - val hostName: String = serverAdr take cpos - parseInt(serverAdr drop (cpos + 1)) match { - case Some(port) => getSocket(hostName, port) - case _ => fail - } - } - } + def getSocket(serverAdr: String): Socket = ( + for ((name, portStr) <- splitWhere(serverAdr, _ == ':', true) ; port <- parseInt(portStr)) yield + getSocket(name, port) + ) getOrElse fatal("Malformed server address: %s; exiting" format serverAdr) def getSocket(hostName: String, port: Int): Socket = Socket(hostName, port).opt getOrElse fatal("Unable to establish connection to server %s:%d; exiting".format(hostName, port)) diff --git a/src/compiler/scala/tools/nsc/CompilerCommand.scala b/src/compiler/scala/tools/nsc/CompilerCommand.scala index 75a3414d75..95a2fff8de 100644 --- a/src/compiler/scala/tools/nsc/CompilerCommand.scala +++ b/src/compiler/scala/tools/nsc/CompilerCommand.scala @@ -8,6 +8,8 @@ package scala.tools.nsc import Settings.Setting import java.io.IOException +import scala.collection.mutable.ListBuffer +import scala.tools.nsc.util.ArgumentsExpander /** A class representing command line info for scalac */ class CompilerCommand( @@ -23,31 +25,22 @@ class CompilerCommand( /** file extensions of files that the compiler can process */ lazy val fileEndings = Properties.fileEndings - /** Private buffer for accumulating files to compile */ - private var fs: List[String] = List() - - /** Public list of files to compile */ - def files: List[String] = fs.reverse - /** The name of the command */ val cmdName = "scalac" private val helpSyntaxColumnWidth: Int = (settings.settingSet map (_.helpSyntax.length)) max - private def format(s: String): String = { - val buf = new StringBuilder(s) - var i = s.length - while (i < helpSyntaxColumnWidth) { buf.append(' '); i += 1 } - buf.toString() - } + private def format(s: String): String = + if (s.length >= helpSyntaxColumnWidth) s + else s + (" " * (helpSyntaxColumnWidth - s.length)) /** Creates a help message for a subset of options based on cond */ def createUsageMsg(label: String, cond: (Setting) => Boolean): String = settings.settingSet . filter(cond) . map(s => format(s.helpSyntax) + " " + s.helpDescription) . - mkString("Usage: %s <options> <source files>\n%s options include:\n " . + toList.sorted.mkString("Usage: %s <options> <source files>\n%s options include:\n " . format(cmdName, label), "\n ", "\n") /** Messages explaining usage and options */ @@ -76,52 +69,19 @@ class CompilerCommand( case None => "" } - /** Whether the command was processed okay */ - var ok = true - - /** Process the arguments and update the settings accordingly. - This method is called only once, during initialization. */ - protected def processArguments() { - // initialization - var args = arguments - def errorAndNotOk(msg: String) = { error(msg) ; ok = false } - - // given a @ argument expands it out - def doExpand(x: String) = - try { args = util.ArgumentsExpander.expandArg(x) ::: args.tail } - catch { case ex: IOException => errorAndNotOk(ex.getMessage) } - - // true if it's a legit looking source file - def isSourceFile(x: String) = - (settings.script.value != "") || - (fileEndings exists (x endsWith _)) - - // given an option for scalac finds out what it is - def doOption(x: String): Unit = { - if (interactive) - return errorAndNotOk("no options can be given in interactive mode") - - val argsLeft = settings.parseParams(args) - if (args != argsLeft) args = argsLeft - else errorAndNotOk("bad option: '" + x + "'") - } - - // cycle through args until empty or error - while (!args.isEmpty && ok) args.head match { - case x if x startsWith "@" => doExpand(x) - case x if x startsWith "-" => doOption(x) - case x if isSourceFile(x) => fs = x :: fs ; args = args.tail - case "" => args = args.tail // quick fix [martin: for what?] - case x => errorAndNotOk("don't know what to do with " + x) - } - - ok &&= settings.checkDependencies - } - // CompilerCommand needs processArguments called at the end of its constructor, // as does its subclass GenericRunnerCommand, but it cannot be called twice as it // accumulates arguments. The fact that it's called from within the constructors // makes initialization order an obstacle to simplicity. - if (shouldProcessArguments) - processArguments() + val (ok: Boolean, files: List[String]) = + if (shouldProcessArguments) { + // expand out @filename to the contents of that filename + val expandedArguments = arguments flatMap { + case x if x startsWith "@" => ArgumentsExpander expandArg x + case x => List(x) + } + + settings processArguments expandedArguments + } + else (true, Nil) } diff --git a/src/compiler/scala/tools/nsc/GenericRunnerCommand.scala b/src/compiler/scala/tools/nsc/GenericRunnerCommand.scala index bde623b5d7..fa0b5bce9c 100644 --- a/src/compiler/scala/tools/nsc/GenericRunnerCommand.scala +++ b/src/compiler/scala/tools/nsc/GenericRunnerCommand.scala @@ -24,56 +24,39 @@ extends CompilerCommand(allargs, settings, error, false, false) override val cmdName = "scala" val compCmdName = "scalac" - /** What to run. If it is None, then the interpreter should be started */ - var thingToRun: Option[String] = None + /** 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 + * + * we can safely process arguments since we passed the superclass shouldProcessArguments=false + */ + val (thingToRun, arguments) = (settings processArguments allargs)._2 match { + case Nil => (None, Nil) + case hd :: tl => (Some(hd), tl) + } - /** Arguments to pass to the object or script to run */ - var arguments: List[String] = Nil + override def usageMsg = """ +%s [ <option> ]... [<torun> <arguments>] - override protected def processArguments() { - var args = allargs +All options to %s are allowed. See %s -help. - while (!args.isEmpty && ok && args.head.startsWith("-")) { - val args0 = args - args = settings parseParams args - if (args eq args0) { - error("bad option: '" + args.head + "'") - ok = false - } - } +<torun>, if present, is an object or script file to run. +If no <torun> is present, run an interactive shell. - if (!args.isEmpty) { - thingToRun = Some(args.head) - arguments = args.tail - } - } +Option -howtorun allows explicitly specifying how to run <torun>: + script: it is a script file + object: it is an object name + guess: (the default) try to guess - // we can safely call processArguments since we passed the superclass shouldProcessArguments=false - processArguments() +Option -i requests that a file be pre-loaded. It is only +meaningful for interactive shells. - override def usageMsg = { - cmdName + " [ <option> ]... [<torun> <arguments>]\n" + - "\n" + - "All options to "+compCmdName+" are allowed. See "+compCmdName+" -help.\n" + - "\n" + - "<torun>, if present, is an object or script file to run.\n" + - "If no <torun> is present, run an interactive shell.\n" + - "\n" + - "Option -howtorun allows explicitly specifying how to run <torun>:\n" + - " script: it is a script file\n" + - " object: it is an object name\n" + - " guess: (the default) try to guess\n" + - "\n" + - "Option -i requests that a file be pre-loaded. It is only\n" + - "meaningful for interactive shells.\n" + - "\n" + - "Option -e requests that its argument be executed as Scala code.\n" + - "\n" + - "Option -savecompiled requests that the compiled script be saved\n" + - "for future use.\n" + - "\n" + - "Option -nocompdaemon requests that the fsc offline compiler not be used.\n" + - "\n" + - "Option -Dproperty=value sets a Java system property.\n" - } +Option -e requests that its argument be executed as Scala code. + +Option -savecompiled requests that the compiled script be saved +for future use. + +Option -nocompdaemon requests that the fsc offline compiler not be used. + +Option -Dproperty=value sets a Java system property. +""".format(cmdName, compCmdName, compCmdName) } diff --git a/src/compiler/scala/tools/nsc/Interpreter.scala b/src/compiler/scala/tools/nsc/Interpreter.scala index 49ce29a7ff..8d460101c9 100644 --- a/src/compiler/scala/tools/nsc/Interpreter.scala +++ b/src/compiler/scala/tools/nsc/Interpreter.scala @@ -503,8 +503,8 @@ class Interpreter(val settings: Settings, out: PrintWriter) { } private[nsc] val powerMkImports = List( - "mkContext", "mkTree", "mkTrees", "mkAlias", "mkSourceFile", "mkUnit", "mkType", "mkTypedTree", "mkTypedTrees", - "treeWrapper" + "mkContext", "mkTree", "mkTrees", "mkAlias", "mkSourceFile", "mkUnit", "mkType", "mkTypedTree", "mkTypedTrees" + // , "treeWrapper" ) /** Compile an nsc SourceFile. Returns true if there are diff --git a/src/compiler/scala/tools/nsc/InterpreterLoop.scala b/src/compiler/scala/tools/nsc/InterpreterLoop.scala index d0c2747ae1..be6881613a 100644 --- a/src/compiler/scala/tools/nsc/InterpreterLoop.scala +++ b/src/compiler/scala/tools/nsc/InterpreterLoop.scala @@ -6,14 +6,14 @@ package scala.tools.nsc -import java.io.{ BufferedReader, File, FileReader, PrintWriter } +import java.io.{ BufferedReader, FileReader, PrintWriter } import java.io.IOException import scala.tools.nsc.{ InterpreterResults => IR } import scala.annotation.tailrec import scala.collection.mutable.ListBuffer import interpreter._ -import io.{ Process } +import io.{ File, Process } // Classes to wrap up interpreter commands and their results // You can add new commands by adding entries to val commands @@ -91,13 +91,13 @@ class InterpreterLoop(in0: Option[BufferedReader], out: PrintWriter) { var addedClasspath: List[String] = Nil /** A reverse list of commands to replay if the user requests a :replay */ - var replayCommandsRev: List[String] = Nil + var replayCommandStack: List[String] = Nil /** A list of commands to replay if the user requests a :replay */ - def replayCommands = replayCommandsRev.reverse + def replayCommands = replayCommandStack.reverse /** Record a command for replay should the user request a :replay */ - def addReplay(cmd: String) = replayCommandsRev = cmd :: replayCommandsRev + def addReplay(cmd: String) = replayCommandStack ::= cmd /** Close the interpreter and set the var to <code>null</code>. */ def closeInterpreter() { @@ -246,22 +246,24 @@ class InterpreterLoop(in0: Option[BufferedReader], out: PrintWriter) { /** interpret all lines from a specified file */ def interpretAllFrom(filename: String) { - val fileIn = - try { new FileReader(filename) } - catch { case _:IOException => return out.println("Error opening file: " + filename) } + val fileIn = File(filename) + if (!fileIn.exists) + return out.println("Error opening file: " + filename) val oldIn = in - val oldReplay = replayCommandsRev + val oldReplay = replayCommandStack + try { - val inFile = new BufferedReader(fileIn) - in = new SimpleReader(inFile, out, false) - out.println("Loading " + filename + "...") - out.flush - repl - } finally { + fileIn applyReader { reader => + in = new SimpleReader(reader, out, false) + out.println("Loading " + filename + "...") + out.flush + repl + } + } + finally { in = oldIn - replayCommandsRev = oldReplay - fileIn.close + replayCommandStack = oldReplay } } @@ -292,8 +294,8 @@ class InterpreterLoop(in0: Option[BufferedReader], out: PrintWriter) { } def withFile(filename: String)(action: String => Unit) { - if (! new File(filename).exists) out.println("That file does not exist") - else action(filename) + if (File(filename).exists) action(filename) + else out.println("That file does not exist") } def load(arg: String) = { @@ -307,14 +309,13 @@ class InterpreterLoop(in0: Option[BufferedReader], out: PrintWriter) { def addJar(arg: String): Unit = { - val f = new java.io.File(arg) - if (!f.exists) { - out.println("The file '" + f + "' doesn't seem to exist.") - return + val f = File(arg).normalize + if (f.exists) { + addedClasspath :::= List(f.path) + println("Added " + f.path + " to your classpath.") + replay() } - addedClasspath = addedClasspath ::: List(f.getCanonicalPath) - println("Added " + f.getCanonicalPath + " to your classpath.") - replay() + else out.println("The file '" + f + "' doesn't seem to exist.") } /** This isn't going to win any efficiency awards, but it's only @@ -531,7 +532,7 @@ class InterpreterLoop(in0: Option[BufferedReader], out: PrintWriter) { for (filename <- settings.loadfiles.value) { val cmd = ":load " + filename command(cmd) - replayCommandsRev = cmd :: replayCommandsRev + addReplay(cmd) out.println() } case _ => diff --git a/src/compiler/scala/tools/nsc/MainGenericRunner.scala b/src/compiler/scala/tools/nsc/MainGenericRunner.scala index cd16ef8c8b..834f4d1742 100644 --- a/src/compiler/scala/tools/nsc/MainGenericRunner.scala +++ b/src/compiler/scala/tools/nsc/MainGenericRunner.scala @@ -11,6 +11,7 @@ import java.io.{ File, IOException } import java.lang.{ClassNotFoundException, NoSuchMethodException} import java.lang.reflect.InvocationTargetException import java.net.{ URL, MalformedURLException } +import scala.tools.util.PathResolver import util.{ ClassPath, ScalaClassLoader } import File.pathSeparator @@ -21,37 +22,6 @@ import Properties.{ versionString, copyrightString } * or interactive entry. */ object MainGenericRunner { - /** Append jars found in ${scala.home}/lib to - * a specified classpath. Also append "." if the - * input classpath is empty; otherwise do not. - * - * @param classpath - * @return the new classpath - */ - private def addClasspathExtras(classpath: String): String = { - val scalaHome = Properties.scalaHome - - def listDir(name: String): List[File] = { - val libdir = new File(new File(scalaHome), name) - if (!libdir.exists || libdir.isFile) Nil else libdir.listFiles.toList - } - lazy val jarsInLib = listDir("lib") filter (_.getName endsWith ".jar") - lazy val dirsInClasses = listDir("classes") filter (_.isDirectory) - val cpScala = - if (scalaHome == null) { - // this is to make the interpreter work when running without the scala script - // (e.g. from eclipse). Before, "java.class.path" was added to the user classpath - // in Settings; this was changed to match the behavior of Sun's javac. - val javacp = System.getProperty("java.class.path") - if (javacp == null) Nil - else ClassPath.expandPath(javacp) - } - else (jarsInLib ::: dirsInClasses) map (_.toString) - - // either prepend existing classpath or append "." - (if (classpath == "") cpScala ::: List(".") else classpath :: cpScala) mkString pathSeparator - } - def main(args: Array[String]) { def errorFn(str: String) = Console println str @@ -62,7 +32,8 @@ object MainGenericRunner { if (!command.ok) return errorFn("%s\n%s".format(command.usageMsg, sampleCompiler.pluginOptionsHelp)) - settings.classpath.value = addClasspathExtras(settings.classpath.value) + // append the jars in ${scala.home}/lib to the classpath, as well as "." if none was given. + settings appendToClasspath PathResolver.basicScalaClassPath(settings.classpath.value == "") settings.defines.applyToCurrentJVM if (settings.version.value) @@ -107,11 +78,12 @@ object MainGenericRunner { val url = specToURL(spec); if !url.isEmpty ) yield url.get - val classpath: List[URL] = + val classpath: List[URL] = ( paths(settings.bootclasspath.value) ::: paths(settings.classpath.value) ::: jars(settings.extdirs.value) ::: urls(settings.Xcodebase.value) + ).distinct def createLoop(): InterpreterLoop = { val loop = new InterpreterLoop diff --git a/src/compiler/scala/tools/nsc/Settings.scala b/src/compiler/scala/tools/nsc/Settings.scala index 8c921b98a1..a2176a48ea 100644 --- a/src/compiler/scala/tools/nsc/Settings.scala +++ b/src/compiler/scala/tools/nsc/Settings.scala @@ -12,10 +12,38 @@ import io.AbstractFile import util.SourceFile import Settings._ import annotation.elidable +import scala.tools.util.PathResolver class Settings(errorFn: String => Unit) extends ScalacSettings { def this() = this(Console.println) + /** Iterates over the arguments applying them to settings where applicable. + * Then verifies setting dependencies are met. + * + * Returns (success, List of unprocessed arguments) + */ + def processArguments(arguments: List[String]): (Boolean, List[String]) = { + var args = arguments + + while (args.nonEmpty) { + if (args.head startsWith "-") { + val args0 = args + args = this parseParams args + if (args eq args0) { + errorFn("bad option: '" + args.head + "'") + return (false, args) + } + } + else if (args.head == "") { // discard empties, sometimes they appear because of ant or etc. + args = args.tail + } + else return (checkDependencies, args) + } + + (checkDependencies, args) + } + def processArgumentString(params: String) = processArguments(splitParams(params)) + // optionizes a system property private def syspropopt(name: String): Option[String] = Option(System.getProperty(name)) private def sysenvopt(name: String): Option[String] = Option(System.getenv(name)) @@ -84,7 +112,6 @@ class Settings(errorFn: String => Unit) extends ScalacSettings { true } - /** A list pairing source directories with their output directory. * This option is not available on the command line, but can be set by * other tools (IDEs especially). The command line specifies a single @@ -93,69 +120,12 @@ class Settings(errorFn: String => Unit) extends ScalacSettings { */ lazy val outputDirs = new OutputDirs - /** - * Split command line parameters by space, properly process quoted parameter + /** Split the given line into parameters. */ - def splitParams(line: String): List[String] = { - def parse(from: Int, i: Int, args: List[String]): List[String] = { - if (i < line.length) { - line.charAt(i) match { - case ' ' => - val args1 = fetchArg(from, i) :: args - val j = skipS(i + 1) - if (j >= 0) { - parse(j, j, args1) - } else args1 - case '"' => - val j = skipTillQuote(i + 1) - if (j > 0) { - parse(from, j + 1, args) - } else { - errorFn("Parameters '" + line + "' with unmatched quote at " + i + ".") - Nil - } - case _ => parse(from, i + 1, args) - } - } else { // done - if (i > from) { - fetchArg(from, i) :: args - } else args - } - } - - def fetchArg(from: Int, until: Int) = { - if (line.charAt(from) == '"') { - line.substring(from + 1, until - 1) - } else { - line.substring(from, until) - } - } - - def skipTillQuote(i: Int): Int = { - if (i < line.length) { - line.charAt(i) match { - case '"' => i - case _ => skipTillQuote(i + 1) - } - } else -1 - } - - def skipS(i: Int): Int = { - if (i < line.length) { - line.charAt(i) match { - case ' ' => skipS(i + 1) - case _ => i - } - } else -1 - } - - // begin split - val j = skipS(0) - if (j >= 0) { - parse(j, j, Nil).reverse - } else Nil - } + def splitParams(line: String) = PathResolver.splitParams(line, errorFn) + /** Returns any unprocessed arguments. + */ def parseParams(args: List[String]): List[String] = { // verify command exists and call setter def tryToSetIfExists( @@ -234,6 +204,8 @@ class Settings(errorFn: String => Unit) extends ScalacSettings { doArgs(args) } + def parseParamString(params: String) = parseParams(splitParams(params)) + // checks both name and any available abbreviations def lookupSetting(cmd: String): Option[Setting] = settingSet.find(x => x.name == cmd || (x.abbreviations contains cmd)) @@ -456,9 +428,9 @@ object Settings { /** Will be called after this Setting is set, for any cases where the * Setting wants to perform extra work. */ - private var _postSetHook: () => Unit = () => () - def postSetHook(): Unit = _postSetHook() - def withPostSetHook(f: () => Unit): this.type = { _postSetHook = f ; this } + private var _postSetHook: this.type => Unit = (x: this.type) => () + def postSetHook(): Unit = _postSetHook(this) + def withPostSetHook(f: this.type => Unit): this.type = { _postSetHook = f ; this } /** After correct Setting has been selected, tryToSet is called with the * remainder of the command line. It consumes any applicable arguments and @@ -805,6 +777,8 @@ object Settings { trait ScalacSettings { self: Settings => + import scala.tools.util.PathResolver + import PathResolver.{ Defaults, Environment } import collection.immutable.TreeSet /** A list of all settings */ @@ -818,7 +792,15 @@ trait ScalacSettings { * Temporary Settings */ val suppressVTWarn = BooleanSetting ("-Ysuppress-vt-typer-warnings", "Suppress warnings from the typer when testing the virtual class encoding, NOT FOR FINAL!") - def appendToClasspath(entry: String) = classpath.value += (pathSeparator + entry) + def appendToClasspath(entry: String) = { + val oldClasspath = classpath.value + classpath.value = + if (classpath.value == "") entry + else classpath.value + pathSeparator + entry + + if (Ylogcp.value) + Console.println("Updated classpath from '%s' to '%s'".format(oldClasspath, classpath.value)) + } /** * Standard settings @@ -826,7 +808,9 @@ trait ScalacSettings { // argfiles is only for the help message val argfiles = BooleanSetting ("@<file>", "A text file containing compiler arguments (options and source files)") val bootclasspath = StringSetting ("-bootclasspath", "path", "Override location of bootstrap class files", bootclasspathDefault) - val classpath = StringSetting ("-classpath", "path", "Specify where to find user class files", classpathDefault).withAbbreviation("-cp") + val classpath = StringSetting ("-classpath", "path", "Specify where to find user class files", classpathDefault) . + withAbbreviation("-cp") . + withPostSetHook(self => if (Ylogcp.value) Console.println("Updated classpath to '%s'".format(self.value))) val outdir = OutputSetting (outputDirs, ".") val dependenciesFile = StringSetting ("-dependencyfile", "file", "Specify the file in which dependencies are tracked", ".scala_dependencies") val deprecation = BooleanSetting ("-deprecation", "Output source locations where deprecated APIs are used") @@ -839,7 +823,7 @@ trait ScalacSettings { withHelpSyntax("-make:<strategy>") val nowarnings = BooleanSetting ("-nowarn", "Generate no warnings") val XO = BooleanSetting ("-optimise", "Generates faster bytecode by applying optimisations to the program").withAbbreviation("-optimize") . - withPostSetHook(() => List(inline, Xcloselim, Xdce) foreach (_.value = true)) + withPostSetHook(_ => List(inline, Xcloselim, Xdce) foreach (_.value = true)) val printLate = BooleanSetting ("-print", "Print program with all Scala-specific features removed") val sourcepath = StringSetting ("-sourcepath", "path", "Specify where to find input source files", "") val target = ChoiceSetting ("-target", "Specify for which target object files should be built", List("jvm-1.5", "msil"), "jvm-1.5") @@ -848,6 +832,10 @@ trait ScalacSettings { val verbose = BooleanSetting ("-verbose", "Output messages about what the compiler is doing") val version = BooleanSetting ("-version", "Print product version and exit") + /** New to classpaths */ + val javabootclasspath = StringSetting ("-javabootclasspath", "path", "Override java boot classpath.", Environment.javaBootClassPath) + val javaextdirs = StringSetting ("-javaextdirs", "path", "Override java extdirs classpath.", Environment.javaExtDirs) + /** * -X "Advanced" settings */ @@ -929,6 +917,7 @@ trait ScalacSettings { val Ypmatnaive = BooleanSetting ("-Ypmat-naive", "Desugar matches as naively as possible..") val Ytailrec = BooleanSetting ("-Ytailrecommend", "Alert methods which would be tail-recursive if private or final.") val Yjenkins = BooleanSetting ("-Yjenkins-hashCodes", "Use jenkins hash algorithm for case class generated hashCodes.") + val Ylogcp = BooleanSetting ("-Ylog-classpath", "Output information about what classpath is being applied.") // Warnings val Xwarninit = BooleanSetting ("-Xwarninit", "Warn about possible changes in initialization semantics") @@ -937,7 +926,7 @@ trait ScalacSettings { val YwarnShadow = BooleanSetting ("-Ywarn-shadowing", "Emit warnings about possible variable shadowing.") val YwarnCatches = BooleanSetting ("-Ywarn-catches", "Emit warnings about catch blocks which catch everything.") val Xwarnings = BooleanSetting ("-Xstrict-warnings", "Emit warnings about lots of things.") . - withPostSetHook(() => + withPostSetHook(_ => List(YwarnShadow, YwarnCatches, Xwarndeadcode, Xwarninit) foreach (_.value = true) ) /** diff --git a/src/compiler/scala/tools/nsc/ast/parser/SymbolicXMLBuilder.scala b/src/compiler/scala/tools/nsc/ast/parser/SymbolicXMLBuilder.scala index 9c34739a15..31529f83e6 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/SymbolicXMLBuilder.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/SymbolicXMLBuilder.scala @@ -11,6 +11,7 @@ import collection.mutable.Map import xml.{ EntityRef, Text } import xml.XML.{ xmlns } import symtab.Flags.MUTABLE +import scala.tools.util.StringOps.splitWhere /** This class builds instance of <code>Tree</code> that represent XML. * @@ -160,9 +161,9 @@ abstract class SymbolicXMLBuilder(p: Parsers#Parser, preserveWS: Boolean) } /** Returns (Some(prefix) | None, rest) based on position of ':' */ - def splitPrefix(name: String): (Option[String], String) = (name indexOf ':') match { - case -1 => (None, name) - case i => (Some(name take i), name drop (i + 1)) + def splitPrefix(name: String): (Option[String], String) = splitWhere(name, _ == ':', true) match { + case Some((pre, rest)) => (Some(pre), rest) + case _ => (None, name) } /** Various node constructions. */ diff --git a/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala b/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala index 02199759b9..0d2f9a13d1 100644 --- a/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala +++ b/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala @@ -9,6 +9,7 @@ package backend import io.AbstractFile import util.JavaClassPath import util.ClassPath.{ JavaContext, DefaultJavaContext } +import scala.tools.util.PathResolver trait JavaPlatform extends Platform[AbstractFile] { import global._ diff --git a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala index 3e697dd923..c3430f8006 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala @@ -1643,9 +1643,11 @@ abstract class GenICode extends SubComponent { abstract class Cleanup; case class MonitorRelease(m: Local) extends Cleanup { + override def hashCode = m.hashCode override def equals(other: Any) = m == other; } case class Finalizer(f: Tree) extends Cleanup { + override def hashCode = f.hashCode override def equals(other: Any) = f == other; } diff --git a/src/compiler/scala/tools/nsc/interpreter/SimpleReader.scala b/src/compiler/scala/tools/nsc/interpreter/SimpleReader.scala index bca2e18e39..9d604ab8b3 100644 --- a/src/compiler/scala/tools/nsc/interpreter/SimpleReader.scala +++ b/src/compiler/scala/tools/nsc/interpreter/SimpleReader.scala @@ -8,6 +8,7 @@ package scala.tools.nsc package interpreter import java.io.{ BufferedReader, PrintWriter } +import io.{ Path, File, Directory } /** Reads using standard JDK API */ class SimpleReader( @@ -16,7 +17,9 @@ class SimpleReader( val interactive: Boolean) extends InteractiveReader { def this() = this(Console.in, new PrintWriter(Console.out), true) + def this(in: File, out: PrintWriter, interactive: Boolean) = this(in.bufferedReader(), out, interactive) + def close() = in.close() def readOneLine(prompt: String): String = { if (interactive) { out.print(prompt) diff --git a/src/compiler/scala/tools/nsc/io/AbstractFile.scala b/src/compiler/scala/tools/nsc/io/AbstractFile.scala index 31073a0799..15a05aa3fc 100644 --- a/src/compiler/scala/tools/nsc/io/AbstractFile.scala +++ b/src/compiler/scala/tools/nsc/io/AbstractFile.scala @@ -20,8 +20,6 @@ import scala.collection.mutable.ArrayBuffer */ object AbstractFile { - def isJarOrZip(f: Path) = cond(f.extension) { case "zip" | "jar" => true } - /** Returns "getFile(new File(path))". */ def getFile(path: String): AbstractFile = getFile(Path(path)) def getFile(path: Path): AbstractFile = getFile(path.toFile) @@ -46,7 +44,7 @@ object AbstractFile */ def getDirectory(file: File): AbstractFile = if (file.isDirectory) new PlainFile(file) - else if (file.isFile && isJarOrZip(file)) ZipArchive fromFile file + else if (file.isFile && Path.isJarOrZip(file)) ZipArchive fromFile file else null /** @@ -58,7 +56,7 @@ object AbstractFile * @return ... */ def getURL(url: URL): AbstractFile = - Option(url) partialMap { case url: URL if isJarOrZip(url.getPath) => ZipArchive fromURL url } orNull + Option(url) partialMap { case url: URL if Path.isJarOrZip(url.getPath) => ZipArchive fromURL url } orNull } /** @@ -93,6 +91,9 @@ abstract class AbstractFile extends AnyRef with Iterable[AbstractFile] { /** Returns the path of this abstract file. */ def path: String + /** Checks extension case insensitively. */ + def hasExtension(other: String) = Path(path) hasExtension other + /** The absolute file, if this is a relative file. */ def absolute: AbstractFile diff --git a/src/compiler/scala/tools/nsc/io/File.scala b/src/compiler/scala/tools/nsc/io/File.scala index 7f5f535da4..4f27f4f8a5 100644 --- a/src/compiler/scala/tools/nsc/io/File.scala +++ b/src/compiler/scala/tools/nsc/io/File.scala @@ -14,13 +14,14 @@ package io import java.io.{ FileInputStream, FileOutputStream, BufferedReader, BufferedWriter, InputStreamReader, OutputStreamWriter, BufferedInputStream, BufferedOutputStream, IOException, File => JFile } -import java.nio.channels.FileChannel +import java.nio.channels.{ Channel, FileChannel } import collection.Traversable import scala.io.Codec object File { def pathSeparator = JFile.pathSeparator + def separator = JFile.separator def apply(path: Path)(implicit codec: Codec = null) = if (codec != null) new File(path.jfile)(codec) @@ -30,7 +31,6 @@ object File def makeTemp(prefix: String = Path.randomPrefix, suffix: String = null, dir: JFile = null) = apply(JFile.createTempFile(prefix, suffix, dir)) - import java.nio.channels.Channel type Closeable = { def close(): Unit } def closeQuietly(target: Closeable) { try target.close() catch { case e: IOException => } diff --git a/src/compiler/scala/tools/nsc/io/Path.scala b/src/compiler/scala/tools/nsc/io/Path.scala index b3f7ea5ab5..39fba8bcb2 100644 --- a/src/compiler/scala/tools/nsc/io/Path.scala +++ b/src/compiler/scala/tools/nsc/io/Path.scala @@ -30,6 +30,8 @@ import scala.util.Random.nextASCIIString object Path { + def isJarOrZip(f: Path) = (f hasExtension "zip") || (f hasExtension "jar") + // 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) @@ -137,6 +139,12 @@ class Path private[io] (val jfile: JFile) case -1 => "" case idx => name drop (idx + 1) } + // compares against extension in a CASE INSENSITIVE way. + def hasExtension(other: String) = extension.toLowerCase == other.toLowerCase + + // conditionally execute + def ifFile[T](f: File => T): Option[T] = if (isFile) Some(f(toFile)) else None + def ifDirectory[T](f: Directory => T): Option[T] = if (isDirectory) Some(f(toDirectory)) else None // Boolean tests def canRead = jfile.canRead() diff --git a/src/compiler/scala/tools/nsc/io/Streamable.scala b/src/compiler/scala/tools/nsc/io/Streamable.scala index 4dc0745534..a55028bc25 100644 --- a/src/compiler/scala/tools/nsc/io/Streamable.scala +++ b/src/compiler/scala/tools/nsc/io/Streamable.scala @@ -101,6 +101,14 @@ object Streamable */ def bufferedReader(codec: Codec = getCodec()) = new BufferedReader(reader(codec)) + /** Creates an InputStream and applies the closure, automatically closing it on completion. + */ + def applyReader[T](f: BufferedReader => T): T = { + val in = bufferedReader() + try f(in) + finally in.close() + } + /** Convenience function to import entire file into a String. */ def slurp(codec: Codec = getCodec()) = chars(codec).mkString diff --git a/src/compiler/scala/tools/nsc/util/ArgumentsExpander.scala b/src/compiler/scala/tools/nsc/util/ArgumentsExpander.scala index 2d8fc8c502..fb90f1d3d1 100644 --- a/src/compiler/scala/tools/nsc/util/ArgumentsExpander.scala +++ b/src/compiler/scala/tools/nsc/util/ArgumentsExpander.scala @@ -1,43 +1,39 @@ package scala.tools.nsc package util -import java.io.{FileReader, BufferedReader, StreamTokenizer, FileNotFoundException} -import scala.tools.nsc.io.AbstractFile -import scala.collection.mutable.ListBuffer +import java.io.{ StreamTokenizer, FileNotFoundException } +import scala.tools.nsc.io.File /** * Expands all arguments starting with @ to the contents of the * file named like each argument. */ object ArgumentsExpander { - - def expandArg(arg: String): List[String] = - expandFromFile(arg.substring(1)) + def expandArg(arg: String): List[String] = { + require(arg.nonEmpty && arg.head == '@') + expandFromFile(arg drop 1) + } /* * Extracts all the arguments in a specified file. * Throws FileNotFoundException if the file does not exist. */ - private def expandFromFile(fileName: String): List[String] = { - val f = AbstractFile.getFile(fileName) - if (f eq null) throw new FileNotFoundException( - "argument file "+ fileName +" could not be found") - - val in = new BufferedReader(new FileReader(f.file)) + def expandFromFile(fileName: String): List[String] = { + val file = File(fileName) + if (!file.exists) + throw new FileNotFoundException("argument file %s could not be found" format fileName) - val tokenizer = new StreamTokenizer( in ) - tokenizer.resetSyntax - tokenizer.wordChars(' ', 255) - tokenizer.whitespaceChars(0, ' ') - tokenizer.commentChar('#') - tokenizer.quoteChar('"') - tokenizer.quoteChar('\'') + file applyReader { in => + val tokenizer = new StreamTokenizer( in ) + tokenizer.resetSyntax + tokenizer.wordChars(' ', 255) + tokenizer.whitespaceChars(0, ' ') + tokenizer.commentChar('#') + tokenizer.quoteChar('"') + tokenizer.quoteChar('\'') - val ts = new ListBuffer[String] - while (tokenizer.nextToken() != StreamTokenizer.TT_EOF) { - ts += tokenizer.sval + def getToken = if (tokenizer.nextToken == StreamTokenizer.TT_EOF) None else Some(tokenizer.sval) + Iterator continually getToken takeWhile (_.isDefined) map (_.get) toList } - in.close() - ts.toList } } diff --git a/src/compiler/scala/tools/nsc/util/ClassPath.scala b/src/compiler/scala/tools/nsc/util/ClassPath.scala index b00e4c0950..6be887396a 100644 --- a/src/compiler/scala/tools/nsc/util/ClassPath.scala +++ b/src/compiler/scala/tools/nsc/util/ClassPath.scala @@ -8,13 +8,12 @@ package scala.tools.nsc package util -import java.io.File +import java.io.{ File => JFile } import java.net.URL -import java.util.StringTokenizer -import scala.util.Sorting import scala.collection.mutable.{ListBuffer, ArrayBuffer, HashSet => MutHashSet} -import scala.tools.nsc.io.AbstractFile +import io.{ File, Directory, Path, AbstractFile } +import scala.tools.util.StringOps.splitWhere /** <p> * This module provides star expansion of '-classpath' option arguments, behaves the same as @@ -26,26 +25,21 @@ import scala.tools.nsc.io.AbstractFile object ClassPath { /** Expand single path entry */ private def expandS(pattern: String): List[String] = { - def isJar(name: String) = name.toLowerCase endsWith ".jar" + val wildSuffix = File.separator + "*" /** Get all jars in directory */ - def lsJars(f: File, filt: String => Boolean = _ => true) = { - val list = f.listFiles() - if (list eq null) Nil - else list.filter(f => f.isFile() && filt(f.getName) && isJar(f.getName())).map(_.getPath()).toList - } - - val suffix = File.separator + "*" + def lsJars(dir: Directory, filt: String => Boolean = _ => true) = + dir.files partialMap { case f if filt(f.name) && (f hasExtension "jar") => f.path } toList def basedir(s: String) = if (s contains File.separator) s.substring(0, s.lastIndexOf(File.separator)) else "." - if (pattern == "*") lsJars(new File(".")) - else if (pattern endsWith suffix) lsJars(new File(pattern dropRight 2)) + if (pattern == "*") lsJars(Directory(".")) + else if (pattern endsWith wildSuffix) lsJars(Directory(pattern dropRight 2)) else if (pattern contains '*') { val regexp = ("^%s$" format pattern.replaceAll("""\*""", """.*""")).r - lsJars(new File(basedir(pattern)), regexp findFirstIn _ isDefined) + lsJars(Directory(pattern).parent, regexp findFirstIn _ isDefined) } else List(pattern) } @@ -56,9 +50,21 @@ object ClassPath { /** Expand path and possibly expanding stars */ def expandPath(path: String, expandStar: Boolean = true): List[String] = - if (expandStar) splitPath(path).flatMap(expandS(_)) + if (expandStar) splitPath(path) flatMap expandS else splitPath(path) + /** Expand dir out to contents */ + def expandDir(extdir: String): List[String] = { + for { + dir <- Option(AbstractFile getDirectory extdir).toList + file <- dir + if file.isDirectory || (file hasExtension "jar") || (file hasExtension "zip") + } + yield { + (dir.sfile / file.name).path + } + } + /** A useful name filter. */ def isTraitImplementation(name: String) = name endsWith "$class.class" @@ -151,32 +157,27 @@ abstract class ClassPath[T] { def validSourceFile(name: String) = validSourceExtensions exists (name endsWith _) def validSourceExtensions = List(".scala", ".java") - /** Utility */ - protected final def dropExtension(name: String) = name take (name.lastIndexOf('.') - 1) - /** * Find a ClassRep given a class name of the form "package.subpackage.ClassName". * Does not support nested classes on .NET */ - def findClass(name: String): Option[AnyClassRep] = { - val i = name.indexOf('.') - if (i < 0) { - classes.find(_.name == name) - } else { - val pkg = name take i - val rest = name drop (i + 1) - (packages find (_.name == pkg) flatMap (_ findClass rest)) map { - case x: ClassRep => x - case x => throw new FatalError("Unexpected ClassRep '%s' found searching for name '%s'".format(x, name)) - } + def findClass(name: String): Option[AnyClassRep] = + splitWhere(name, _ == '.', true) match { + case Some((pkg, rest)) => + val rep = packages find (_.name == pkg) flatMap (_ findClass rest) + rep map { + case x: ClassRep => x + case x => throw new FatalError("Unexpected ClassRep '%s' found searching for name '%s'".format(x, name)) + } + case _ => + classes find (_.name == name) } - } - def findSourceFile(name: String): Option[AbstractFile] = { + + def findSourceFile(name: String): Option[AbstractFile] = findClass(name) match { case Some(ClassRep(Some(x: AbstractFile), _)) => Some(x) case _ => None } - } } trait AbstractFileClassPath extends ClassPath[AbstractFile] { @@ -235,7 +236,7 @@ abstract class MergedClassPath[T] extends ClassPath[T] { val cls = new ListBuffer[AnyClassRep] for (e <- entries; c <- e.classes) { val name = c.name - val idx = cls.indexWhere(cl => cl.name == name) + val idx = cls.indexWhere(_.name == name) if (idx >= 0) { val existing = cls(idx) if (existing.binary.isEmpty && c.binary.isDefined) @@ -253,7 +254,7 @@ abstract class MergedClassPath[T] extends ClassPath[T] { val pkg = new ListBuffer[ClassPath[T]] for (e <- entries; p <- e.packages) { val name = p.name - val idx = pkg.indexWhere(pk => pk.name == name) + val idx = pkg.indexWhere(_.name == name) if (idx >= 0) { pkg(idx) = addPackage(pkg(idx), p) } else { @@ -263,17 +264,14 @@ abstract class MergedClassPath[T] extends ClassPath[T] { pkg.toList } - lazy val sourcepaths: List[AbstractFile] = entries.flatMap(_.sourcepaths) + lazy val sourcepaths: List[AbstractFile] = entries flatMap (_.sourcepaths) - private def addPackage(to: ClassPath[T], pkg: ClassPath[T]) = { - def expand = to match { - case cp: MergedClassPath[_] => cp.entries - case _ => List(to) - } - newMergedClassPath(expand :+ pkg) - } + private def addPackage(to: ClassPath[T], pkg: ClassPath[T]) = newMergedClassPath(to match { + case cp: MergedClassPath[_] => cp.entries :+ pkg + case _ => List(to, pkg) + }) - private def newMergedClassPath(entrs: List[ClassPath[T]]) = + private def newMergedClassPath(entrs: List[ClassPath[T]]): MergedClassPath[T] = new MergedClassPath[T] { protected val entries = entrs val context = outer.context @@ -301,47 +299,32 @@ class JavaClassPath( import ClassPath._ val etr = new ListBuffer[ClassPath[AbstractFile]] - def addFilesInPath(path: String, expand: Boolean, ctr: AbstractFile => ClassPath[AbstractFile] = x => createDirectoryPath(x)) { - for (fileName <- expandPath(path, expandStar = expand)) { - val file = AbstractFile.getDirectory(fileName) - if (file ne null) etr += ctr(file) - } - } + def addFilesInPath(path: String, expand: Boolean, ctr: AbstractFile => ClassPath[AbstractFile]): Unit = + for (fileName <- expandPath(path, expand) ; file <- Option(AbstractFile getDirectory fileName)) yield + etr += ctr(file) + + def addContentsOfDir(path: String): Unit = + for (name <- expandDir(path) ; archive <- Option(AbstractFile getDirectory name)) + etr += createDirectoryPath(archive) + + def addContentsOfURL(spec: String): Unit = + for (url <- specToURL(spec) ; archive <- Option(AbstractFile getURL url)) + etr += createDirectoryPath(archive) // 1. Boot classpath - addFilesInPath(boot, false) + addFilesInPath(boot, false, createDirectoryPath) // 2. Ext classpath - for (fileName <- expandPath(ext, expandStar = false)) { - val dir = AbstractFile.getDirectory(fileName) - if (dir ne null) { - for (file <- dir) { - val name = file.name.toLowerCase - if (name.endsWith(".jar") || name.endsWith(".zip") || file.isDirectory) { - val archive = AbstractFile.getDirectory(new File(dir.file, name)) - if (archive ne null) etr += createDirectoryPath(archive) - } - } - } - } + addContentsOfDir(ext) // 3. User classpath - addFilesInPath(user, true) + addFilesInPath(user, true, createDirectoryPath) // 4. Codebase entries (URLs) - { - for { - spec <- Xcodebase.trim split " " - url <- specToURL(spec) - archive <- Option(AbstractFile getURL url) - } { - etr += createDirectoryPath(archive) - } - } + Xcodebase.trim split " " foreach addContentsOfURL // 5. Source path - if (source != "") - addFilesInPath(source, false, x => createSourcePath(x)) + addFilesInPath(source, false, createSourcePath) etr.toList } diff --git a/src/compiler/scala/tools/util/PathResolver.scala b/src/compiler/scala/tools/util/PathResolver.scala new file mode 100644 index 0000000000..3aca9c3ee0 --- /dev/null +++ b/src/compiler/scala/tools/util/PathResolver.scala @@ -0,0 +1,313 @@ +/* NSC -- new Scala compiler + * Copyright 2006-2010 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools +package util + +import java.net.{ URL, MalformedURLException } +import nsc.{ Settings } +import nsc.util.{ ClassPath, JavaClassPath, ScalaClassLoader } +import nsc.io.{ File, Directory, Path } +import ClassPath.DefaultJavaContext +import File.{ pathSeparator } +import PartialFunction.condOpt + +// Mostly based on the specification at: +// https://lampsvn.epfl.ch/trac/scala/wiki/Classpath +// + +object PathResolver { + private def propOrElse(name: String, alt: String) = System.getProperty(name, alt) + private def envOrElse(name: String, alt: String) = Option(System getenv name) getOrElse alt + + private def fileOpt(f: Path): Option[String] = f ifFile (_.path) + private def dirOpt(d: Path): Option[String] = d ifDirectory (_.path) + private def expandToPath(p: Path) = joincp(ClassPath.expandPath(p.path, true)) + private def expandToContents(p: Path) = joincp(ClassPath.expandDir(p.path)) + private def joincp(xs: Seq[String]): String = xs filterNot (_ == "") mkString pathSeparator + private def splitcp(cp: String): Seq[String] = cp split pathSeparator filterNot (_ == "") toSeq + + /** pretty print class path */ + def ppcp(s: String) = splitcp(s) match { + case Nil => "" + case Seq(x) => x + case xs => xs map ("\n" + _) mkString + } + + /** Values found solely by inspecting environment or property variables. + */ + object Environment { + private def searchForBootClasspath = { + import scala.collection.JavaConversions._ + System.getProperties find (_._1 endsWith ".boot.class.path") map (_._2) getOrElse "" + } + + def classPathEnv = envOrElse("CLASSPATH", "") + def toolPathEnv = envOrElse("TOOL_CLASSPATH", "") + def classPathProp = propOrElse("java.class.path", "") + def javaBootClassPath = propOrElse("sun.boot.class.path", searchForBootClasspath) + def javaExtDirs = propOrElse("java.ext.dirs", "") + def userHome = propOrElse("user.home", "") + def scalaHome = propOrElse("scala.home", "") + + def classPath = List(classPathProp, classPathEnv) find (_ != "") getOrElse "." + + override def toString = """ + |object Environment { + | classPathEnv = %s + | toolPathEnv = %s + | classPathProp = %s + | javaBootClassPath = %s + | javaExtDirs = %s + | userHome = %s + | scalaHome = %s + |}""".trim.stripMargin.format( + classPathEnv, toolPathEnv, ppcp(classPathProp), ppcp(javaBootClassPath), + javaExtDirs, userHome, scalaHome + ) + } + + /** Default values based on those in Environment as interpretered according + * to the path resolution specification. + */ + object Defaults { + private lazy val guessedScalaHome = { + for (url <- ScalaClassLoader originOfClass classOf[ScalaObject] ; if url.getProtocol == "file") yield + File(url.getFile).parent.path + } getOrElse "" + + private def translateScalaHome(s: String) = s.replaceAll("""${SCALA_HOME}""", scalaHome) + + def scalaHome = Environment.scalaHome match { case "" => guessedScalaHome ; case x => x } + def scalaHomeDir = Directory(scalaHome) + def scalaLibDir = Directory(scalaHomeDir / "lib") + def scalaClassesDir = Directory(scalaHomeDir / "classes") + + def scalaLibJar = File(scalaLibDir / "scala-library.jar") + def scalaLibClassDir = Directory(scalaClassesDir / "library") + def scalaLibFound: Option[Directory] = + if (scalaLibJar.isFile) Some(scalaLibDir) + else if (scalaLibClassDir.isDirectory) Some(scalaClassesDir) + else None + def scalaLibPath = scalaLibFound map (_.path) getOrElse "" + + def scalaExtDirs = scalaLibFound map (_.path) getOrElse "" + def expandedScalaExtDirs = if (scalaExtDirs == "") "" else expandToContents(scalaExtDirs) + def expandedClassPath = expandToPath(Environment.classPath) + + val pluginSearchPath = List("misc", "scala-devel", "plugins") + def scalaPluginDir = pluginSearchPath map (scalaHomeDir / _) find (_.isDirectory) map (_.path) getOrElse "" + + // The class path that a runner script uses to interpret a program is called the “execution class path”. + // The execution class path is the concatenation of the following sub-path. + // If a class is available in multiple locations, it must be loaded from that with the lowest number. + def executionPath = List( + // 1. The Java bootstrap class path. + Environment.javaBootClassPath, + // 2. The Java extension class path. + Environment.javaExtDirs, + // 3. The first available path below. + // * The fixed class path (TOOL_CLASSPATH) set in the runner script when it was generated + // (which can be set as the "classpath" attribute when using the scalatool Ant task). + // This path may contain absolute locations, or locations relative to Scala's home by using + // the shell variable ${SCALA_HOME} in the path. + // * The class path formed by all JAR and ZIP files and all folders in Scala's home lib folder. + Environment.toolPathEnv match { + case "" => Defaults.expandedScalaExtDirs + case x => expandToPath(translateScalaHome(x)) + } + ) + + override def toString = """ + |object Defaults { + | scalaHome = %s + | scalaLibFound = %s + | scalaPluginDir = %s + | expandedClassPath = %s + |}""".trim.stripMargin.format( + scalaHome, scalaLibFound, scalaPluginDir, + ppcp(expandedScalaExtDirs), ppcp(expandedClassPath) + ) + } + + def executionPath = joincp(Defaults.executionPath) + + /** The original logic of MainGenericRunner. + */ + def basicScalaClassPath(includeCwd: Boolean): String = { + // this is to make the interpreter work when running without the scala script + // (e.g. from eclipse). Before, "java.class.path" was added to the user classpath + // in Settings; this was changed to match the behavior of Sun's javac. + def maincp = + if (Environment.scalaHome == "") Defaults.expandedClassPath + else Defaults.expandedScalaExtDirs + def dotcp = if (includeCwd) "." else "" + + joincp(Seq(maincp, dotcp)) + } + + /** XXX not yet used. + */ + def toJavaClassPath(settings: Settings): JavaClassPath = { + new JavaClassPath( + settings.bootclasspath.value, settings.extdirs.value, + settings.classpath.value, settings.sourcepath.value, + settings.Xcodebase.value, DefaultJavaContext + ) + } + + /** With no arguments, show the interesting values in Environment and Defaults. + * If there are arguments, show those in Calculated as if those options had been + * given to a scala runner. + */ + def main(args: Array[String]): Unit = { + if (args.isEmpty) { + println(Environment) + println(Defaults) + } + else { + val settings = new Settings() + val rest = settings.processArguments(args.toList)._2 + val pr = new PathResolver(settings) + println(" COMMAND: 'scala %s'".format(args.mkString(" "))) + println("RESIDUAL: 'scala %s'\n".format(rest.mkString(" "))) + println(pr.Calculated) + } + } + + /** + * Split command line parameters by space, properly process quoted parameter + */ + def splitParams(line: String, errorFn: String => Unit): List[String] = { + def parse(from: Int, i: Int, args: List[String]): List[String] = { + if (i < line.length) { + line.charAt(i) match { + case ' ' => + val args1 = fetchArg(from, i) :: args + val j = skipS(i + 1) + if (j >= 0) { + parse(j, j, args1) + } else args1 + case '"' => + val j = skipTillQuote(i + 1) + if (j > 0) { + parse(from, j + 1, args) + } else { + errorFn("Parameters '" + line + "' with unmatched quote at " + i + ".") + Nil + } + case _ => parse(from, i + 1, args) + } + } else { // done + if (i > from) { + fetchArg(from, i) :: args + } else args + } + } + + def fetchArg(from: Int, until: Int) = { + if (line.charAt(from) == '"') { + line.substring(from + 1, until - 1) + } else { + line.substring(from, until) + } + } + + def skipTillQuote(i: Int): Int = { + if (i < line.length) { + line.charAt(i) match { + case '"' => i + case _ => skipTillQuote(i + 1) + } + } else -1 + } + + def skipS(i: Int): Int = { + if (i < line.length) { + line.charAt(i) match { + case ' ' => skipS(i + 1) + case _ => i + } + } else -1 + } + + // begin split + val j = skipS(0) + if (j >= 0) { + parse(j, j, Nil).reverse + } else Nil + } +} +import PathResolver.{ Defaults, Environment, joincp, splitcp, ppcp } + +class PathResolver(settings: Settings) { + private def cmdLineOrElse(name: String, alt: String) = { + (commandLineFor(name) match { + case Some("") => None + case x => x + }) getOrElse alt + } + + private def commandLineFor(s: String): Option[String] = condOpt(s) { + case "javabootclasspath" => settings.javabootclasspath.value + case "javaextdirs" => settings.javaextdirs.value + case "bootclasspath" => settings.bootclasspath.value + case "extdirs" => settings.extdirs.value + case "classpath" | "cp" => settings.classpath.value + case "sourcepath" => settings.sourcepath.value + case "Ycodebase" => settings.Xcodebase.value + } + + /** Calculated values based on any given command line options, falling back on + * those in Defaults. + */ + object Calculated { + def javaBootClassPath = cmdLineOrElse("javabootclasspath", Environment.javaBootClassPath) + def javaExtDirs = cmdLineOrElse("javaextdirs", Environment.javaExtDirs) + def scalaBootClassPath = cmdLineOrElse("bootclasspath", Defaults.scalaLibPath) + def scalaExtDirs = cmdLineOrElse("extdirs", Defaults.scalaExtDirs) + def sourcePath = cmdLineOrElse("sourcepath", "") + def codeBase = cmdLineOrElse("Ycodebase", "") // XXX + def classPath = cmdLineOrElse("cp", Environment.classPath) + + def referencePath = List( + // 1. The value of -javabootclasspath if it is set, or the Java bootstrap class path. + javaBootClassPath, + // 2. The value of -bootclasspath if it is set, + // or the lib/scala-library.jar file of Scala's home if it is available, + // or the classes/library folder of Scala's home if it is available. + scalaBootClassPath, + // 3. All JAR and ZIP files present in any folder listed by the value of -javaextdirs, if it is set, + // or the Java extension class path. + javaExtDirs, + // 4. All JAR and ZIP files present in any folder listed by the value of -extdirs, if it is set. + scalaExtDirs, + // 5. The first available path below. + // * The value of -classpath or -cp. + // * The value of the CLASSPATH environment variable. + // * The current directory (that is the location of "."). + // + // XXX doesn't mention java.class.path + classPath + ) + + override def toString = """ + |object Calculated { + | javaBootClassPath = %s + | javaExtDirs = %s + | scalaBootClassPath = %s + | scalaExtDirs = %s + | sourcePath = %s + | codeBase = %s + | classPath = %s + |}""".trim.stripMargin.format( + ppcp(javaBootClassPath), ppcp(javaExtDirs), + ppcp(scalaBootClassPath), ppcp(scalaExtDirs), + ppcp(sourcePath), codeBase, ppcp(classPath) + ) + } + + def referencePath = joincp(Calculated.referencePath) +} diff --git a/src/compiler/scala/tools/util/StringOps.scala b/src/compiler/scala/tools/util/StringOps.scala index 01b2482147..21ece6490f 100644 --- a/src/compiler/scala/tools/util/StringOps.scala +++ b/src/compiler/scala/tools/util/StringOps.scala @@ -8,7 +8,8 @@ // $Id$ -package scala.tools.util +package scala.tools +package util /** This objects provides methods to extract elements from * a string according to some defined character separator. @@ -31,4 +32,11 @@ object StringOps { } def words(str: String): List[String] = decompose(str, ' ') + + def splitWhere(str: String, f: Char => Boolean, doDropIndex: Boolean = false): Option[(String, String)] = + splitAt(str, str indexWhere f, doDropIndex) + + def splitAt(str: String, idx: Int, doDropIndex: Boolean = false): Option[(String, String)] = + if (idx == -1) None + else Some(str take idx, str drop (if (doDropIndex) idx + 1 else idx)) } diff --git a/src/partest/scala/tools/partest/nest/TestFile.scala b/src/partest/scala/tools/partest/nest/TestFile.scala index a7656a3c12..3ae46399ec 100644 --- a/src/partest/scala/tools/partest/nest/TestFile.scala +++ b/src/partest/scala/tools/partest/nest/TestFile.scala @@ -8,7 +8,7 @@ package scala.tools.partest package nest -import java.io.{File, BufferedReader, FileReader} +import java.io.File import scala.tools.nsc.Settings import scala.tools.nsc.io.{ Path, Directory } @@ -30,13 +30,8 @@ abstract class TestFile(kind: String) { settings.outdir.value = (Path(dir) / objDir).createDirectory(true).path // add additional flags found in 'testname.flags' - val flagsFile = new File(dir, fileBase + ".flags") - if (flagsFile.exists) { - val reader = new BufferedReader(new java.io.FileReader(flagsFile)) - val flags = reader.readLine - if (flags ne null) - settings.parseParams(settings.splitParams(flags)) - } + def flagsPath = Path(dir) / (fileBase + ".flags") + flagsPath ifFile { _.slurp().trim } foreach (settings processArgumentString _) } def defineSettings(settings: Settings) { |