Provide application and boot classpath to running code through resources.

This commit is contained in:
Mark Harrah 2010-05-21 09:32:14 -04:00
parent 5e8e72a965
commit 600053bf2c
10 changed files with 121 additions and 20 deletions

View File

@ -1,5 +1,5 @@
/* sbt -- Simple Build Tool /* sbt -- Simple Build Tool
* Copyright 2008, 2009 Mark Harrah * Copyright 2008, 2009, 2010 Mark Harrah
*/ */
package sbt package sbt
@ -8,6 +8,8 @@ import java.net.{URI, URL, URLClassLoader}
import java.util.Collections import java.util.Collections
import scala.collection.Set import scala.collection.Set
import scala.collection.mutable.{HashSet, ListBuffer} import scala.collection.mutable.{HashSet, ListBuffer}
import xsbt.ScalaInstance
import xsbt.FileUtilities.{createTemporaryDirectory, withTemporaryDirectory, write}
object ClasspathUtilities object ClasspathUtilities
{ {
@ -15,7 +17,7 @@ object ClasspathUtilities
def toClasspath(paths: Iterable[Path]): Array[URL] = Path.getURLs(paths) def toClasspath(paths: Iterable[Path]): Array[URL] = Path.getURLs(paths)
def toLoader(finder: PathFinder): ClassLoader = toLoader(finder.get) def toLoader(finder: PathFinder): ClassLoader = toLoader(finder.get)
def toLoader(finder: PathFinder, parent: ClassLoader): ClassLoader = toLoader(finder.get, parent) def toLoader(finder: PathFinder, parent: ClassLoader): ClassLoader = toLoader(finder.get, parent)
def toLoader(paths: Iterable[Path]): ClassLoader = new URLClassLoader(toClasspath(paths), rootLoader) def toLoader(paths: Iterable[Path]): ClassLoader = toLoader(paths, rootLoader)
def toLoader(paths: Iterable[Path], parent: ClassLoader): ClassLoader = new URLClassLoader(toClasspath(paths), parent) def toLoader(paths: Iterable[Path], parent: ClassLoader): ClassLoader = new URLClassLoader(toClasspath(paths), parent)
lazy val rootLoader = lazy val rootLoader =
@ -28,6 +30,32 @@ object ClasspathUtilities
parent(getClass.getClassLoader) parent(getClass.getClassLoader)
} }
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(appPaths: Iterable[Path], bootPaths: Iterable[Path], baseDir: Path): Iterable[Path] =
{
def writePaths(name: String, paths: Iterable[Path]): Unit =
write(baseDir / name asFile, Path.makeString(paths))
writePaths(AppClassPath, appPaths)
writePaths(BootClassPath, bootPaths)
appPaths ++ Seq(baseDir)
}
/** The client is responsible for cleaning up the temporary directory.*/
def makeLoader[T](classpath: Iterable[Path], instance: ScalaInstance): (ClassLoader, Path) =
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)
}
private[sbt] def printSource(c: Class[_]) = private[sbt] def printSource(c: Class[_]) =
println(c.getName + " loader=" +c.getClassLoader + " location=" + FileUtilities.classLocationFile(c)) println(c.getName + " loader=" +c.getClassLoader + " location=" + FileUtilities.classLocationFile(c))

View File

