diff --git a/compile/AnalyzingCompiler.scala b/compile/AnalyzingCompiler.scala index efa698539..33765e024 100644 --- a/compile/AnalyzingCompiler.scala +++ b/compile/AnalyzingCompiler.scala @@ -5,11 +5,9 @@ package xsbt import java.net.URLClassLoader /** Interface to the Scala compiler that uses the dependency analysis plugin. This class uses the Scala library and compiler -* obtained through the 'scalaLoader' class loader. This class requires a ComponentManager in order to obtain the -* interface code to scalac and the analysis plugin. Because these call Scala code for a different Scala version, they must -* be compiled for the version of Scala being used. It is essential that the provided 'scalaVersion' be a 1:1 mapping to the -* actual version of Scala being used for compilation (-SNAPSHOT is not acceptable). Otherwise, binary compatibility -* issues will ensue!*/ +* provided by scalaInstance. This class requires a ComponentManager in order to obtain the interface code to scalac and +* the analysis plugin. Because these call Scala code for a different Scala version than the one used for this class, they must +* be compiled for the version of Scala being used.*/ class AnalyzingCompiler(scalaInstance: ScalaInstance, manager: ComponentManager) extends NotNull { def apply(sources: Set[File], classpath: Set[File], outputDirectory: File, options: Seq[String], callback: AnalysisCallback, maximumErrors: Int, log: CompileLogger): Unit = diff --git a/compile/interface/src/test/scala/CheckBasic.scala b/compile/interface/src/test/scala/CheckBasic.scala deleted file mode 100644 index 63ad61fad..000000000 --- a/compile/interface/src/test/scala/CheckBasic.scala +++ /dev/null @@ -1,25 +0,0 @@ -package xsbt - -import java.io.File -import org.specs.Specification - -object CheckBasic extends Specification -{ - val basicName = new File("Basic.scala") - val basicSource = "package org.example { object Basic }" - - "Compiling basic file should succeed" in { - WithFiles(basicName -> basicSource){ files => - TestCompile(files){ loader => Class.forName("org.example.Basic", false, loader) } - true must beTrue // don't know how to just check that previous line completes without exception - } - } - "Analyzer plugin should send source begin and end" in { - WithFiles(basicName -> basicSource) { files => - CallbackTest(files) { callback => - (callback.beganSources) must haveTheSameElementsAs(files) - (callback.endedSources) must haveTheSameElementsAs(files) - } - } - } -} \ No newline at end of file diff --git a/compile/interface/src/test/scala/TestCompile.scala b/compile/interface/src/test/scala/TestCompile.scala deleted file mode 100644 index 66ff1c747..000000000 --- a/compile/interface/src/test/scala/TestCompile.scala +++ /dev/null @@ -1,38 +0,0 @@ -package xsbt - -import java.io.File -import java.net.URLClassLoader -import xsbti.TestCallback -import FileUtilities.withTemporaryDirectory - -object TestCompile -{ - /** Tests running the compiler interface with the analyzer plugin with a test callback. The test callback saves all information - * that the plugin sends it for post-compile analysis by the provided function.*/ - def apply[T](arguments: Seq[String], superclassNames: Seq[String])(f: (TestCallback, Logger) => T): T = - { - val testCallback = new TestCallback(superclassNames.toArray) - val i = new CompilerInterface - val log = new BufferedLogger(new ConsoleLogger) - log.bufferQuietly { - i.run(arguments.toArray, testCallback, 5, log) - f(testCallback, log) - } - } - /** Tests running the compiler interface with the analyzer plugin. The provided function is given a ClassLoader that can - * load the compiled classes..*/ - def apply[T](sources: Seq[File])(f: ClassLoader => T): T = - CallbackTest.apply(sources, Nil){ case (callback, outputDir, log) => f(new URLClassLoader(Array(outputDir.toURI.toURL))) } -} -object CallbackTest -{ - def apply[T](sources: Iterable[File])(f: TestCallback => T): T = - apply(sources.toSeq, Nil){ case (callback, outputDir, log) => f(callback) } - def apply[T](sources: Seq[File], superclassNames: Seq[String])(f: (TestCallback, File, Logger) => T): T = - { - withTemporaryDirectory { outputDir => - val newArgs = "-d" :: outputDir.getAbsolutePath :: sources.map(_.getAbsolutePath).toList - TestCompile(newArgs, superclassNames) { case (callback, log) => f(callback, outputDir, log) } - } - } -} \ No newline at end of file diff --git a/compile/interface/src/test/scala/ApplicationsTest.scala b/compile/src/test/scala/ApplicationsTest.scala similarity index 84% rename from compile/interface/src/test/scala/ApplicationsTest.scala rename to compile/src/test/scala/ApplicationsTest.scala index 8be9fc955..547bd94de 100644 --- a/compile/interface/src/test/scala/ApplicationsTest.scala +++ b/compile/src/test/scala/ApplicationsTest.scala @@ -98,13 +98,14 @@ object ApplicationsTest extends Specification "Analysis plugin should detect applications" in { WithFiles(sources : _*) { case files @ Seq(main, main2, main3, main4, main5, main6, main7, main8, main9, mainA, mainB, mainC, mainD, mainE) => - CallbackTest(files, Nil) { (callback, file, log) => - val expected = Seq( main -> "Main", main4 -> "Main4", main8 -> "Main8", main9 -> "Main9", mainB -> "MainB", - mainE -> "MainE1", mainE -> "MainE2", mainE -> "MainE3", mainE -> "MainE4", mainE -> "MainE5" ) - (callback.applications) must haveTheSameElementsAs(expected) - val loader = new URLClassLoader(Array(file.toURI.toURL), getClass.getClassLoader) - for( (_, className) <- expected) testRun(loader, className) - } + for(scalaVersion <- TestCompile.allVersions) + CallbackTest(scalaVersion, files, Nil) { (callback, file, log) => + val expected = Seq( main -> "Main", main4 -> "Main4", main8 -> "Main8", main9 -> "Main9", mainB -> "MainB", + mainE -> "MainE1", mainE -> "MainE2", mainE -> "MainE3", mainE -> "MainE4", mainE -> "MainE5" ) + (callback.applications) must haveTheSameElementsAs(expected) + val loader = new URLClassLoader(Array(file.toURI.toURL), getClass.getClassLoader) + for( (_, className) <- expected) testRun(loader, className) + } } } private def testRun(loader: ClassLoader, className: String) diff --git a/compile/src/test/scala/CheckBasic.scala b/compile/src/test/scala/CheckBasic.scala new file mode 100644 index 000000000..8ca866ac0 --- /dev/null +++ b/compile/src/test/scala/CheckBasic.scala @@ -0,0 +1,29 @@ +package xsbt + +import java.io.File +import org.specs.Specification + +object CheckBasic extends Specification +{ + val basicName = new File("Basic.scala") + val basicSource = "package org.example { object Basic }" + + "Compiling basic file should succeed" in { + WithFiles(basicName -> basicSource){ files => + for(scalaVersion <- TestCompile.allVersions) + { + TestCompile(scalaVersion, files){ loader => Class.forName("org.example.Basic", false, loader) } + true must beTrue // don't know how to just check that previous line completes without exception + } + } + } + "Analyzer plugin should send source begin and end" in { + WithFiles(basicName -> basicSource) { files => + for(scalaVersion <- TestCompile.allVersions) + CallbackTest(scalaVersion, files) { callback => + (callback.beganSources) must haveTheSameElementsAs(files) + (callback.endedSources) must haveTheSameElementsAs(files) + } + } + } +} \ No newline at end of file diff --git a/compile/src/test/scala/CompileTest.scala b/compile/src/test/scala/CompileTest.scala index 7c00ba665..13ed67c6e 100644 --- a/compile/src/test/scala/CompileTest.scala +++ b/compile/src/test/scala/CompileTest.scala @@ -4,13 +4,15 @@ import java.io.File import FileUtilities.withTemporaryDirectory import org.specs._ -// compile w/ analysis a bit hard to test properly right now: -// requires compile project to depend on +publish-local, which is not possible in sbt (addressed in xsbt, but that doesn't help here!) object CompileTest extends Specification { "Analysis compiler" should { "compile basic sources" in { WithCompiler( "2.7.2" )(testCompileAnalysis) + WithCompiler( "2.7.3" )(testCompileAnalysis) + WithCompiler( "2.7.4" )(testCompileAnalysis) + WithCompiler( "2.7.5" )(testCompileAnalysis) + WithCompiler( "2.8.0-SNAPSHOT" )(testCompileAnalysis) } } private def testCompileAnalysis(compiler: AnalyzingCompiler, log: CompileLogger) @@ -38,7 +40,12 @@ object WithCompiler val manager = new ComponentManager(launch.getSbtHome(sbtVersion, scalaVersion), log) prepare(manager, ComponentCompiler.compilerInterfaceSrcID, "CompilerInterface.scala") prepare(manager, ComponentCompiler.xsbtiID, classOf[xsbti.AnalysisCallback]) - f(AnalyzingCompiler(scalaVersion, launch, manager), log) + val result = f(AnalyzingCompiler(scalaVersion, launch, manager), log) + launch.clearScalaLoaderCache + System.gc() + System.gc() + System.gc() + result } } } diff --git a/compile/interface/src/test/scala/DetectSubclasses.scala b/compile/src/test/scala/DetectSubclasses.scala similarity index 57% rename from compile/interface/src/test/scala/DetectSubclasses.scala rename to compile/src/test/scala/DetectSubclasses.scala index a502bbe50..60e5c889c 100644 --- a/compile/interface/src/test/scala/DetectSubclasses.scala +++ b/compile/src/test/scala/DetectSubclasses.scala @@ -18,16 +18,17 @@ object DetectSubclasses extends Specification WithFiles(sources.map{case (file, content) => (new File(file), content)} : _*) { case files @ Seq(supFile, sup2File, midFile, sub1File, sub2File, sub3File) => - CallbackTest(files, Seq( "a.Super", "Super2", "x.Super3", "Super4") ) { (callback, x, xx) => - val expected = - (sub1File, "a.Sub1", "a.Super", false) :: - (sub2File, "Sub2", "a.Super", false) :: - (sup2File, "Super2", "Super2", false) :: - (sub3File, "c.Sub3", "Super2", true) :: - Nil - (callback.foundSubclasses) must haveTheSameElementsAs(expected) - (callback.invalidSuperclasses) must haveTheSameElementsAs(Seq("x.Super3", "Super4")) - } + for(scalaVersion <- TestCompile.allVersions) + CallbackTest(scalaVersion, files, Seq( "a.Super", "Super2", "x.Super3", "Super4") ) { (callback, x, xx) => + val expected = + (sub1File, "a.Sub1", "a.Super", false) :: + (sub2File, "Sub2", "a.Super", false) :: + (sup2File, "Super2", "Super2", false) :: + (sub3File, "c.Sub3", "Super2", true) :: + Nil + (callback.foundSubclasses) must haveTheSameElementsAs(expected) + (callback.invalidSuperclasses) must haveTheSameElementsAs(Seq("x.Super3", "Super4")) + } } } } \ No newline at end of file diff --git a/compile/src/test/scala/TestCompile.scala b/compile/src/test/scala/TestCompile.scala new file mode 100644 index 000000000..e350a9997 --- /dev/null +++ b/compile/src/test/scala/TestCompile.scala @@ -0,0 +1,36 @@ +package xsbt + +import java.io.File +import java.net.URLClassLoader +import xsbti.TestCallback +import FileUtilities.withTemporaryDirectory + +object TestCompile +{ + // skip 2.7.3 and 2.7.4 for speed + def allVersions = List("2.7.2", "2.7.5", "2.8.0-SNAPSHOT")//List("2.7.2", "2.7.3", "2.7.4", "2.7.5", "2.8.0-SNAPSHOT") + /** Tests running the compiler interface with the analyzer plugin with a test callback. The test callback saves all information + * that the plugin sends it for post-compile analysis by the provided function.*/ + def apply[T](scalaVersion: String, sources: Set[File], outputDirectory: File, options: Seq[String], superclassNames: Seq[String])(f: (TestCallback, CompileLogger) => T): T = + { + val testCallback = new TestCallback(superclassNames.toArray) + val result = WithCompiler(scalaVersion) { (compiler, log) => + compiler(sources, Set.empty, outputDirectory, options, testCallback, 5, log) + f(testCallback, log) + } + result + } + /** Tests running the compiler interface with the analyzer plugin. The provided function is given a ClassLoader that can + * load the compiled classes..*/ + def apply[T](scalaVersion: String, sources: Seq[File])(f: ClassLoader => T): T = + CallbackTest.apply(scalaVersion, sources, Nil){ case (callback, outputDir, log) => f(new URLClassLoader(Array(outputDir.toURI.toURL))) } +} +object CallbackTest +{ + def apply[T](scalaVersion: String, sources: Iterable[File])(f: TestCallback => T): T = + apply(scalaVersion, sources.toSeq, Nil){ case (callback, outputDir, log) => f(callback) } + def apply[T](scalaVersion: String, sources: Seq[File], superclassNames: Seq[String])(f: (TestCallback, File, CompileLogger) => T): T = + withTemporaryDirectory { outputDir => + TestCompile(scalaVersion, Set() ++ sources, outputDir, Nil, superclassNames) { case (callback, log) => f(callback, outputDir, log) } + } +} \ No newline at end of file diff --git a/launch/Launch.scala b/launch/Launch.scala index cdaea303b..e5cff462d 100644 --- a/launch/Launch.scala +++ b/launch/Launch.scala @@ -109,7 +109,8 @@ class Launch(projectRootDirectory: File, mainClassName: String) extends Launcher } } - private val scalaLoaderCache = new scala.collection.jcl.WeakHashMap[String, ClassLoader] + private val scalaLoaderCache = new scala.collection.mutable.HashMap[String, ClassLoader] + def clearScalaLoaderCache { scalaLoaderCache.clear() } def launcher(directory: File, mainClassName: String): Launcher = new Launch(directory, mainClassName) def getScalaLoader(scalaVersion: String) = scalaLoaderCache.getOrElseUpdate(scalaVersion, createScalaLoader(scalaVersion))