Rename some layering related fields/classes

In code review, @eed3si9n suggested that I switch to a more verbose and
descriptive naming scheme. In addition to trying to make layers more
descriptive, I also made the various layer case objects extend the
relevant layers so it's more clear what the layer should look like.
This commit is contained in:
Ethan Atkins 2019-01-28 14:26:18 -08:00
parent 1ca6b97de2
commit 3a211710f9
7 changed files with 72 additions and 57 deletions

View File

@ -8,10 +8,10 @@
package sbt
/**
* Represents a ClassLoader layering strategy. By providing an instance of [[LayeringStrategy]],
* Represents a ClassLoader layering strategy. By providing an instance of [[ClassLoaderLayeringStrategy]],
* users can configure the strategy that they want to use in various sbt tasks, most importantly
* [[Keys.run]] and [[Keys.test]]. This setting is only relevant if fork := false in the task for
* which we obtain a LayeringStrategy.
* which we obtain a ClassLoaderLayeringStrategy.
*
* ClassLoaders can be composed of multiple ClassLoaders
* to form a graph for loading a class. The different portions of the graph may be cached and
@ -62,54 +62,66 @@ package sbt
* In general, this should only happen if the user explicitly overrides the thread context
* ClassLoader or uses reflection to manipulate classes loaded by different loaders.
*/
sealed trait LayeringStrategy
sealed trait ClassLoaderLayeringStrategy
/**
* Provides instances of [[LayeringStrategy]] that can be used to define the ClassLoader used by
* Provides instances of [[ClassLoaderLayeringStrategy]] that can be used to define the ClassLoader used by
* [[Keys.run]], [[Keys.test]] or any other task that runs java code inside of the sbt jvm.
*/
object LayeringStrategy {
object ClassLoaderLayeringStrategy {
/**
* Use the default LayeringStrategy for this task.
* Use the default ClassLoaderLayeringStrategy for this task.
*/
case object Default extends LayeringStrategy
case object Default extends ClassLoaderLayeringStrategy
/**
* Include all of the dependencies in the loader. The base loader will be the Application
* ClassLoader. All classes apart from system classes will be reloaded with each run.
*/
case object Flat extends LayeringStrategy
case object Flat extends ClassLoaderLayeringStrategy
/**
* Add a layer for the runtime jar dependencies.
* Add a layer for the scala instance class loader.
*/
sealed trait RuntimeLayer extends LayeringStrategy
/**
* Add a layer for the runtime jar dependencies.
*/
case object RuntimeDependencies extends RuntimeLayer
/**
* Add a layer for the test jar dependencies.
*/
sealed trait TestLayer extends LayeringStrategy
/**
* Add a layer for the test jar dependencies.
*/
case object TestDependencies extends TestLayer
/**
* Add a layer for the test jar dependencies as well as a layer for the runtime jar dependencies.
*/
case object Full extends RuntimeLayer with TestLayer
sealed trait ScalaInstance extends ClassLoaderLayeringStrategy
/**
* This should indicate that we use a two layer ClassLoader where the top layer is the scala
* instance and all of the dependencies and project class paths are included in the search path
* of the second layer.
*/
case object ScalaInstance extends LayeringStrategy
case object ScalaInstance extends ScalaInstance
/**
* Add a layer on top of the ScalaInstance layer for the runtime jar dependencies.
*/
sealed trait RuntimeDependencies extends ScalaInstance
/**
* Add a layer on top of the ScalaInstance layer for the runtime jar dependencies.
*/
case object RuntimeDependencies extends ScalaInstance with RuntimeDependencies
/**
* Add a layer on top of the ScalaInstance layer for the test jar dependencies.
*/
sealed trait TestDependencies extends ScalaInstance
/**
* Add a layer on top of the ScalaInstance layer for the test jar dependencies.
*/
case object TestDependencies extends ScalaInstance with TestDependencies
/**
* 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.
*/
case object ShareRuntimeDependenciesLayerWithTestDependencies
extends ScalaInstance
with RuntimeDependencies
with TestDependencies
}

View File

@ -141,7 +141,7 @@ object Defaults extends BuildCommon {
defaultTestTasks(test) ++ defaultTestTasks(testOnly) ++ defaultTestTasks(testQuick) ++ Seq(
excludeFilter :== HiddenFileFilter,
classLoaderCache := ClassLoaderCache(4),
layeringStrategy := LayeringStrategy.Default
classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.Default
) ++ TaskRepository
.proxy(GlobalScope / classLoaderCache, ClassLoaderCache(4)) ++ globalIvyCore ++ globalJvmCore
) ++ globalSbtCore
@ -813,7 +813,7 @@ object Defaults extends BuildCommon {
(fullClasspath in test).value,
testForkedParallel.value,
(javaOptions in test).value,
(layeringStrategy).value
(classLoaderLayeringStrategy).value
)
}
).value,
@ -980,7 +980,7 @@ object Defaults extends BuildCommon {
fullClasspath.value,
testForkedParallel.value,
javaOptions.value,
layeringStrategy.value
classLoaderLayeringStrategy.value
)
val taskName = display.show(resolvedScoped.value)
val trl = testResultLogger.value
@ -1024,7 +1024,7 @@ object Defaults extends BuildCommon {
cp,
forkedParallelExecution = false,
javaOptions = Nil,
strategy = LayeringStrategy.Default
strategy = ClassLoaderLayeringStrategy.Default
)
}
@ -1046,7 +1046,7 @@ object Defaults extends BuildCommon {
cp,
forkedParallelExecution,
javaOptions = Nil,
strategy = LayeringStrategy.Default
strategy = ClassLoaderLayeringStrategy.Default
)
}
@ -1059,7 +1059,7 @@ object Defaults extends BuildCommon {
cp: Classpath,
forkedParallelExecution: Boolean,
javaOptions: Seq[String],
strategy: LayeringStrategy,
strategy: ClassLoaderLayeringStrategy,
): Initialize[Task[Tests.Output]] = {
val runners = createTestRunners(frameworks, loader, config)
val groupTasks = groups map {
@ -1091,16 +1091,18 @@ object Defaults extends BuildCommon {
case (suite, e) =>
e.throwables
.collectFirst {
case t if t.isInstanceOf[NoClassDefFoundError] && strategy != LayeringStrategy.Flat =>
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 LayeringStrategy"
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"
+ " LayeringStrategy in your configuration (generally Test or IntegrationTest),"
+ "e.g.:\nTest / layeringStrategy := LayeringStrategy.Flat\n"
+ "See LayeringStrategy.scala for the full list of options."
+ " ClassLoaderLayeringStrategy in your configuration (generally Test or IntegrationTest),"
+ "e.g.:\nTest / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.Flat\n"
+ "See ClassLoaderLayeringStrategy.scala for the full list of options."
)
}
}

View File

@ -264,7 +264,7 @@ object Keys {
val bgRunMain = inputKey[JobHandle]("Start a provided main class as a background job")
val fgRunMain = inputKey[Unit]("Start a provided main class as a foreground job")
val bgCopyClasspath = settingKey[Boolean]("Copies classpath on bgRun to prevent conflict.")
val layeringStrategy = settingKey[LayeringStrategy]("Creates the classloader layering strategy for the particular configuration.")
val classLoaderLayeringStrategy = settingKey[ClassLoaderLayeringStrategy]("Creates the classloader layering strategy for the particular configuration.")
// Test Keys
val testLoader = taskKey[ClassLoader]("Provides the class loader used for testing.").withRank(DTask)

View File

@ -35,15 +35,15 @@ 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 strategy = layeringStrategy.value
val strategy = classLoaderLayeringStrategy.value
val runtimeCache = (Runtime / classLoaderCache).value
val testCache = classLoaderCache.value
val tmp = IO.createUniqueDirectory(taskTemporaryDirectory.value)
val resources = ClasspathUtilities.createClasspathResources(fullCP, si)
val raw = strategy match {
case LayeringStrategy.Flat => flatLoader(rawCP, interfaceLoader)
case s =>
case ClassLoaderLayeringStrategy.Flat => flatLoader(rawCP, interfaceLoader)
case s =>
/*
* Create a layered classloader. There are up to four layers:
* 1) the scala instance class loader
@ -55,10 +55,11 @@ private[sbt] object ClassLoaders {
* and test dependencies, it's important to be able to configure which layers are used.
*/
val (layerDependencies, layerTestDependencies) = s match {
case LayeringStrategy.Full => (true, true)
case LayeringStrategy.ScalaInstance => (false, false)
case LayeringStrategy.RuntimeDependencies => (true, false)
case _ => (false, true)
case ClassLoaderLayeringStrategy.ShareRuntimeDependenciesLayerWithTestDependencies =>
(true, true)
case ClassLoaderLayeringStrategy.ScalaInstance => (false, false)
case ClassLoaderLayeringStrategy.RuntimeDependencies => (true, false)
case _ => (false, true)
}
// Do not include exportedProducts in any cached layers because they may change between runs.
val exclude = dependencyJars(exportedProducts).value.toSet ++ si.allJars.toSeq
@ -127,8 +128,8 @@ private[sbt] object ClassLoaders {
val newLoader =
(classpath: Seq[File]) => {
val resources = ClasspathUtilities.createClasspathResources(classpath, instance)
val classLoader = layeringStrategy.value match {
case LayeringStrategy.Flat =>
val classLoader = classLoaderLayeringStrategy.value match {
case ClassLoaderLayeringStrategy.Flat =>
ClasspathUtilities
.toLoader(Nil, flatLoader(classpath, new NullLoader), resources, tmp)
case _ =>

View File

@ -1,4 +1,4 @@
> set Test / layeringStrategy := LayeringStrategy.Full
> set Test / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.ShareRuntimeDependenciesLayerWithTestDependencies
> run

View File

@ -1,4 +1,4 @@
> set Test / layeringStrategy := LayeringStrategy.Full
> set Test / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.ShareRuntimeDependenciesLayerWithTestDependencies
> run
@ -6,7 +6,7 @@
# have the sbt.foo.Foo.y method defined.
> test
> set Test / layeringStrategy := LayeringStrategy.TestDependencies
> set Test / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.TestDependencies
> run

View File

@ -52,8 +52,8 @@ final class TestFramework(val implClassNames: String*) extends Serializable {
+ " using a layered class loader that cannot reach the sbt.testing.Framework class."
+ " The most likely cause is that your project has a runtime dependency on your"
+ " test framework, e.g. scalatest. To fix this, you can try to set\n"
+ "Test / layeringStrategy := new LayeringStrategy.Test(false, true)\nor\n"
+ "Test / layeringStrategy := LayeringStrategy.Flat"
+ "Test / classLoaderLayeringStrategy := new ClassLoaderLayeringStrategy.Test(false, true)\nor\n"
+ "Test / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.Flat"
)
None
}