@ -206,7 +206,8 @@ abstract class BasicScalaProject extends ScalaProject with BasicDependencyProjec
protected def getFingerprints(frameworks: Seq[TestFramework]): Fingerprints = protected def getFingerprints(frameworks: Seq[TestFramework]): Fingerprints =
{ {
import org.scalatools.testing.{SubclassFingerprint, AnnotatedFingerprint} import org.scalatools.testing.{SubclassFingerprint, AnnotatedFingerprint}
val loader = TestFramework.createTestLoader(classpath.get, buildScalaInstance.loader) val (loader, tempDir) = TestFramework.createTestLoader(classpath.get, buildScalaInstance)
xsbt.FileUtilities.delete(tempDir.asFile)
val annotations = new ListBuffer[String] val annotations = new ListBuffer[String]
val superclasses = new ListBuffer[String] val superclasses = new ListBuffer[String]
frameworks flatMap { _.create(loader, log) } flatMap(TestFramework.getTests) foreach { frameworks flatMap { _.create(loader, log) } flatMap(TestFramework.getTests) foreach {

View File

@ -10,7 +10,8 @@ import scala.tools.nsc.util.ClassPath
import java.io.File import java.io.File
import java.net.{URL, URLClassLoader} import java.net.{URL, URLClassLoader}
import java.lang.reflect.Modifier.{isPublic, isStatic} import java.lang.reflect.{Method, Modifier}
import Modifier.{isPublic, isStatic}
trait ScalaRun trait ScalaRun
{ {
@ -51,9 +52,17 @@ class Run(instance: xsbt.ScalaInstance) extends ScalaRun
} }
private def run0(mainClassName: String, classpath: Iterable[Path], options: Seq[String], log: Logger) private def run0(mainClassName: String, classpath: Iterable[Path], options: Seq[String], log: Logger)
{ {
val loader = getLoader(classpath, log) log.debug(" Classpath:\n\t" + classpath.mkString("\n\t"))
val main = getMainMethod(mainClassName, loader) val (loader, tempDir) = ClasspathUtilities.makeLoader(classpath, instance)
try
{
val main = getMainMethod(mainClassName, loader)
invokeMain(loader, main, options)
}
finally { xsbt.FileUtilities.delete(tempDir asFile) }
}
private def invokeMain(loader: ClassLoader, main: Method, options: Seq[String])
{
val currentThread = Thread.currentThread val currentThread = Thread.currentThread
val oldLoader = Thread.currentThread.getContextClassLoader() val oldLoader = Thread.currentThread.getContextClassLoader()
currentThread.setContextClassLoader(loader) currentThread.setContextClassLoader(loader)
@ -69,11 +78,6 @@ class Run(instance: xsbt.ScalaInstance) extends ScalaRun
if(!isStatic(modifiers)) throw new NoSuchMethodException(mainClassName + ".main is not static") if(!isStatic(modifiers)) throw new NoSuchMethodException(mainClassName + ".main is not static")
method method
} }
def getLoader(classpath: Iterable[Path], log: Logger) =
{
log.debug(" Classpath:\n\t" + classpath.mkString("\n\t"))
ClasspathUtilities.toLoader(classpath, instance.loader)
}
} }
/** This module is an interface to starting the scala interpreter or runner.*/ /** This module is an interface to starting the scala interpreter or runner.*/

View File

@ -311,7 +311,7 @@ trait ScalaProject extends SimpleScalaProject with FileTasks with MultiTaskProje
} }
def includeTest(test: TestDefinition) = !excludeTestsSet.contains(test.name) && testFilters.forall(filter => filter(test.name)) def includeTest(test: TestDefinition) = !excludeTestsSet.contains(test.name) && testFilters.forall(filter => filter(test.name))
val tests = HashSet.empty[TestDefinition] ++ analysis.allTests.map(_.toDefinition).filter(includeTest) val tests = HashSet.empty[TestDefinition] ++ analysis.allTests.map(_.toDefinition).filter(includeTest)
TestFramework.testTasks(frameworks, classpath.get, buildScalaInstance.loader, tests.toSeq, log, TestFramework.testTasks(frameworks, classpath.get, buildScalaInstance, tests.toSeq, log,
testListeners.readOnly, false, setup.readOnly, cleanup.readOnly, testArgsByFramework) testListeners.readOnly, false, setup.readOnly, cleanup.readOnly, testArgsByFramework)
} }
private def flatten[T](i: Iterable[Iterable[T]]) = i.flatMap(x => x) private def flatten[T](i: Iterable[Iterable[T]]) = i.flatMap(x => x)

View File

