summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/util/PathResolver.scala
diff options
context:
space:
mode:
authorPaul Phillips <paulp@improving.org>2010-02-08 22:28:17 +0000
committerPaul Phillips <paulp@improving.org>2010-02-08 22:28:17 +0000
commit171d21f11a58e905de8f96a9bac41f6aced3e384 (patch)
tree1abc4a6ca8597e517b78f2faef13064d7c69cfc6 /src/compiler/scala/tools/util/PathResolver.scala
parent53545e7af8d3455f97d88c1a455fea86461419fb (diff)
downloadscala-171d21f11a58e905de8f96a9bac41f6aced3e384.tar.gz
scala-171d21f11a58e905de8f96a9bac41f6aced3e384.tar.bz2
scala-171d21f11a58e905de8f96a9bac41f6aced3e384.zip
Some work on classpaths.
at https://lampsvn.epfl.ch/trac/scala/wiki/Classpath modulo some minor details which remain to be investigated. It is not entirely integrated, and should not involve any behavioral changes. The patch also contains a number of small improvements targetting widely duplicated code. PathResolver offers a main method. If run with no arguments it will output a pile of information about classpath relevant environment vars and properties. If given arguments, it will output the classpath info that any scala runner script would use if given the same args. There is a wrapper in the tools directory. Example: tools/pathResolver -extdirs /foo -sourcepath /bar | egrep "sourcePath|scalaExtDirs" scalaExtDirs = /foo sourcePath = /bar There is also a (probably temporary) command line option -Ylog-classpath which will print out the settings.classpath value anytime it changes. Review by community.
Diffstat (limited to 'src/compiler/scala/tools/util/PathResolver.scala')
-rw-r--r--src/compiler/scala/tools/util/PathResolver.scala313
1 files changed, 313 insertions, 0 deletions
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)
+}