Improve ScalaMetaBuildClassLoader construction

I realized that all of the data structures that I needed to isolate the
classpath are contained in the AppProvider interface so there was no
need to use structural reflection on the top class loader.
This commit is contained in:
Ethan Atkins 2019-05-13 13:42:11 -07:00
parent 418e7e09fd
commit 54412d8c59
2 changed files with 20 additions and 34 deletions

View File

@ -27,7 +27,7 @@ import sbt.io._
import sbt.io.syntax._
import sbt.util.{ Level, Logger, Show }
import xsbti.compile.CompilerCache
import xsbti.{ AppMain, AppProvider, ComponentProvider, ScalaProvider }
import xsbti.{ AppMain, AppProvider, ComponentProvider, Launcher, ScalaProvider }
import scala.annotation.tailrec
import scala.concurrent.ExecutionContext
@ -70,11 +70,19 @@ final class xMain extends xsbti.AppMain {
*/
private class ModifiedConfiguration(val configuration: xsbti.AppConfiguration)
extends xsbti.AppConfiguration {
private[this] val initLoader = configuration.provider.loader
private[this] val scalaLoader = configuration.provider.scalaProvider.loader
private[this] val metaLoader: ClassLoader = SbtMetaBuildClassLoader(scalaLoader, initLoader)
private[this] val metaLoader: ClassLoader = SbtMetaBuildClassLoader(configuration.provider)
private class ModifiedAppProvider(val appProvider: AppProvider) extends AppProvider {
override def scalaProvider(): ScalaProvider = appProvider.scalaProvider
override def scalaProvider(): ScalaProvider = new ScalaProvider {
val delegate = configuration.provider.scalaProvider
override def launcher(): Launcher = delegate.launcher
override def version(): String = delegate.version
override def loader(): ClassLoader = metaLoader.getParent
override def jars(): Array[File] = delegate.jars
override def libraryJar(): File = delegate.libraryJar
override def compilerJar(): File = delegate.compilerJar
override def app(id: xsbti.ApplicationID): AppProvider = delegate.app(id)
}
override def id(): xsbti.ApplicationID = appProvider.id()
override def loader(): ClassLoader = metaLoader
@deprecated("Implements deprecated api", "1.3.0")

View File

@ -20,8 +20,7 @@ import sbt.internal.util.Attributed
import sbt.internal.util.Attributed.data
import sbt.io.IO
import sbt.librarymanagement.Configurations.{ Runtime, Test }
import scala.annotation.tailrec
import xsbti.AppProvider
private[sbt] object ClassLoaders {
private[this] val interfaceLoader = classOf[sbt.testing.Framework].getClassLoader
@ -202,36 +201,15 @@ private[sbt] object ClassLoaders {
}
private[sbt] object SbtMetaBuildClassLoader {
private[this] implicit class Ops(val c: ClassLoader) {
def urls: Array[URL] = c match {
case u: URLClassLoader => u.getURLs
case cl =>
throw new IllegalStateException(s"sbt was launched with a non URLClassLoader: $cl")
}
}
def apply(scalaProviderLoader: ClassLoader, fullLoader: ClassLoader): ClassLoader = {
val libFilter: URL => Boolean = _.getFile.endsWith("scala-library.jar")
def bootLoader(loader: ClassLoader): ClassLoader = {
@tailrec def getAllParents(
classLoader: ClassLoader,
parents: List[ClassLoader]
): List[ClassLoader] = classLoader.getParent match {
case null => parents
case cl => getAllParents(cl, classLoader :: parents)
}
@tailrec def getLoader(remaining: List[ClassLoader]): ClassLoader = remaining match {
case head :: (next: URLClassLoader) :: _ if next.getURLs.exists(libFilter) => head
case head :: Nil => head
case _ :: tail => getLoader(tail)
}
getLoader(getAllParents(loader, Nil))
}
def apply(appProvider: AppProvider): ClassLoader = {
val interfaceFilter: URL => Boolean = _.getFile.endsWith("test-interface-1.0.jar")
val (interfaceURL, rest) = fullLoader.urls.partition(interfaceFilter)
val interfaceLoader = new URLClassLoader(interfaceURL, bootLoader(scalaProviderLoader)) {
def urls(jars: Array[File]): Array[URL] = jars.map(_.toURI.toURL)
val (interfaceURL, rest) = urls(appProvider.mainClasspath).partition(interfaceFilter)
val scalaProvider = appProvider.scalaProvider
val interfaceLoader = new URLClassLoader(interfaceURL, scalaProvider.launcher.topLoader) {
override def toString: String = s"SbtTestInterfaceClassLoader(${getURLs.head})"
}
val updatedLibraryLoader = new URLClassLoader(scalaProviderLoader.urls, interfaceLoader) {
val updatedLibraryLoader = new URLClassLoader(urls(scalaProvider.jars), interfaceLoader) {
override def toString: String = s"ScalaClassLoader(jars = {${getURLs.mkString(", ")}}"
}
new URLClassLoader(rest, updatedLibraryLoader) {