From 600053bf2cc9df13629239796851dd3a0a29efec Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Fri, 21 May 2010 09:32:14 -0400 Subject: [PATCH] Provide application and boot classpath to running code through resources. --- .../main/scala/sbt/ClasspathUtilities.scala | 32 ++++++++++++++- sbt/src/main/scala/sbt/DefaultProject.scala | 3 +- sbt/src/main/scala/sbt/Run.scala | 22 +++++----- sbt/src/main/scala/sbt/ScalaProject.scala | 2 +- sbt/src/main/scala/sbt/TestFramework.scala | 16 ++++---- .../run2.8/project/build.properties | 2 + .../run2.8/project/build/A.scala | 7 ++++ .../run2.8/src/main/scala/Foo.scala | 40 +++++++++++++++++++ .../run2.8/src/test/scala/ATest.scala | 14 +++++++ sbt/src/sbt-test/compiler-project/run2.8/test | 3 ++ 10 files changed, 121 insertions(+), 20 deletions(-) create mode 100644 sbt/src/sbt-test/compiler-project/run2.8/project/build.properties create mode 100644 sbt/src/sbt-test/compiler-project/run2.8/project/build/A.scala create mode 100644 sbt/src/sbt-test/compiler-project/run2.8/src/main/scala/Foo.scala create mode 100644 sbt/src/sbt-test/compiler-project/run2.8/src/test/scala/ATest.scala create mode 100644 sbt/src/sbt-test/compiler-project/run2.8/test diff --git a/sbt/src/main/scala/sbt/ClasspathUtilities.scala b/sbt/src/main/scala/sbt/ClasspathUtilities.scala index 3b5608d34..8703b66f3 100644 --- a/sbt/src/main/scala/sbt/ClasspathUtilities.scala +++ b/sbt/src/main/scala/sbt/ClasspathUtilities.scala @@ -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)) diff --git a/sbt/src/main/scala/sbt/DefaultProject.scala b/sbt/src/main/scala/sbt/DefaultProject.scala index d44018134..95d5556f3 100644 --- a/sbt/src/main/scala/sbt/DefaultProject.scala +++ b/sbt/src/main/scala/sbt/DefaultProject.scala @@ -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 { diff --git a/sbt/src/main/scala/sbt/Run.scala b/sbt/src/main/scala/sbt/Run.scala index 78c83380e..b2b2bb7a3 100644 --- a/sbt/src/main/scala/sbt/Run.scala +++ b/sbt/src/main/scala/sbt/Run.scala @@ -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.*/ diff --git a/sbt/src/main/scala/sbt/ScalaProject.scala b/sbt/src/main/scala/sbt/ScalaProject.scala index 2142c7e1f..438e6904b 100644 --- a/sbt/src/main/scala/sbt/ScalaProject.scala +++ b/sbt/src/main/scala/sbt/ScalaProject.scala @@ -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) diff --git a/sbt/src/main/scala/sbt/TestFramework.scala b/sbt/src/main/scala/sbt/TestFramework.scala index 28b40b6fd..f8120c556 100644 --- a/sbt/src/main/scala/sbt/TestFramework.scala +++ b/sbt/src/main/scala/sbt/TestFramework.scala @@ -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) } } diff --git a/sbt/src/sbt-test/compiler-project/run2.8/project/build.properties b/sbt/src/sbt-test/compiler-project/run2.8/project/build.properties new file mode 100644 index 000000000..98dfdeae3 --- /dev/null +++ b/sbt/src/sbt-test/compiler-project/run2.8/project/build.properties @@ -0,0 +1,2 @@ +project.name=Run And Compiler +project.version=1.0 \ No newline at end of file diff --git a/sbt/src/sbt-test/compiler-project/run2.8/project/build/A.scala b/sbt/src/sbt-test/compiler-project/run2.8/project/build/A.scala new file mode 100644 index 000000000..dfbe135a9 --- /dev/null +++ b/sbt/src/sbt-test/compiler-project/run2.8/project/build/A.scala @@ -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" +} \ No newline at end of file diff --git a/sbt/src/sbt-test/compiler-project/run2.8/src/main/scala/Foo.scala b/sbt/src/sbt-test/compiler-project/run2.8/src/main/scala/Foo.scala new file mode 100644 index 000000000..b5ea08ac8 --- /dev/null +++ b/sbt/src/sbt-test/compiler-project/run2.8/src/main/scala/Foo.scala @@ -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 } + } +} \ No newline at end of file diff --git a/sbt/src/sbt-test/compiler-project/run2.8/src/test/scala/ATest.scala b/sbt/src/sbt-test/compiler-project/run2.8/src/test/scala/ATest.scala new file mode 100644 index 000000000..639e44adc --- /dev/null +++ b/sbt/src/sbt-test/compiler-project/run2.8/src/test/scala/ATest.scala @@ -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) + } + } +} \ No newline at end of file diff --git a/sbt/src/sbt-test/compiler-project/run2.8/test b/sbt/src/sbt-test/compiler-project/run2.8/test new file mode 100644 index 000000000..fa479e2b1 --- /dev/null +++ b/sbt/src/sbt-test/compiler-project/run2.8/test @@ -0,0 +1,3 @@ +> ++2.8.0.RC2 +> update +> test \ No newline at end of file