@ -6,6 +6,7 @@ package sbt
import java.net.URLClassLoader import java.net.URLClassLoader
import org.scalatools.testing.{AnnotatedFingerprint, Fingerprint, SubclassFingerprint, TestFingerprint} import org.scalatools.testing.{AnnotatedFingerprint, Fingerprint, SubclassFingerprint, TestFingerprint}
import org.scalatools.testing.{Event, EventHandler, Framework, Runner, Runner2, Logger=>TLogger} import org.scalatools.testing.{Event, EventHandler, Framework, Runner, Runner2, Logger=>TLogger}
import xsbt.ScalaInstance
object Result extends Enumeration object Result extends Enumeration
{ {
@ -120,7 +121,7 @@ object TestFramework
def testTasks(frameworks: Seq[TestFramework], def testTasks(frameworks: Seq[TestFramework],
classpath: Iterable[Path], classpath: Iterable[Path],
scalaLoader: ClassLoader, scalaInstance: ScalaInstance,
tests: Seq[TestDefinition], tests: Seq[TestDefinition],
log: Logger, log: Logger,
listeners: Seq[TestReportListener], listeners: Seq[TestReportListener],
@ -130,16 +131,17 @@ object TestFramework
testArgsByFramework: Map[TestFramework, Seq[String]]): testArgsByFramework: Map[TestFramework, Seq[String]]):
(Iterable[NamedTestTask], Iterable[NamedTestTask], Iterable[NamedTestTask]) = (Iterable[NamedTestTask], Iterable[NamedTestTask], Iterable[NamedTestTask]) =
{ {
val loader = createTestLoader(classpath, scalaLoader) val (loader, tempDir) = createTestLoader(classpath, scalaInstance)
val arguments = immutable.Map() ++ val arguments = immutable.Map() ++
( for(framework <- frameworks; created <- framework.create(loader, log)) yield ( for(framework <- frameworks; created <- framework.create(loader, log)) yield
(created, testArgsByFramework.getOrElse(framework, Nil)) ) (created, testArgsByFramework.getOrElse(framework, Nil)) )
val cleanTmp = () => FileUtilities.clean(tempDir, log)
val mappedTests = testMap(arguments.keys.toList, tests, arguments) val mappedTests = testMap(arguments.keys.toList, tests, arguments)
if(mappedTests.isEmpty) if(mappedTests.isEmpty)
(new NamedTestTask(TestStartName, None) :: Nil, Nil, new NamedTestTask(TestFinishName, { log.info("No tests to run."); None }) :: Nil ) (new NamedTestTask(TestStartName, None) :: Nil, Nil, new NamedTestTask(TestFinishName, { log.info("No tests to run."); cleanTmp() }) :: Nil )
else else
createTestTasks(loader, mappedTests, log, listeners, endErrorsEnabled, setup, cleanup) createTestTasks(loader, mappedTests, log, listeners, endErrorsEnabled, setup, Seq(cleanTmp) ++ cleanup)
} }
private def testMap(frameworks: Seq[Framework], tests: Seq[TestDefinition], args: Map[Framework, Seq[String]]): private def testMap(frameworks: Seq[Framework], tests: Seq[TestDefinition], args: Map[Framework, Seq[String]]):
@ -221,12 +223,12 @@ object TestFramework
val endTask = new NamedTestTask(TestFinishName, end() ) :: createTasks(cleanup, "Test cleanup") val endTask = new NamedTestTask(TestFinishName, end() ) :: createTasks(cleanup, "Test cleanup")
(startTask, testTasks, endTask) (startTask, testTasks, endTask)
} }
def createTestLoader(classpath: Iterable[Path], scalaLoader: ClassLoader): ClassLoader = def createTestLoader(classpath: Iterable[Path], scalaInstance: ScalaInstance): (ClassLoader, Path) =
{ {
val filterCompilerLoader = new FilteredLoader(scalaLoader, ScalaCompilerJarPackages) val filterCompilerLoader = new FilteredLoader(scalaInstance.loader, ScalaCompilerJarPackages)
val interfaceFilter = (name: String) => name.startsWith("org.scalatools.testing.") val interfaceFilter = (name: String) => name.startsWith("org.scalatools.testing.")
val notInterfaceFilter = (name: String) => !interfaceFilter(name) val notInterfaceFilter = (name: String) => !interfaceFilter(name)
val dual = new xsbt.DualLoader(filterCompilerLoader, notInterfaceFilter, x => true, getClass.getClassLoader, interfaceFilter, x => false) val dual = new xsbt.DualLoader(filterCompilerLoader, notInterfaceFilter, x => true, getClass.getClassLoader, interfaceFilter, x => false)
ClasspathUtilities.toLoader(classpath, dual) ClasspathUtilities.makeLoader(classpath, dual, scalaInstance)
} }
} }

View File

@ -0,0 +1,2 @@
project.name=Run And Compiler
project.version=1.0

View File

@ -0,0 +1,7 @@
import sbt._
class A(info: ProjectInfo) extends DefaultProject(info)
{
val snaps = ScalaToolsSnapshots
val specs28 = "org.scala-tools.testing" %% "specs" % "1.6.5-SNAPSHOT" % "test"
}

View File

@ -0,0 +1,40 @@
package foo.bar
import java.io.File
import File.{pathSeparator => / }
import scala.io.Source
class Holder { var value: Any = _ }
import scala.tools.nsc.{GenericRunnerSettings, Interpreter, Settings}
class Foo {
val g = new GenericRunnerSettings(System.err.println)
val settings = new Settings()
val loader = getClass.getClassLoader
settings.classpath.value = classpath("app", loader).getOrElse(error("Error: could not find application classpath"))
settings.bootclasspath.value = settings.bootclasspath.value + / + classpath("boot", loader).getOrElse(error("Error: could not find boot classpath"))
val inter = new Interpreter(settings) {
override protected def parentClassLoader = Foo.this.getClass.getClassLoader
}
def eval(code: String): Any = {
val h = new Holder
inter.bind("$r_", h.getClass.getName, h)
val r = inter.interpret("$r_.value = " + code)
h.value
}
private def classpath(name: String, loader: ClassLoader) =
Option(loader.getResource(name + ".class.path")).map { cp =>
Source.fromURL(cp).mkString
}
}
object Test
{
def main(args: Array[String])
{
val foo = new Foo
args.foreach { arg => foo.eval(arg) == arg.toInt }
}
}

View File

@ -0,0 +1,14 @@
package foo.bar
import org.specs._
class ATest extends Specification
{
"application and boot classpath" should {
"be provided to running applications and tests" in {
val foo = new Foo
val numbers = List[Any](1,2,5, 19)
numbers.map(i => foo.eval(i.toString)) must haveTheSameElementsAs(numbers)
}
}
}

View File

@ -0,0 +1,3 @@
> ++2.8.0.RC2
> update
> test