From de3ad8c8602fa10791064b6571694709f33a4966 Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Sat, 13 Nov 2010 20:16:44 -0500 Subject: [PATCH] eliminate temporary directory for injecting resources into a class loader --- run/Run.scala | 22 +++++----- util/classpath/ClasspathUtilities.scala | 41 +++++++++---------- util/classpath/RawURL.scala | 54 +++++++++++++++++++++++++ util/io/Path.scala | 5 +++ 4 files changed, 86 insertions(+), 36 deletions(-) create mode 100644 util/classpath/RawURL.scala diff --git a/run/Run.scala b/run/Run.scala index 344fe9ea2..60581374c 100644 --- a/run/Run.scala +++ b/run/Run.scala @@ -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. */ diff --git a/util/classpath/ClasspathUtilities.scala b/util/classpath/ClasspathUtilities.scala index c966697b9..5e27b215b 100644 --- a/util/classpath/ClasspathUtilities.scala +++ b/util/classpath/ClasspathUtilities.scala @@ -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)) diff --git a/util/classpath/RawURL.scala b/util/classpath/RawURL.scala new file mode 100644 index 000000000..645dcb10d --- /dev/null +++ b/util/classpath/RawURL.scala @@ -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 + } + } +} \ No newline at end of file diff --git a/util/io/Path.scala b/util/io/Path.scala index ca66c9684..e198552cc 100644 --- a/util/io/Path.scala +++ b/util/io/Path.scala @@ -148,6 +148,9 @@ object Path extends PathExtra /** Constructs a String representation of Paths. The absolute path String of each Path 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 Paths. The relative path String of each Path 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 get