eliminate temporary directory for injecting resources into a class loader

This commit is contained in:
Mark Harrah 2010-11-13 20:16:44 -05:00
parent fdb4a98d8b
commit de3ad8c860
4 changed files with 86 additions and 36 deletions

View File

@ -11,11 +11,11 @@ import classpath.ClasspathUtilities
trait ScalaRun
{
def run(mainClass: String, classpath: Iterable[Path], options: Seq[String], log: Logger): Option[String]
def run(mainClass: String, classpath: Seq[File], options: Seq[String], log: Logger): Option[String]
}
class ForkRun(config: ForkScalaRun) extends ScalaRun
{
def run(mainClass: String, classpath: Iterable[Path], options: Seq[String], log: Logger): Option[String] =
def run(mainClass: String, classpath: Seq[File], options: Seq[String], log: Logger): Option[String] =
{
val scalaOptions = classpathOption(classpath) ::: mainClass :: options.toList
val exitCode = config.outputStrategy match {
@ -24,7 +24,7 @@ class ForkRun(config: ForkScalaRun) extends ScalaRun
}
processExitCode(exitCode, "runner")
}
private def classpathOption(classpath: Iterable[Path]) = "-cp" :: Path.makeString(classpath) :: Nil
private def classpathOption(classpath: Seq[File]) = "-cp" :: Path.makeString(classpath) :: Nil
private def processExitCode(exitCode: Int, label: String) =
{
if(exitCode == 0)
@ -36,7 +36,7 @@ class ForkRun(config: ForkScalaRun) extends ScalaRun
class Run(instance: ScalaInstance) extends ScalaRun
{
/** Runs the class 'mainClass' using the given classpath and options using the scala runner.*/
def run(mainClass: String, classpath: Iterable[Path], options: Seq[String], log: Logger) =
def run(mainClass: String, classpath: Seq[File], options: Seq[String], log: Logger) =
{
log.info("Running " + mainClass + " " + options.mkString(" "))
@ -46,16 +46,12 @@ class Run(instance: ScalaInstance) extends ScalaRun
Run.executeTrapExit( execute, log )
}
private def run0(mainClassName: String, classpath: Iterable[Path], options: Seq[String], log: Logger)
private def run0(mainClassName: String, classpath: Seq[File], options: Seq[String], log: Logger)
{
log.debug(" Classpath:\n\t" + classpath.mkString("\n\t"))
val (loader, tempDir) = ClasspathUtilities.makeLoader(classpath, instance)
try
{
val main = getMainMethod(mainClassName, loader)
invokeMain(loader, main, options)
}
finally { IO.delete(tempDir asFile) }
val loader = ClasspathUtilities.makeLoader(classpath, instance)
val main = getMainMethod(mainClassName, loader)
invokeMain(loader, main, options)
}
private def invokeMain(loader: ClassLoader, main: Method, options: Seq[String])
{
@ -79,7 +75,7 @@ class Run(instance: ScalaInstance) extends ScalaRun
/** This module is an interface to starting the scala interpreter or runner.*/
object Run
{
def run(mainClass: String, classpath: Iterable[Path], options: Seq[String], log: Logger)(implicit runner: ScalaRun) =
def run(mainClass: String, classpath: Seq[File], options: Seq[String], log: Logger)(implicit runner: ScalaRun) =
runner.run(mainClass, classpath, options, log)
/** Executes the given function, trapping calls to System.exit. */

View File

@ -4,6 +4,7 @@
package sbt
package classpath
import java.lang.ref.{Reference, SoftReference, WeakReference}
import java.io.File
import java.net.{URI, URL, URLClassLoader}
import java.util.Collections
@ -13,12 +14,14 @@ import IO.{createTemporaryDirectory, write}
object ClasspathUtilities
{
def toClasspath(finder: PathFinder): Array[URL] = finder.getURLs
def toClasspath(paths: Iterable[Path]): Array[URL] = Path.getURLs(paths)
def toLoader(finder: PathFinder): ClassLoader = toLoader(finder.get)
def toLoader(finder: PathFinder, parent: ClassLoader): ClassLoader = toLoader(finder.get, parent)
def toLoader(paths: Iterable[Path]): ClassLoader = toLoader(paths, rootLoader)
def toLoader(paths: Iterable[Path], parent: ClassLoader): ClassLoader = new URLClassLoader(toClasspath(paths), parent)
def toLoader(finder: PathFinder): ClassLoader = toLoader(finder, rootLoader)
def toLoader(finder: PathFinder, parent: ClassLoader): ClassLoader = new URLClassLoader(finder.getURLs, parent)
def toLoader(paths: Seq[File]): ClassLoader = toLoader(paths, rootLoader)
def toLoader(paths: Seq[File], parent: ClassLoader): ClassLoader = new URLClassLoader(Path.toURLs(paths), parent)
def toLoader(paths: Seq[File], parent: ClassLoader, resourceMap: Map[String,String]): ClassLoader =
new URLClassLoader(Path.toURLs(paths), parent) with RawResources { override def resources = resourceMap }
lazy val rootLoader =
{
@ -33,28 +36,20 @@ object ClasspathUtilities
final val AppClassPath = "app.class.path"
final val BootClassPath = "boot.class.path"
def createClasspathResources(classpath: Iterable[Path], instance: ScalaInstance, baseDir: Path): Iterable[Path] =
createClasspathResources(classpath ++ Path.fromFiles(instance.jars), Path.fromFiles(instance.jars), baseDir)
def createClasspathResources(classpath: Seq[File], instance: ScalaInstance): Map[String,String] =
createClasspathResources(classpath ++ instance.jars, instance.jars)
def createClasspathResources(appPaths: Iterable[Path], bootPaths: Iterable[Path], baseDir: Path): Iterable[Path] =
def createClasspathResources(appPaths: Seq[File], bootPaths: Seq[File]): Map[String, String] =
{
def writePaths(name: String, paths: Iterable[Path]): Unit =
write(baseDir / name asFile, Path.makeString(paths))
writePaths(AppClassPath, appPaths)
writePaths(BootClassPath, bootPaths)
appPaths ++ Seq(baseDir)
def make(name: String, paths: Seq[File]) = name -> Path.makeString(paths)
Map( make(AppClassPath, appPaths), make(BootClassPath, bootPaths) )
}
/** The client is responsible for cleaning up the temporary directory.*/
def makeLoader[T](classpath: Iterable[Path], instance: ScalaInstance): (ClassLoader, Path) =
def makeLoader[T](classpath: Seq[File], instance: ScalaInstance): ClassLoader =
makeLoader(classpath, instance.loader, instance)
/** The client is responsible for cleaning up the temporary directory.*/
def makeLoader[T](classpath: Iterable[Path], parent: ClassLoader, instance: ScalaInstance): (ClassLoader, Path) =
{
val dir = Path.fromFile(createTemporaryDirectory)
val modifiedClasspath = createClasspathResources(classpath, instance, dir)
(toLoader(modifiedClasspath, parent), dir)
}
def makeLoader[T](classpath: Seq[File], parent: ClassLoader, instance: ScalaInstance): ClassLoader =
toLoader(classpath, parent, createClasspathResources(classpath, instance))
private[sbt] def printSource(c: Class[_]) =
println(c.getName + " loader=" +c.getClassLoader + " location=" + IO.classLocationFile(c))

View File

@ -0,0 +1,54 @@
/* sbt -- Simple Build Tool
* Copyright 2010 Mark Harrah
*/
package sbt
package classpath
import java.io.{ByteArrayInputStream, InputStream}
import java.net.{Proxy, URL, URLConnection, URLStreamHandler}
import java.util.Enumeration
object RawURL
{
def apply(file: String, value: String): URL =
apply(file, value.getBytes)
def apply(file: String, value: Array[Byte]): URL =
apply(file)(new ByteArrayInputStream(value))
def apply(file: String)(value: => InputStream): URL =
new URL("raw", null, -1, file, new RawStreamHandler(value))
private[this] final class RawStreamHandler(value: => InputStream) extends URLStreamHandler
{
override protected def openConnection(url: URL, p: Proxy): URLConnection =
openConnection(url)
override protected def openConnection(url: URL): URLConnection =
new URLConnection(url)
{
private lazy val in = value
def connect() { in }
override def getInputStream = in
}
}
}
trait RawResources extends FixedResources
{
protected def resources: Map[String, String]
override protected final val resourceURL = resources.transform(RawURL.apply)
}
trait FixedResources extends ClassLoader
{
protected def resourceURL: Map[String, URL]
override def findResource(s: String): URL = resourceURL.getOrElse(s, super.findResource(s))
import java.util.Collections.{enumeration, singletonList}
override def findResources(s: String): Enumeration[URL] =
{
val sup = super.findResources(s)
resourceURL.get(s) match
{
case Some(url) => new DualEnumeration(enumeration(singletonList(url)), sup)
case None => sup
}
}
}

View File

@ -148,6 +148,9 @@ object Path extends PathExtra
/** Constructs a String representation of <code>Path</code>s. The absolute path String of each <code>Path</code> is
* separated by the given separator String.*/
def makeString(paths: Iterable[Path], sep: String): String = paths.map(_.absolutePath).mkString(sep)
def makeString(paths: Seq[File]): String = makeString(paths, pathSeparator)
def makeString(paths: Seq[File], sep: String): String = paths.map(_.getAbsolutePath).mkString(sep)
/** Constructs a String representation of <code>Path</code>s. The relative path String of each <code>Path</code> is
* separated by the platform's path separator.*/
@ -262,6 +265,8 @@ object Path extends PathExtra
def getFiles(files: Traversable[Path]): immutable.Set[File] = files.map(_.asFile).toSet
def getURLs(files: Traversable[Path]): Array[URL] = files.map(_.asURL).toArray
def toURLs(files: Seq[File]): Array[URL] = files.map(_.toURI.toURL).toArray
}
/** A path finder constructs a set of paths. The set is evaluated by a call to the <code>get</code>