summaryrefslogblamecommitdiff
path: root/src/compiler/scala/tools/util/PathResolver.scala
blob: 9feb61e0a3990c37ff7b8af1a6c1568d976badfa (plain) (tree)
1
2
3
4
5
6
7
8
9








                                              
                                              

                                                              
                                                                 






                                                    














                                                                       

                                                                                              
                                                                                    


                                                                            

                                                                                              
 



                                                                                 
                                
                                        













                                                                                          
                                                                             
                                                                                       
                                                               


                                                             
                                                              


                               
                                
                                


                                
                                
                                    

                                                                            
       










                                                                                                           


                                                                                                                                              
 
                                                                                  



                                                               





                                                                   
               
 




                                                            







                                                                           
 
                                                      
                                                                    
                                                                               





                                                                                                                
                        
                                          


                                                                                                      



                               
                                  
                                  
                                  

                                  
                                  
                                    


                                                       


       
                                                  
                                                              
 




                                                                                           

                                                                                         




                                                                                                               
                                                                                                            
                                                                                         





                                                                                                             
                   


                                                                                                      
                                                                                  
 



                                                               
                                                     


                                                             
                                          
                                                         

   



                                                                               

                                                                       

   
                                                                                            




                                                                           
 










                                                                                   
                                                                 





































































                                                                                     
                                                                  















                                                                      
                                                         





                                                                                

                                                                                            
                                                                                         
                                                                                
                                                                             
                                                            
                                                             
                                                            
                                                                                   














                                                                                                          
                                                   

                                                                  

                    



                               
                                  
                                  
                                  
                                  
                                  
                                  
                                  
                                  
                                    
                  

                                                          

                                              


       
                                                    


                                                                                      
 
