mirror of https://github.com/sbt/sbt.git
commit
5b1756fe60
|
|
@ -112,7 +112,9 @@ object ClassLoaderLayeringStrategy {
|
|||
* Add the TestDependencies layer on top of the RuntimeDependencies layer on top of the
|
||||
* ScalaInstance layer. This differs from TestDependencies in that it will not reload the
|
||||
* runtime classpath. The drawback to using this is that if the test dependencies evict
|
||||
* classes provided in the runtime layer, then tests can fail.
|
||||
* classes provided in the runtime layer, then tests can fail. In order for sharing the runtime
|
||||
* layer to work, it is necessary to set [[Keys.bgCopyClasspath]] to false. Otherwise the
|
||||
* runtime and test classpaths are completely different.
|
||||
*/
|
||||
case object ShareRuntimeDependenciesLayerWithTestDependencies
|
||||
extends ScalaInstance
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
package sbt
|
||||
|
||||
import java.io.{ File, PrintWriter }
|
||||
import java.net.{ URI, URL }
|
||||
import java.net.{ URI, URL, URLClassLoader }
|
||||
import java.util.Optional
|
||||
import java.util.concurrent.{ Callable, TimeUnit }
|
||||
|
||||
|
|
@ -787,9 +787,14 @@ object Defaults extends BuildCommon {
|
|||
// ((streams in test, loadedTestFrameworks, testLoader, testGrouping in test, testExecution in test, fullClasspath in test, javaHome in test, testForkedParallel, javaOptions in test) flatMap allTestGroupsTask).value,
|
||||
testResultLogger in (Test, test) :== TestResultLogger.SilentWhenNoTests, // https://github.com/sbt/sbt/issues/1185
|
||||
test := {
|
||||
val loader = testLoader.value match { case u: URLClassLoader => Some(u); case _ => None }
|
||||
val trl = (testResultLogger in (Test, test)).value
|
||||
val taskName = Project.showContextKey(state.value).show(resolvedScoped.value)
|
||||
trl.run(streams.value.log, executeTests.value, taskName)
|
||||
try {
|
||||
trl.run(streams.value.log, executeTests.value, taskName)
|
||||
} finally {
|
||||
loader.foreach(_.close())
|
||||
}
|
||||
},
|
||||
testOnly := inputTests(testOnly).evaluated,
|
||||
testQuick := inputTests(testQuick).evaluated
|
||||
|
|
@ -1056,22 +1061,21 @@ object Defaults extends BuildCommon {
|
|||
val result = output map { out =>
|
||||
out.events.foreach {
|
||||
case (suite, e) =>
|
||||
e.throwables
|
||||
.collectFirst {
|
||||
case t
|
||||
if t
|
||||
.isInstanceOf[NoClassDefFoundError] && strategy != ClassLoaderLayeringStrategy.Flat =>
|
||||
t
|
||||
}
|
||||
.foreach { t =>
|
||||
s.log.error(
|
||||
s"Test suite $suite failed with $t. This may be due to the ClassLoaderLayeringStrategy"
|
||||
+ s" ($strategy) used by your task. This issue may be resolved by changing the"
|
||||
+ " ClassLoaderLayeringStrategy in your configuration (generally Test or IntegrationTest),"
|
||||
+ "e.g.:\nTest / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.Flat\n"
|
||||
+ "See ClassLoaderLayeringStrategy.scala for the full list of options."
|
||||
)
|
||||
}
|
||||
if (strategy != ClassLoaderLayeringStrategy.Flat) {
|
||||
e.throwables
|
||||
.find { t =>
|
||||
t.isInstanceOf[NoClassDefFoundError] || t.isInstanceOf[IllegalAccessError]
|
||||
}
|
||||
.foreach { t =>
|
||||
s.log.error(
|
||||
s"Test suite $suite failed with $t. This may be due to the ClassLoaderLayeringStrategy"
|
||||
+ s" ($strategy) used by your task. This issue may be resolved by changing the"
|
||||
+ " ClassLoaderLayeringStrategy in your configuration (generally Test or IntegrationTest),"
|
||||
+ " e.g.:\nTest / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.Flat\n"
|
||||
+ "See ClassLoaderLayeringStrategy.scala for the full list of options."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
val summaries =
|
||||
runners map {
|
||||
|
|
@ -1772,18 +1776,26 @@ object Defaults extends BuildCommon {
|
|||
Classpaths.addUnmanagedLibrary ++
|
||||
Vector(
|
||||
TaskRepository.proxy(
|
||||
classLoaderCache,
|
||||
Compile / classLoaderCache,
|
||||
// We need a cache of size four so that the subset of the runtime dependencies that are used
|
||||
// by the test task layers may be cached without evicting the runtime classloader layers. The
|
||||
// cache size should be a multiple of two to support snapshot layers.
|
||||
ClassLoaderCache(4)
|
||||
)
|
||||
),
|
||||
bgCopyClasspath in bgRun := {
|
||||
val old = (bgCopyClasspath in bgRun).value
|
||||
old && (Test / classLoaderLayeringStrategy).value != ClassLoaderLayeringStrategy.ShareRuntimeDependenciesLayerWithTestDependencies
|
||||
},
|
||||
bgCopyClasspath in bgRunMain := {
|
||||
val old = (bgCopyClasspath in bgRunMain).value
|
||||
old && (Test / classLoaderLayeringStrategy).value != ClassLoaderLayeringStrategy.ShareRuntimeDependenciesLayerWithTestDependencies
|
||||
},
|
||||
)
|
||||
|
||||
lazy val testSettings: Seq[Setting[_]] = configSettings ++ testTasks ++
|
||||
Vector(
|
||||
TaskRepository.proxy(
|
||||
classLoaderCache,
|
||||
Test / classLoaderCache,
|
||||
// We need a cache of size two for the test dependency layers (regular and snapshot).
|
||||
ClassLoaderCache(2)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -503,7 +503,7 @@ object Keys {
|
|||
val resolvedScoped = Def.resolvedScoped
|
||||
val pluginData = taskKey[PluginData]("Information from the plugin build needed in the main build definition.").withRank(DTask)
|
||||
val globalPluginUpdate = taskKey[UpdateReport]("A hook to get the UpdateReport of the global plugin.").withRank(DTask)
|
||||
val classLoaderCache = taskKey[internal.ClassLoaderCache]("The cache of ClassLoaders to be used for layering in tasks that invoke other java code").withRank(DTask)
|
||||
private[sbt] val classLoaderCache = taskKey[internal.ClassLoaderCache]("The cache of ClassLoaders to be used for layering in tasks that invoke other java code").withRank(DTask)
|
||||
private[sbt] val taskRepository = AttributeKey[TaskRepository.Repr]("task-repository", "A repository that can be used to cache arbitrary values for a given task key that can be read or filled during task evaluation.", 10000)
|
||||
private[sbt] val taskCancelStrategy = settingKey[State => TaskCancellationStrategy]("Experimental task cancellation handler.").withRank(DTask)
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
package sbt
|
||||
|
||||
import java.io.{ File, IOException }
|
||||
import java.lang.reflect.InvocationTargetException
|
||||
import java.net.URI
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.{ Locale, Properties }
|
||||
|
|
@ -27,6 +28,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 scala.annotation.tailrec
|
||||
import scala.concurrent.ExecutionContext
|
||||
|
|
@ -35,6 +37,47 @@ import scala.util.control.NonFatal
|
|||
/** This class is the entry point for sbt. */
|
||||
final class xMain extends xsbti.AppMain {
|
||||
def run(configuration: xsbti.AppConfiguration): xsbti.MainResult = {
|
||||
val modifiedConfiguration = new ModifiedConfiguration(configuration)
|
||||
val loader = modifiedConfiguration.provider.loader
|
||||
// No need to memoize the old class loader. It is reset by the launcher anyway.
|
||||
Thread.currentThread.setContextClassLoader(loader)
|
||||
val clazz = loader.loadClass("sbt.xMainImpl$")
|
||||
val instance = clazz.getField("MODULE$").get(null)
|
||||
val runMethod = clazz.getMethod("run", classOf[xsbti.AppConfiguration])
|
||||
try {
|
||||
runMethod.invoke(instance, modifiedConfiguration).asInstanceOf[xsbti.MainResult]
|
||||
} catch {
|
||||
case e: InvocationTargetException =>
|
||||
// This propogates xsbti.FullReload to the launcher
|
||||
throw e.getCause
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Replaces the AppProvider.loader method with a new loader that puts the sbt test interface
|
||||
* jar ahead of the rest of the sbt classpath in the classloading hierarchy.
|
||||
*/
|
||||
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 class ModifiedAppProvider(val appProvider: AppProvider) extends AppProvider {
|
||||
override def scalaProvider(): ScalaProvider = appProvider.scalaProvider
|
||||
override def id(): xsbti.ApplicationID = appProvider.id()
|
||||
override def loader(): ClassLoader = metaLoader
|
||||
override def mainClass(): Class[_ <: AppMain] = appProvider.mainClass()
|
||||
override def entryPoint(): Class[_] = appProvider.entryPoint()
|
||||
override def newMain(): AppMain = appProvider.newMain()
|
||||
override def mainClasspath(): Array[File] = appProvider.mainClasspath()
|
||||
override def components(): ComponentProvider = appProvider.components()
|
||||
}
|
||||
override def arguments(): Array[String] = configuration.arguments
|
||||
override def baseDirectory(): File = configuration.baseDirectory
|
||||
override def provider(): AppProvider = new ModifiedAppProvider(configuration.provider)
|
||||
}
|
||||
}
|
||||
private[sbt] object xMainImpl {
|
||||
private[sbt] def run(configuration: xsbti.AppConfiguration): xsbti.MainResult = {
|
||||
import BasicCommandStrings.{ DashClient, DashDashClient, runEarly }
|
||||
import BasicCommands.early
|
||||
import BuiltinCommands.defaults
|
||||
|
|
|
|||
|
|
@ -14,17 +14,17 @@ import sbt.internal.util.TypeFunctions.Id
|
|||
|
||||
import scala.annotation.tailrec
|
||||
|
||||
sealed trait ClassLoaderCache
|
||||
private[sbt] sealed trait ClassLoaderCache
|
||||
extends Repository[Id, (Seq[File], ClassLoader, Map[String, String], File), ClassLoader]
|
||||
|
||||
object ClassLoaderCache {
|
||||
private[sbt] object ClassLoaderCache {
|
||||
private type Resources = Map[String, String]
|
||||
private sealed trait CachedClassLoader extends ClassLoader {
|
||||
def close(): Unit
|
||||
}
|
||||
private sealed trait StableClassLoader extends CachedClassLoader
|
||||
private sealed trait SnapshotClassLoader extends CachedClassLoader
|
||||
def apply(maxSize: Int): ClassLoaderCache =
|
||||
def apply(maxSize: Int): ClassLoaderCache = {
|
||||
new ClassLoaderCache {
|
||||
private final def mktmp(tmp: File): File =
|
||||
if (maxSize > 0) Files.createTempDirectory("sbt-jni").toFile else tmp
|
||||
|
|
@ -89,10 +89,5 @@ object ClassLoaderCache {
|
|||
}
|
||||
}
|
||||
}
|
||||
def empty(newLoader: (Seq[File], ClassLoader, Resources, File) => ClassLoader): ClassLoaderCache =
|
||||
new ClassLoaderCache {
|
||||
override def get(key: (Seq[File], ClassLoader, Resources, File)): ClassLoader =
|
||||
newLoader.tupled(key)
|
||||
override def close(): Unit = {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,36 +9,20 @@ package sbt
|
|||
package internal
|
||||
|
||||
import java.io.File
|
||||
import java.net.URLClassLoader
|
||||
import java.net.{ URL, URLClassLoader }
|
||||
|
||||
import sbt.ClassLoaderLayeringStrategy.{ ScalaInstance => ScalaInstanceLayer, _ }
|
||||
import sbt.Keys._
|
||||
import sbt.SlashSyntax0._
|
||||
import sbt.internal.inc.ScalaInstance
|
||||
import sbt.internal.inc.classpath.{ ClasspathUtilities, DualLoader, NullLoader }
|
||||
import sbt.internal.inc.classpath.ClasspathUtilities
|
||||
import sbt.internal.util.Attributed
|
||||
import sbt.internal.util.Attributed.data
|
||||
import sbt.io.IO
|
||||
import sbt.librarymanagement.Configurations.Runtime
|
||||
import PrettyPrint.indent
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import ClassLoaderLayeringStrategy.{ ScalaInstance => ScalaInstanceLayer, _ }
|
||||
import sbt.librarymanagement.Configurations.{ Runtime, Test }
|
||||
|
||||
private[sbt] object ClassLoaders {
|
||||
private[this] lazy val interfaceLoader =
|
||||
combine(
|
||||
classOf[sbt.testing.Framework].getClassLoader,
|
||||
new NullLoader,
|
||||
toString = "sbt.testing.Framework interface ClassLoader"
|
||||
)
|
||||
private[this] lazy val baseLoader = {
|
||||
@tailrec
|
||||
def getBase(classLoader: ClassLoader): ClassLoader = classLoader.getParent match {
|
||||
case null => classLoader
|
||||
case loader => getBase(loader)
|
||||
}
|
||||
getBase(ClassLoaders.getClass.getClassLoader)
|
||||
}
|
||||
private[this] val interfaceLoader = classOf[sbt.testing.Framework].getClassLoader
|
||||
/*
|
||||
* Get the class loader for a test task. The configuration could be IntegrationTest or Test.
|
||||
*/
|
||||
|
|
@ -46,19 +30,19 @@ private[sbt] object ClassLoaders {
|
|||
val si = scalaInstance.value
|
||||
val rawCP = data(fullClasspath.value)
|
||||
val fullCP = if (si.isManagedVersion) rawCP else si.allJars.toSeq ++ rawCP
|
||||
val exclude = dependencyJars(exportedProducts).value.toSet ++ si.allJars.toSeq
|
||||
val exclude = dependencyJars(exportedProducts).value.toSet ++ si.allJars
|
||||
buildLayers(
|
||||
classLoaderLayeringStrategy.value,
|
||||
si,
|
||||
fullCP,
|
||||
dependencyJars(Runtime / dependencyClasspath).value.filterNot(exclude),
|
||||
dependencyJars(dependencyClasspath).value.filterNot(exclude).toSet,
|
||||
interfaceLoader,
|
||||
(Runtime / classLoaderCache).value,
|
||||
classLoaderCache.value,
|
||||
ClasspathUtilities.createClasspathResources(fullCP, si),
|
||||
IO.createUniqueDirectory(taskTemporaryDirectory.value),
|
||||
resolvedScoped.value.scope
|
||||
strategy = classLoaderLayeringStrategy.value,
|
||||
si = si,
|
||||
fullCP = fullCP,
|
||||
rawRuntimeDependencies =
|
||||
dependencyJars(Runtime / dependencyClasspath).value.filterNot(exclude),
|
||||
allDependencies = dependencyJars(dependencyClasspath).value.filterNot(exclude),
|
||||
runtimeCache = (Runtime / classLoaderCache).value,
|
||||
testCache = (Test / classLoaderCache).value,
|
||||
resources = ClasspathUtilities.createClasspathResources(fullCP, si),
|
||||
tmp = IO.createUniqueDirectory(taskTemporaryDirectory.value),
|
||||
scope = resolvedScoped.value.scope
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -88,23 +72,23 @@ private[sbt] object ClassLoaders {
|
|||
s.log.warn(s"$showJavaOptions will be ignored, $showFork is set to false")
|
||||
}
|
||||
val runtimeCache = (Runtime / classLoaderCache).value
|
||||
val testCache = classLoaderCache.value
|
||||
val testCache = (Test / classLoaderCache).value
|
||||
val exclude = dependencyJars(exportedProducts).value.toSet ++ instance.allJars
|
||||
val runtimeDeps = dependencyJars(Runtime / dependencyClasspath).value.filterNot(exclude)
|
||||
val allDeps = dependencyJars(dependencyClasspath).value.filterNot(exclude)
|
||||
val newLoader =
|
||||
(classpath: Seq[File]) => {
|
||||
buildLayers(
|
||||
classLoaderLayeringStrategy.value: @sbtUnchecked,
|
||||
instance,
|
||||
classpath,
|
||||
(dependencyJars(Runtime / dependencyClasspath).value: @sbtUnchecked)
|
||||
.filterNot(exclude),
|
||||
(dependencyJars(dependencyClasspath).value: @sbtUnchecked).filterNot(exclude).toSet,
|
||||
baseLoader,
|
||||
runtimeCache,
|
||||
testCache,
|
||||
ClasspathUtilities.createClasspathResources(classpath, instance),
|
||||
taskTemporaryDirectory.value: @sbtUnchecked,
|
||||
resolvedScope
|
||||
strategy = classLoaderLayeringStrategy.value: @sbtUnchecked,
|
||||
si = instance,
|
||||
fullCP = classpath,
|
||||
rawRuntimeDependencies = runtimeDeps,
|
||||
allDependencies = allDeps,
|
||||
runtimeCache = runtimeCache,
|
||||
testCache = testCache,
|
||||
resources = ClasspathUtilities.createClasspathResources(classpath, instance),
|
||||
tmp = taskTemporaryDirectory.value: @sbtUnchecked,
|
||||
scope = resolvedScope
|
||||
)
|
||||
}
|
||||
new Run(newLoader, trapExit.value)
|
||||
|
|
@ -127,8 +111,7 @@ private[sbt] object ClassLoaders {
|
|||
si: ScalaInstance,
|
||||
fullCP: Seq[File],
|
||||
rawRuntimeDependencies: Seq[File],
|
||||
allDependencies: Set[File],
|
||||
base: ClassLoader,
|
||||
allDependencies: Seq[File],
|
||||
runtimeCache: ClassLoaderCache,
|
||||
testCache: ClassLoaderCache,
|
||||
resources: Map[String, String],
|
||||
|
|
@ -137,7 +120,7 @@ private[sbt] object ClassLoaders {
|
|||
): ClassLoader = {
|
||||
val isTest = scope.config.toOption.map(_.name) == Option("test")
|
||||
val raw = strategy match {
|
||||
case Flat => flatLoader(fullCP, base)
|
||||
case Flat => flatLoader(fullCP, interfaceLoader)
|
||||
case _ =>
|
||||
val (layerDependencies, layerTestDependencies) = strategy match {
|
||||
case ShareRuntimeDependenciesLayerWithTestDependencies if isTest => (true, true)
|
||||
|
|
@ -150,30 +133,31 @@ private[sbt] object ClassLoaders {
|
|||
"Flat, ScalaInstance, RuntimeDependencies }"
|
||||
throw new IllegalArgumentException(msg)
|
||||
}
|
||||
val allDependenciesSet = allDependencies.toSet
|
||||
// The raw declarations are to avoid having to make a dynamic task. The
|
||||
// allDependencies and allTestDependencies create a mutually exclusive list of jar
|
||||
// dependencies for layers 2 and 3. Note that in the Runtime or Compile configs, it
|
||||
// should always be the case that allTestDependencies == Nil
|
||||
val allTestDependencies = if (layerTestDependencies) allDependencies else Set.empty[File]
|
||||
val allTestDependencies = if (layerTestDependencies) allDependenciesSet else Set.empty[File]
|
||||
val allRuntimeDependencies = (if (layerDependencies) rawRuntimeDependencies else Nil).toSet
|
||||
|
||||
val scalaInstanceLayer = new ScalaInstanceLoader(si)
|
||||
// layer 2
|
||||
val runtimeDependencies = allDependencies intersect allRuntimeDependencies
|
||||
val runtimeLayer =
|
||||
layer(runtimeDependencies.toSeq, loader(si), runtimeCache, resources, tmp)
|
||||
val runtimeDependencySet = allDependenciesSet intersect allRuntimeDependencies
|
||||
val runtimeDependencies = rawRuntimeDependencies.filter(runtimeDependencySet)
|
||||
lazy val runtimeLayer =
|
||||
if (layerDependencies)
|
||||
layer(runtimeDependencies, scalaInstanceLayer, runtimeCache, resources, tmp)
|
||||
else scalaInstanceLayer
|
||||
|
||||
// layer 3 (optional if testDependencies are empty)
|
||||
|
||||
// The top layer needs to include the interface jar or else the test task cannot be created.
|
||||
// It needs to be separated from the runtimeLayer or else the runtimeLayer cannot be
|
||||
// shared between the runtime and test tasks.
|
||||
val top = combine(base, runtimeLayer)
|
||||
val testDependencies = allTestDependencies diff runtimeDependencies
|
||||
val testLayer = layer(testDependencies.toSeq, top, testCache, resources, tmp)
|
||||
val testDependencySet = allTestDependencies diff runtimeDependencySet
|
||||
val testDependencies = allDependencies.filter(testDependencySet)
|
||||
val testLayer = layer(testDependencies, runtimeLayer, testCache, resources, tmp)
|
||||
|
||||
// layer 4
|
||||
val dynamicClasspath =
|
||||
fullCP.filterNot(testDependencies ++ runtimeDependencies ++ si.allJars)
|
||||
fullCP.filterNot(testDependencySet ++ runtimeDependencies ++ si.allJars)
|
||||
if (dynamicClasspath.nonEmpty)
|
||||
new LayeredClassLoader(dynamicClasspath, testLayer, resources, tmp)
|
||||
else testLayer
|
||||
|
|
@ -201,49 +185,42 @@ private[sbt] object ClassLoaders {
|
|||
if (snapshots.isEmpty) jarLoader else cache.get((snapshots, jarLoader, resources, tmp))
|
||||
}
|
||||
|
||||
// Code related to combining two classloaders that primarily exists so the test loader correctly
|
||||
// loads the testing framework using the same classloader as sbt itself.
|
||||
private val interfaceFilter = (name: String) =>
|
||||
name.startsWith("org.scalatools.testing.") || name.startsWith("sbt.testing.") || name
|
||||
.startsWith("java.") || name.startsWith("sun.")
|
||||
private val notInterfaceFilter = (name: String) => !interfaceFilter(name)
|
||||
private class WrappedDualLoader(
|
||||
val parent: ClassLoader,
|
||||
val child: ClassLoader,
|
||||
string: => String
|
||||
) extends ClassLoader(
|
||||
new DualLoader(parent, interfaceFilter, _ => false, child, notInterfaceFilter, _ => true)
|
||||
) {
|
||||
private class ScalaInstanceLoader(val instance: ScalaInstance)
|
||||
extends URLClassLoader(instance.allJars.map(_.toURI.toURL), interfaceLoader) {
|
||||
override def equals(o: Any): Boolean = o match {
|
||||
case that: WrappedDualLoader => this.parent == that.parent && this.child == that.child
|
||||
case _ => false
|
||||
case that: ScalaInstanceLoader => this.instance.allJars.sameElements(that.instance.allJars)
|
||||
case _ => false
|
||||
}
|
||||
override def hashCode: Int = (parent.hashCode * 31) ^ child.hashCode
|
||||
override lazy val toString: String = string
|
||||
override def hashCode: Int = instance.hashCode
|
||||
override lazy val toString: String =
|
||||
s"ScalaInstanceLoader($interfaceLoader, jars = {${instance.allJars.mkString(", ")}})"
|
||||
}
|
||||
private def combine(parent: ClassLoader, child: ClassLoader, toString: String): ClassLoader =
|
||||
new WrappedDualLoader(parent, child, toString)
|
||||
private def combine(parent: ClassLoader, child: ClassLoader): ClassLoader =
|
||||
new WrappedDualLoader(
|
||||
parent,
|
||||
child,
|
||||
s"WrappedDualLoader(\n parent =\n${indent(parent, 4)}"
|
||||
+ s"\n child =\n${indent(child, 4)}\n)"
|
||||
)
|
||||
|
||||
// helper methods
|
||||
private def flatLoader(classpath: Seq[File], parent: ClassLoader): ClassLoader =
|
||||
new URLClassLoader(classpath.map(_.toURI.toURL).toArray, parent)
|
||||
|
||||
// This makes the toString method of the ScalaInstance classloader much more readable, but
|
||||
// it is not strictly necessary.
|
||||
private def loader(si: ScalaInstance): ClassLoader = new ClassLoader(si.loader) {
|
||||
override lazy val toString: String =
|
||||
"ScalaInstanceClassLoader(\n instance = " +
|
||||
s"${indent(si.toString.split(",").mkString("\n ", ",\n ", "\n"), 4)}\n)"
|
||||
// Delegate equals to that.equals in case that is itself some kind of wrapped classloader that
|
||||
// needs to delegate its equals method to the delegated ClassLoader.
|
||||
override def equals(that: Any): Boolean = if (that != null) that.equals(si.loader) else false
|
||||
override def hashCode: Int = si.loader.hashCode
|
||||
}
|
||||
|
||||
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(libraryLoader: ClassLoader, fullLoader: ClassLoader): ClassLoader = {
|
||||
val interfaceFilter: URL => Boolean = _.getFile.endsWith("test-interface-1.0.jar")
|
||||
val (interfaceURL, rest) = fullLoader.urls.partition(interfaceFilter)
|
||||
val interfaceLoader = new URLClassLoader(interfaceURL, libraryLoader.getParent) {
|
||||
override def toString: String = s"SbtTestInterfaceClassLoader(${getURLs.head})"
|
||||
}
|
||||
val updatedLibraryLoader = new URLClassLoader(libraryLoader.urls, interfaceLoader) {
|
||||
override def toString: String = s"ScalaClassLoader(jars = {${getURLs.mkString(", ")}}"
|
||||
}
|
||||
new URLClassLoader(rest, updatedLibraryLoader) {
|
||||
override def toString: String = s"SbtMetaBuildClassLoader"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,14 +10,15 @@ package sbt
|
|||
import java.io.File
|
||||
import java.lang.reflect.{ Method, Modifier }
|
||||
import Modifier.{ isPublic, isStatic }
|
||||
|
||||
import sbt.internal.inc.classpath.ClasspathUtilities
|
||||
import sbt.internal.inc.ScalaInstance
|
||||
import sbt.internal.util.MessageOnlyException
|
||||
|
||||
import sbt.io.Path
|
||||
|
||||
import sbt.util.Logger
|
||||
import scala.util.{ Try, Success, Failure }
|
||||
|
||||
import scala.reflect.internal.util.ScalaClassLoader.URLClassLoader
|
||||
import scala.util.{ Failure, Success, Try }
|
||||
import scala.util.control.NonFatal
|
||||
import scala.sys.process.Process
|
||||
|
||||
|
|
@ -90,14 +91,36 @@ class Run(newLoader: Seq[File] => ClassLoader, trapExit: Boolean) extends ScalaR
|
|||
): Unit = {
|
||||
log.debug(" Classpath:\n\t" + classpath.mkString("\n\t"))
|
||||
val loader = newLoader(classpath)
|
||||
val main = getMainMethod(mainClassName, loader)
|
||||
invokeMain(loader, main, options)
|
||||
try {
|
||||
val main = getMainMethod(mainClassName, loader)
|
||||
invokeMain(loader, main, options)
|
||||
} finally {
|
||||
loader match {
|
||||
case u: URLClassLoader => u.close()
|
||||
case _ =>
|
||||
}
|
||||
}
|
||||
}
|
||||
private def invokeMain(loader: ClassLoader, main: Method, options: Seq[String]): Unit = {
|
||||
private def invokeMain(
|
||||
loader: ClassLoader,
|
||||
main: Method,
|
||||
options: Seq[String]
|
||||
): Unit = {
|
||||
val currentThread = Thread.currentThread
|
||||
val oldLoader = Thread.currentThread.getContextClassLoader
|
||||
currentThread.setContextClassLoader(loader)
|
||||
try { main.invoke(null, options.toArray[String]); () } finally {
|
||||
try { main.invoke(null, options.toArray[String]); () } catch {
|
||||
case t: Throwable =>
|
||||
t.getCause match {
|
||||
case e: java.lang.IllegalAccessError =>
|
||||
val msg = s"Error running $main.\n$e\n" +
|
||||
"If using a layered classloader, this can occur if jvm package private classes are " +
|
||||
"accessed across layers. This can be fixed by changing to the Flat or " +
|
||||
"ScalaInstance class loader layering strategies."
|
||||
throw new IllegalAccessError(msg)
|
||||
case _ => throw t
|
||||
}
|
||||
} finally {
|
||||
currentThread.setContextClassLoader(oldLoader)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
libraryDependencies += "org.scala-sbt.ivy" % "ivy" % "2.3.0-sbt-cb9cc189e9f3af519f9f102e6c5d446488ff6832"
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package org.apache.ivy.plugins.parser.m2
|
||||
|
||||
import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor
|
||||
|
||||
object Run {
|
||||
def main(args: Array[String]): Unit = {
|
||||
new PomModuleDescriptorBuilder.ConfMapper {
|
||||
override def addMappingConfs(dd: DefaultDependencyDescriptor, isOptional: Boolean): Unit = {}
|
||||
}
|
||||
()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
-> run
|
||||
|
||||
> set Compile / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.Flat
|
||||
|
||||
> run
|
||||
|
||||
> set Runtime / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.ScalaInstance
|
||||
|
||||
> run
|
||||
|
|
@ -48,9 +48,10 @@ Provided by:
|
|||
Defined at:
|
||||
\t(sbt.Defaults.testTasks) Defaults.scala:670
|
||||
Dependencies:
|
||||
\tTest / executeTests
|
||||
\tTest / test / streams
|
||||
\tTest / state
|
||||
\tTest / testLoader
|
||||
\tTest / test / streams
|
||||
\tTest / executeTests
|
||||
\tTest / test / testResultLogger
|
||||
Delegates:
|
||||
\tTest / test
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ object RunFromSourceMain {
|
|||
}
|
||||
|
||||
@tailrec private def launch(conf: AppConfiguration): Option[Int] =
|
||||
new xMain().run(conf) match {
|
||||
xMainImpl.run(conf) match {
|
||||
case e: xsbti.Exit => Some(e.code)
|
||||
case _: xsbti.Continue => None
|
||||
case r: xsbti.Reboot => launch(getConf(conf.baseDirectory(), r.arguments()))
|
||||
|
|
|
|||
|
|
@ -177,6 +177,7 @@ final class ScriptedTests(
|
|||
case "classloader-cache/jni" => LauncherBased // sbt/Package$
|
||||
case "classloader-cache/library-mismatch" => LauncherBased // sbt/Package$
|
||||
case "classloader-cache/runtime-layers" => LauncherBased // sbt/Package$
|
||||
case "classloader-cache/package-private" => LauncherBased // sbt/Package$
|
||||
case "compiler-project/dotty-compiler-plugin" => LauncherBased // sbt/Package$
|
||||
case "compiler-project/run-test" => LauncherBased // sbt/Package$
|
||||
case "compiler-project/src-dep-plugin" => LauncherBased // sbt/Package$
|
||||
|
|
|
|||
|
|
@ -122,9 +122,25 @@ final class TestRunner(
|
|||
val results = new scala.collection.mutable.ListBuffer[Event]
|
||||
val handler = new EventHandler { def handle(e: Event): Unit = { results += e } }
|
||||
val loggers: Vector[ContentLogger] = listeners.flatMap(_.contentLogger(testDefinition))
|
||||
def errorEvents(e: Throwable): Array[sbt.testing.Task] = {
|
||||
val taskDef = testTask.taskDef
|
||||
val event = new Event {
|
||||
val status = Status.Error
|
||||
val throwable = new OptionalThrowable(e)
|
||||
val fullyQualifiedName = taskDef.fullyQualifiedName
|
||||
val selector = new TestSelector(name)
|
||||
val fingerprint = taskDef.fingerprint
|
||||
val duration = -1L
|
||||
}
|
||||
results += event
|
||||
Array.empty
|
||||
}
|
||||
val nestedTasks =
|
||||
try testTask.execute(handler, loggers.map(_.log).toArray)
|
||||
finally {
|
||||
catch {
|
||||
case NonFatal(e) => errorEvents(e)
|
||||
case e: IllegalAccessError => errorEvents(e)
|
||||
} finally {
|
||||
loggers.foreach(_.flush())
|
||||
}
|
||||
val event = TestEvent(results)
|
||||
|
|
|
|||
Loading…
Reference in New Issue