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
* Copyright 2008, 2009 Mark Harrah
* Copyright 2008, 2009, 2010 Mark Harrah
*/
package sbt
@ -8,6 +8,8 @@ import java.net.{URI, URL, URLClassLoader}
import java.util.Collections
import scala.collection.Set
import scala.collection.mutable.{HashSet, ListBuffer}
import xsbt.ScalaInstance
import xsbt.FileUtilities.{createTemporaryDirectory, withTemporaryDirectory, write}
object ClasspathUtilities
{
@ -15,7 +17,7 @@ object ClasspathUtilities
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 = 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)
lazy val rootLoader =
@ -28,6 +30,32 @@ object ClasspathUtilities
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[_]) =
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 =
{
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 superclasses = new ListBuffer[String]
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.net.{URL, URLClassLoader}
import java.lang.reflect.Modifier.{isPublic, isStatic}
import java.lang.reflect.{Method, Modifier}
import Modifier.{isPublic, isStatic}
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)
{
val loader = getLoader(classpath, log)
val main = getMainMethod(mainClassName, loader)
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 { xsbt.FileUtilities.delete(tempDir asFile) }
}
private def invokeMain(loader: ClassLoader, main: Method, options: Seq[String])
{
val currentThread = Thread.currentThread
val oldLoader = Thread.currentThread.getContextClassLoader()
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")
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.*/

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))
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)
}
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 org.scalatools.testing.{AnnotatedFingerprint, Fingerprint, SubclassFingerprint, TestFingerprint}
import org.scalatools.testing.{Event, EventHandler, Framework, Runner, Runner2, Logger=>TLogger}
import xsbt.ScalaInstance
object Result extends Enumeration
{
@ -120,7 +121,7 @@ object TestFramework
def testTasks(frameworks: Seq[TestFramework],
classpath: Iterable[Path],
scalaLoader: ClassLoader,
scalaInstance: ScalaInstance,
tests: Seq[TestDefinition],
log: Logger,
listeners: Seq[TestReportListener],
@ -130,16 +131,17 @@ object TestFramework
testArgsByFramework: Map[TestFramework, Seq[String]]):
(Iterable[NamedTestTask], Iterable[NamedTestTask], Iterable[NamedTestTask]) =
{
val loader = createTestLoader(classpath, scalaLoader)
val (loader, tempDir) = createTestLoader(classpath, scalaInstance)
val arguments = immutable.Map() ++
( for(framework <- frameworks; created <- framework.create(loader, log)) yield
(created, testArgsByFramework.getOrElse(framework, Nil)) )
val cleanTmp = () => FileUtilities.clean(tempDir, log)
val mappedTests = testMap(arguments.keys.toList, tests, arguments)
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
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]]):
@ -221,12 +223,12 @@ object TestFramework
val endTask = new NamedTestTask(TestFinishName, end() ) :: createTasks(cleanup, "Test cleanup")
(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 notInterfaceFilter = (name: String) => !interfaceFilter(name)
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