/* 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, GenericRunnerSettings }
import nsc.util.{ ClassPath, JavaClassPath, ScalaClassLoader }
import nsc.io.{ File, Directory, Path }
import ClassPath.{ JavaContext, DefaultJavaContext, join, split }
import PartialFunction.condOpt

// Mostly based on the specification at:
// https://lampsvn.epfl.ch/trac/scala/wiki/Classpath
//

object PathResolver {
  // val debugLogger = {
  //   val f = File("/tmp/path-resolve-log.txt")
  //   if (f.exists) f.truncate()
  //   else f.createFile()
  //
  //   val res = f.bufferedWriter()
  //   res write ("Started debug log: %s\n".format(new java.util.Date))
  //   res
  // }
  // def log(msg: Any) = {
  //   Console println msg
  //   debugLogger.write(msg.toString + "\n")
  //   debugLogger flush
  // }

  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 firstNonEmpty(xs: String*)            = xs find (_ != "") getOrElse ""

  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)                 = join(ClassPath.expandPath(p.path, true))
  private def expandToContents(p: Path)             = join(ClassPath.expandDir(p.path))

  /** Map all classpath elements to absolute paths and reconstruct the classpath.
    */
  def makeAbsolute(cp: String) = ClassPath.map(cp, x => Path(x).toAbsolute.path)

  /** pretty print class path */
  def ppcp(s: String) = split(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 sourcePathEnv       =  envOrElse("SOURCEPATH", "")        // not used
    def javaBootClassPath   = propOrElse("sun.boot.class.path", searchForBootClasspath)
    def javaUserClassPath   = propOrElse("java.class.path", "")
    def javaExtDirs         = propOrElse("java.ext.dirs", "")
    def userHome            = propOrElse("user.home", "")
    def scalaHome           = propOrElse("scala.home", "")
    def scalaExtDirs        = propOrElse("scala.ext.dirs", "")

    override def toString = """
      |object Environment {
      |  javaBootClassPath  = %s
      |  javaUserClassPath  = %s
      |  javaExtDirs        = %s
      |  userHome           = %s
      |  scalaHome          = %s
      |  scalaExtDirs       = %s
      |}""".trim.stripMargin.format(
        ppcp(javaBootClassPath), ppcp(javaUserClassPath), ppcp(javaExtDirs),
        userHome, scalaHome, ppcp(scalaExtDirs)
      )
  }

  /** 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 ""

    // XXX review these semantics
    def javaBootClassPath = join(Seq(Environment.javaBootClassPath, Environment.javaUserClassPath))   // ... ignoring Environment.classPathEnv
    def javaExtDirs       = Environment.javaExtDirs

    def scalaHome         = firstNonEmpty(Environment.scalaHome, guessedScalaHome)
    def scalaHomeDir      = Directory(scalaHome)
    def scalaLibDir       = Directory(scalaHomeDir / "lib")
    def scalaClassesDir   = Directory(scalaHomeDir / "classes")

    def scalaLibAsJar     = File(scalaLibDir / "scala-library.jar")
    def scalaLibAsDir     = Directory(scalaClassesDir / "library")

    def scalaLibDirFound: Option[Directory] =
      if (scalaLibAsJar.isFile) Some(scalaLibDir)
      else if (scalaLibAsDir.isDirectory) Some(scalaClassesDir)
      else None

    def scalaLibFound =
      if (scalaLibAsJar.isFile) scalaLibAsJar.path
      else if (scalaLibAsDir.isDirectory) scalaLibAsDir.path
      else ""

    // This attempt to duplicate the original logic of MainGenericRunner
    // was causing the issue described in r20878.  Obviously it's past time
    // to establish with certainty what each of these paths should contain.
    def scalaBootClassPath = ""
    // scalaLibDirFound match {
    //   case Some(dir)    => join(ClassPath expandDir dir.path)
    //   case _            => ""
    // }

    def scalaExtDirs        = Environment.scalaExtDirs
    def scalaPluginDirs     = List("misc", "scala-devel", "plugins")
    def scalaPluginPath     = join(scalaPluginDirs map (scalaHomeDir / _ path))

    // 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.
      javaBootClassPath,
      // 2. The Java extension class path.
      javaExtDirs,
      // 3. The class path formed by all JAR and ZIP files and all folders in Scala's home lib folder.
      scalaBootClassPath
    )

    override def toString = """
      |object Defaults {
      |  javaBootClassPath    = %s
      |  scalaHome            = %s
      |  scalaLibDirFound     = %s
      |  scalaLibFound        = %s
      |  scalaBootClassPath   = %s
      |  scalaPluginPath      = %s
      |}""".trim.stripMargin.format(
        ppcp(javaBootClassPath),
        scalaHome, scalaLibDirFound, scalaLibFound,
        ppcp(scalaBootClassPath), ppcp(scalaPluginPath)
      )
  }

  def executionPath = join(Defaults.executionPath)
  def executionPathURLs = fromPathString(executionPath).asURLs

  private def classPathContainersFromSettings(settings: Settings, context: JavaContext) = {
    val pr = new PathResolver(settings)
    import context._
    import pr.Calculated._

    // XXX how should the contents of lib/* break down between bootclasspath and extdirs?
    // XXX what exactly is codebase for?
    val sources = List(
      classesInPath(javaBootClassPath),           // -javabootclasspath multiple entries, no expansion
      contentsOfDirsInPath(scalaBootClassPath),   // -bootclasspath     ???
      contentsOfDirsInPath(javaExtDirs),          // -javaextdirs       multiple dirs, each expands to contents
      contentsOfDirsInPath(scalaExtDirs),         // -extdirs           ???
      classesInExpandedPath(userClassPath),       // -classpath         multiple entries, first expanding *s
      classesAtAllURLS(codeBase),                 // -Ycodebase         ??? multiple URLs
      sourcesInPath(sourcePath)                   // -sourcepath        multiple source entries, no expansion
    )

    if (settings.Ylogcp.value)
      Console.println("PathResolver calculated classpath:\n" + pr.Calculated)

    sources.flatten
  }
  def urlsFromSettings(settings: Settings): List[URL] = urlsFromSettings(settings, DefaultJavaContext)
  def urlsFromSettings(settings: Settings, context: JavaContext): List[URL] =
    classPathContainersFromSettings(settings, context) flatMap (_.asURLs) distinct

  private def contextFromSettings(s: Settings) =
    if (s.inline.value) new JavaContext else DefaultJavaContext

  def fromArgumentString(argString: String): JavaClassPath =
    fromArgumentList(splitParams(argString, _ => ()))

  def fromArgumentList(args: List[String]): JavaClassPath = {
    val settings = new Settings()
    settings.processArguments(args, false)
    fromSettings(settings, contextFromSettings(settings))
  }

  def fromSettings(settings: Settings): JavaClassPath =
    fromSettings(settings, contextFromSettings(settings))

  def fromSettings(settings: Settings, context: JavaContext): JavaClassPath = {
    val containers = classPathContainersFromSettings(settings, context)
    new JavaClassPath(containers, context)
  }

  def fromPathString(path: String): JavaClassPath = fromPathString(path, DefaultJavaContext)
  def fromPathString(path: String, context: JavaContext): JavaClassPath = {
    val s = new Settings()
    s.classpath.value = path
    fromSettings(s, context)
  }

  /** 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, false)._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, firstNonEmpty, 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.Ycodebase.value
  }

  /** Calculated values based on any given command line options, falling back on
   *  those in Defaults.
   */
  object Calculated {
    def scalaHome           = Defaults.scalaHome
    def javaBootClassPath   = cmdLineOrElse("javabootclasspath", Defaults.javaBootClassPath)
    def scalaBootClassPath  = cmdLineOrElse("bootclasspath", Defaults.scalaBootClassPath)
    def javaExtDirs         = cmdLineOrElse("javaextdirs", Defaults.javaExtDirs)
    def scalaExtDirs        = cmdLineOrElse("extdirs", Defaults.scalaExtDirs)
    def userClassPath       = cmdLineOrElse("classpath", "")
    def sourcePath          = cmdLineOrElse("sourcepath", "")
    def codeBase            = cmdLineOrElse("Ycodebase", "")
    def dotPath             = if (settings.userSuppliedClassPath == "") "." else ""

    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.
      //    *  ---> XXX what about java.class.path?
      //    * The value of the CLASSPATH environment variable.
      //    * The current directory (that is the location of ".").
      userClassPath,
      dotPath
    )

    override def toString = """
      |object Calculated {
      |  scalaHome            = %s
      |  javaBootClassPath    = %s
      |  scalaBootClassPath   = %s
      |  javaExtDirs          = %s
      |  scalaExtDirs         = %s
      |  userClassPath        = %s
      |  sourcePath           = %s
      |  referencePath        = %s
      |}""".trim.stripMargin.format(
        scalaHome,
        ppcp(javaBootClassPath), ppcp(scalaBootClassPath),
        ppcp(javaExtDirs), ppcp(scalaExtDirs),
        ppcp(userClassPath), ppcp(sourcePath),
        ppcp(PathResolver.this.referencePath)
      )
  }

  def referencePath = join(Calculated.referencePath)
  def referencePathAsURLs = ClassPath toURLs referencePath
  def minimalPath = join(Seq(Calculated.scalaBootClassPath, Calculated.userClassPath))
  def minimalPathAsURLs = ClassPath toURLs minimalPath
}