diff --git a/.travis.yml b/.travis.yml index eca3b1567..494b09b04 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,7 +23,7 @@ env: - SBT_CMD="scripted source-dependencies/*1of3" - SBT_CMD="scripted source-dependencies/*2of3" - SBT_CMD="scripted source-dependencies/*3of3" - - SBT_CMD="scripted tests/* watch/*" + - SBT_CMD="scripted tests/* watch/* classloader-cache/*" - SBT_CMD="repoOverrideTest:scripted dependency-management/*" matrix: @@ -53,7 +53,7 @@ install: script: # It doesn't need that much memory because compile and run are forked - - sbt -J-XX:ReservedCodeCacheSize=128m -J-Xmx800M -J-Xms800M -J-server "$SBT_CMD" + - sbt -Dsbt.version=1.2.6 -J-XX:ReservedCodeCacheSize=128m -J-Xmx800M -J-Xms800M -J-server "$SBT_CMD" before_cache: - find $HOME/.ivy2 -name "ivydata-*.properties" -delete diff --git a/main-command/src/main/scala/sbt/BasicCommandStrings.scala b/main-command/src/main/scala/sbt/BasicCommandStrings.scala index cd760a313..5240a060f 100644 --- a/main-command/src/main/scala/sbt/BasicCommandStrings.scala +++ b/main-command/src/main/scala/sbt/BasicCommandStrings.scala @@ -233,8 +233,6 @@ $AliasCommand name= def continuousDetail: String = "Executes the specified command whenever source files change." def continuousBriefHelp: (String, String) = (ContinuousExecutePrefix + " ", continuousDetail) - def FlushFileTreeRepository: String = "flushFileTreeRepository" - def FlushDetailed: String = - "Resets the global file repository in the event that the repository has become inconsistent " + - "with the file system." + def ClearCaches: String = "clearCaches" + def ClearCachesDetailed: String = "Clears all of sbt's internal caches." } diff --git a/main-command/src/main/scala/sbt/BasicKeys.scala b/main-command/src/main/scala/sbt/BasicKeys.scala index 66de24ce6..0f71774f8 100644 --- a/main-command/src/main/scala/sbt/BasicKeys.scala +++ b/main-command/src/main/scala/sbt/BasicKeys.scala @@ -8,6 +8,7 @@ package sbt import java.io.File + import sbt.internal.util.AttributeKey import sbt.internal.inc.classpath.ClassLoaderCache import sbt.internal.server.ServerHandler diff --git a/main/src/main/scala/sbt/ClassLoaderLayeringStrategy.scala b/main/src/main/scala/sbt/ClassLoaderLayeringStrategy.scala new file mode 100644 index 000000000..89eae4f05 --- /dev/null +++ b/main/src/main/scala/sbt/ClassLoaderLayeringStrategy.scala @@ -0,0 +1,122 @@ +/* + * sbt + * Copyright 2011 - 2018, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under Apache License 2.0 (see LICENSE) + */ + +package sbt + +/** + * 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 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 + * reused to minimize both the memory taken up by ClassLoaders (and the classes that they load) and + * the startup time for tasks like test and run. For example, the scala library is large and takes + * a while just to load the classes in predef. The [[Keys.scalaInstance]] task provides access to + * a classloader that can load all of the java bootstrap classes and scala.*. Supposing that we want + * to run code in a jar containing scala code called "foo_2.12.jar" in the base directory and that + * we have a scala instance in scope and suppose further that "foo_2.12.jar" contains a main method + * in the class foo.Main, then we can invoke foo.Main.main like so + * + * {{{ + * val fooJarFile = new File("foo_2.12.jar") + * val classLoader = new URLClassLoader( + * Array(fooJarFile.toURI.toURL), scalaInstance.loaderLibraryOnly) + * val main = classLoader.loadClass("foo.Main").getDeclaredMethod("main", classOf[Array[String]]) + * main.invoke(null, Array.empty[String]) + * }}} + * + * Now suppose that we have an alternative jar "foo_alt_2.12.jar" that also provides foo.Main, then + * we can run that main method: + * + * {{{ + * val fooJarFile = new File("foo_alt_2.12.jar") + * val altClassLoader = new URLClassLoader( + * Array(fooAltJarFile.toURI.toURL), scalaInstance.loaderLibraryOnly) + * val altMain = classLoader.loadClass("foo.Main").getDeclaredMethod("main", classOf[Array[String]]) + * altMain.invoke(null, Array.empty[String]) + * }}} + * + * In the second invocation, the scala library will have already been loaded by the + * scalaInstance.loaderLibraryOnly ClassLoader. This can reduce the startup time by O(500ms) and + * prevents an accumulation of scala related Class objects. Note that these ClassLoaders should + * only be used at a code boundary such that their loaded classes do not leak outside of the + * defining scope. This is because the layered class loaders can create mutually incompatible + * classes. For example, in the example above, suppose that there is a class foo.Bar provided + * by both "foo_2.12.jar" and "foo_2.12.jar" and that both also provide a static method + * "foo.Foo$.bar" that returns an instance of foo.Bar, then the following code will not work: + * + * {{{ + * Thread.currentThread.setContextClassLoader(altClassLoader) + * val bar: Object = classLoader.loadClass("foo.Foo$").getDeclaredMethod("bar").invoke(null) + * val barTyped: foo.Bar = bar.asInstanceOf[foo.Bar] + * // throws ClassCastException because the thread context class loader is altClassLoader, but + * // but bar was loaded by classLoader. + * }}} + * + * 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 ClassLoaderLayeringStrategy + +/** + * 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 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 ClassLoaderLayeringStrategy + + /** + * Add a layer for the scala instance class loader. + */ + 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 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 + +} diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 95dd8295f..6d0590334 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -139,8 +139,10 @@ object Defaults extends BuildCommon { ) private[sbt] lazy val globalCore: Seq[Setting[_]] = globalDefaults( defaultTestTasks(test) ++ defaultTestTasks(testOnly) ++ defaultTestTasks(testQuick) ++ Seq( - excludeFilter :== HiddenFileFilter - ) ++ globalIvyCore ++ globalJvmCore + excludeFilter :== HiddenFileFilter, + classLoaderCache := ClassLoaderCache(4), + ) ++ TaskRepository + .proxy(GlobalScope / classLoaderCache, ClassLoaderCache(4)) ++ globalIvyCore ++ globalJvmCore ) ++ globalSbtCore private[sbt] lazy val globalJvmCore: Seq[Setting[_]] = @@ -790,11 +792,7 @@ object Defaults extends BuildCommon { : Seq[Setting[_]] = testTaskOptions(test) ++ testTaskOptions(testOnly) ++ testTaskOptions( testQuick ) ++ testDefaults ++ Seq( - testLoader := TestFramework.createTestLoader( - data(fullClasspath.value), - scalaInstance.value, - IO.createUniqueDirectory(taskTemporaryDirectory.value) - ), + testLoader := ClassLoaders.testTask.value, loadedTestFrameworks := { val loader = testLoader.value val log = streams.value.log @@ -813,7 +811,8 @@ object Defaults extends BuildCommon { (testExecution in test).value, (fullClasspath in test).value, testForkedParallel.value, - (javaOptions in test).value + (javaOptions in test).value, + (classLoaderLayeringStrategy).value ) } ).value, @@ -979,7 +978,8 @@ object Defaults extends BuildCommon { newConfig, fullClasspath.value, testForkedParallel.value, - javaOptions.value + javaOptions.value, + classLoaderLayeringStrategy.value ) val taskName = display.show(resolvedScoped.value) val trl = testResultLogger.value @@ -1022,7 +1022,8 @@ object Defaults extends BuildCommon { config, cp, forkedParallelExecution = false, - javaOptions = Nil + javaOptions = Nil, + strategy = ClassLoaderLayeringStrategy.TestDependencies, ) } @@ -1043,7 +1044,8 @@ object Defaults extends BuildCommon { config, cp, forkedParallelExecution, - javaOptions = Nil + javaOptions = Nil, + strategy = ClassLoaderLayeringStrategy.TestDependencies, ) } @@ -1055,7 +1057,8 @@ object Defaults extends BuildCommon { config: Tests.Execution, cp: Classpath, forkedParallelExecution: Boolean, - javaOptions: Seq[String] + javaOptions: Seq[String], + strategy: ClassLoaderLayeringStrategy, ): Initialize[Task[Tests.Output]] = { val runners = createTestRunners(frameworks, loader, config) val groupTasks = groups map { @@ -1083,6 +1086,25 @@ object Defaults extends BuildCommon { } val output = Tests.foldTasks(groupTasks, config.parallel) 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." + ) + } + } val summaries = runners map { case (tf, r) => @@ -1384,37 +1406,11 @@ object Defaults extends BuildCommon { } } + @deprecated("This is no longer used internally by sbt.", "1.3.0") def runnerTask: Setting[Task[ScalaRun]] = runner := runnerInit.value - def runnerInit: Initialize[Task[ScalaRun]] = Def.task { - val tmp = taskTemporaryDirectory.value - val resolvedScope = resolvedScoped.value.scope - val si = scalaInstance.value - val s = streams.value - val opts = forkOptions.value - val options = javaOptions.value - val trap = trapExit.value - if (fork.value) { - s.log.debug(s"javaOptions: $options") - new ForkRun(opts) - } else { - if (options.nonEmpty) { - val mask = ScopeMask(project = false) - val showJavaOptions = Scope.displayMasked( - (javaOptions in resolvedScope).scopedKey.scope, - (javaOptions in resolvedScope).key.label, - mask - ) - val showFork = Scope.displayMasked( - (fork in resolvedScope).scopedKey.scope, - (fork in resolvedScope).key.label, - mask - ) - s.log.warn(s"$showJavaOptions will be ignored, $showFork is set to false") - } - new Run(si, trap, tmp) - } - } + @deprecated("This is no longer used internally by sbt.", "1.3.0") + def runnerInit: Initialize[Task[ScalaRun]] = ClassLoaders.runner private def foreachJobTask( f: (BackgroundJobService, JobHandle) => Unit @@ -1764,7 +1760,11 @@ object Defaults extends BuildCommon { // 1. runnerSettings is added unscoped via JvmPlugin. // 2. In addition it's added scoped to run task. - lazy val runnerSettings: Seq[Setting[_]] = Seq(runnerTask, forkOptions := forkOptionsTask.value) + lazy val runnerSettings: Seq[Setting[_]] = { + val unscoped: Seq[Def.Setting[_]] = + Seq(runner := ClassLoaders.runner.value, forkOptions := forkOptionsTask.value) + inConfig(Compile)(unscoped) ++ inConfig(Test)(unscoped) + } lazy val baseTasks: Seq[Setting[_]] = projectTasks ++ packageBase @@ -1772,16 +1772,32 @@ object Defaults extends BuildCommon { Classpaths.configSettings ++ configTasks ++ configPaths ++ packageConfig ++ Classpaths.compilerPluginConfig ++ deprecationSettings + private val runtimeLayeringSettings: Seq[Setting[_]] = TaskRepository.proxy( + 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) + ) :+ (classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.RuntimeDependencies) + lazy val compileSettings: Seq[Setting[_]] = configSettings ++ (mainBgRunMainTask +: mainBgRunTask +: FileManagement.appendBaseSources) ++ - Classpaths.addUnmanagedLibrary + Classpaths.addUnmanagedLibrary ++ runtimeLayeringSettings + private val testLayeringSettings: Seq[Setting[_]] = TaskRepository.proxy( + classLoaderCache, + // We need a cache of size two for the test dependency layers (regular and snapshot). + ClassLoaderCache(2) + ) :+ (classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.TestDependencies) lazy val testSettings: Seq[Setting[_]] = configSettings ++ testTasks - lazy val itSettings: Seq[Setting[_]] = inConfig(IntegrationTest)(testSettings) + lazy val itSettings: Seq[Setting[_]] = inConfig(IntegrationTest) { + testSettings :+ (classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.RuntimeDependencies) + } lazy val defaultConfigs: Seq[Setting[_]] = inConfig(Compile)(compileSettings) ++ - inConfig(Test)(testSettings) ++ inConfig(Runtime)(Classpaths.configSettings) + inConfig(Test)(testSettings ++ testLayeringSettings) ++ + inConfig(Runtime)(Classpaths.configSettings) // These are project level settings that MUST be on every project. lazy val coreDefaultSettings: Seq[Setting[_]] = @@ -3556,17 +3572,19 @@ trait BuildExtra extends BuildCommon with DefExtra { InputTask.apply(Def.value((s: State) => Def.spaceDelimited()))(f) Vector( - scoped := (inputTask { result => - (initScoped(scoped.scopedKey, runnerInit) - .zipWith(Def.task { ((fullClasspath in config).value, streams.value, result.value) })) { + scoped := inputTask { result => + initScoped( + scoped.scopedKey, + ClassLoaders.runner mapReferenced Project.mapScope(s => s.in(config)) + ).zipWith(Def.task { ((fullClasspath in config).value, streams.value, result.value) }) { (rTask, t) => (t, rTask) map { case ((cp, s, args), r) => r.run(mainClass, data(cp), baseArguments ++ args, s.log).get } } - }).evaluated - ) ++ inTask(scoped)(forkOptions := forkOptionsTask.value) + }.evaluated + ) ++ inTask(scoped)(forkOptions in config := forkOptionsTask.value) } // public API @@ -3578,16 +3596,18 @@ trait BuildExtra extends BuildCommon with DefExtra { arguments: String* ): Vector[Setting[_]] = Vector( - scoped := ((initScoped(scoped.scopedKey, runnerInit) - .zipWith(Def.task { ((fullClasspath in config).value, streams.value) })) { + scoped := initScoped( + scoped.scopedKey, + ClassLoaders.runner mapReferenced Project.mapScope(s => s.in(config)) + ).zipWith(Def.task { ((fullClasspath in config).value, streams.value) }) { case (rTask, t) => (t, rTask) map { case ((cp, s), r) => r.run(mainClass, data(cp), arguments, s.log).get } - }) + } .value - ) ++ inTask(scoped)(forkOptions := forkOptionsTask.value) + ) ++ inTask(scoped)(forkOptions in config := forkOptionsTask.value) def initScoped[T](sk: ScopedKey[_], i: Initialize[T]): Initialize[T] = initScope(fillTaskAxis(sk.scope, sk.key), i) diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index cf8cf4356..6696e9171 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -264,6 +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 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) @@ -460,6 +461,8 @@ 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 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) // wrapper to work around SI-2915 private[sbt] final class TaskProgress(val progress: ExecuteProgress[Task]) diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index 2dfb6da25..0ae325509 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -214,7 +214,7 @@ object BuiltinCommands { BasicCommands.multi, act, continuous, - flushFileTreeRepository + clearCaches ) ++ allBasicCommands def DefaultBootCommands: Seq[String] = @@ -830,7 +830,7 @@ object BuiltinCommands { val session = Load.initialSession(structure, eval, s0) SessionSettings.checkSession(session, s) - registerGlobalFileRepository(Project.setProject(session, structure, s)) + registerGlobalCaches(Project.setProject(session, structure, s)) } def registerCompilerCache(s: State): State = { @@ -848,26 +848,31 @@ object BuiltinCommands { } s.put(Keys.stateCompilerCache, cache) } - def registerGlobalFileRepository(s: State): State = { + private[sbt] def registerGlobalCaches(s: State): State = { val extracted = Project.extract(s) try { - val (_, config: FileTreeViewConfig) = extracted.runTask(Keys.fileTreeViewConfig, s) - val view: FileTreeDataView[StampedFile] = config.newDataView() - val newState = s.addExitHook { + def cleanup(): Unit = { s.get(BasicKeys.globalFileTreeView).foreach(_.close()) s.attributes.remove(BasicKeys.globalFileTreeView) + s.get(Keys.taskRepository).foreach(_.close()) + s.attributes.remove(Keys.taskRepository) () } - newState.get(BasicKeys.globalFileTreeView).foreach(_.close()) - newState.put(BasicKeys.globalFileTreeView, view) + val (_, config: FileTreeViewConfig) = extracted.runTask(Keys.fileTreeViewConfig, s) + val view: FileTreeDataView[StampedFile] = config.newDataView() + val newState = s.addExitHook(cleanup()) + cleanup() + newState + .put(BasicKeys.globalFileTreeView, view) + .put(Keys.taskRepository, new TaskRepository.Repr) } catch { case NonFatal(_) => s } } - def flushFileTreeRepository: Command = { - val help = Help.more(FlushFileTreeRepository, FlushDetailed) - Command.command(FlushFileTreeRepository, help)(registerGlobalFileRepository) + def clearCaches: Command = { + val help = Help.more(ClearCaches, ClearCachesDetailed) + Command.command(ClearCaches, help)(registerGlobalCaches) } def shell: Command = Command.command(Shell, Help.more(Shell, ShellDetailed)) { s0 => diff --git a/main/src/main/scala/sbt/internal/ClassLoaderCache.scala b/main/src/main/scala/sbt/internal/ClassLoaderCache.scala new file mode 100644 index 000000000..3a2c3db04 --- /dev/null +++ b/main/src/main/scala/sbt/internal/ClassLoaderCache.scala @@ -0,0 +1,98 @@ +/* + * sbt + * Copyright 2011 - 2018, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under Apache License 2.0 (see LICENSE) + */ + +package sbt.internal + +import java.io.File +import java.nio.file.Files + +import sbt.internal.util.TypeFunctions.Id + +import scala.annotation.tailrec + +sealed trait ClassLoaderCache + extends Repository[Id, (Seq[File], ClassLoader, Map[String, String], File), ClassLoader] + +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 = + new ClassLoaderCache { + private final def mktmp(tmp: File): File = + if (maxSize > 0) Files.createTempDirectory("sbt-jni").toFile else tmp + private[this] val lruCache = + LRUCache[(JarClassPath, ClassLoader), (JarClassPath, CachedClassLoader)]( + maxSize = maxSize, + onExpire = + (_: (JarClassPath, ClassLoader), v: (JarClassPath, CachedClassLoader)) => close(v._2) + ) + override def get(info: (Seq[File], ClassLoader, Resources, File)): ClassLoader = + synchronized { + val (paths, parent, resources, tmp) = info + val key @ (keyJCP, _) = (new JarClassPath(paths), parent) + def addLoader(base: Option[StableClassLoader] = None): CachedClassLoader = { + val baseLoader = base.getOrElse { + if (keyJCP.regularJars.isEmpty) new ClassLoader(parent) with StableClassLoader { + override def close(): Unit = parent match { + case s: StableClassLoader => s.close() + case _ => () + } + override def toString: String = parent.toString + } else + new LayeredClassLoader(keyJCP.regularJars, parent, resources, mktmp(tmp)) + with StableClassLoader + } + val loader: CachedClassLoader = + if (keyJCP.snapshotJars.isEmpty) baseLoader + else + new LayeredClassLoader(keyJCP.snapshotJars, baseLoader, resources, mktmp(tmp)) + with SnapshotClassLoader + lruCache.put(key, keyJCP -> loader) + loader + } + lruCache.get(key) match { + case Some((jcp, cl)) if keyJCP.strictEquals(jcp) => cl + case Some((_, cl: SnapshotClassLoader)) => + cl.close() + cl.getParent match { + case p: StableClassLoader => addLoader(Some(p)) + case _ => addLoader() + } + case None => addLoader() + } + } + override def close(): Unit = synchronized(lruCache.close()) + override def toString: String = { + import PrettyPrint.indent + val cacheElements = lruCache.entries.map { + case ((jcp, parent), (_, l)) => + s"(\n${indent(jcp, 4)},\n${indent(parent, 4)}\n) =>\n $l" + } + s"ClassLoaderCache(\n size = $maxSize,\n elements =\n${indent(cacheElements, 4)}\n)" + } + + // Close the ClassLoader and all of it's closeable parents. + @tailrec + private def close(loader: CachedClassLoader): Unit = { + loader.close() + loader.getParent match { + case c: CachedClassLoader => close(c) + case _ => () + } + } + } + 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 = {} + } +} diff --git a/main/src/main/scala/sbt/internal/ClassLoaders.scala b/main/src/main/scala/sbt/internal/ClassLoaders.scala new file mode 100644 index 000000000..79a303f80 --- /dev/null +++ b/main/src/main/scala/sbt/internal/ClassLoaders.scala @@ -0,0 +1,249 @@ +/* + * sbt + * Copyright 2011 - 2018, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under Apache License 2.0 (see LICENSE) + */ + +package sbt +package internal + +import java.io.File +import java.net.URLClassLoader + +import sbt.Keys._ +import sbt.SlashSyntax0._ +import sbt.internal.inc.ScalaInstance +import sbt.internal.inc.classpath.{ ClasspathUtilities, DualLoader, NullLoader } +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, _ } + +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) + } + /* + * Get the class loader for a test task. The configuration could be IntegrationTest or Test. + */ + private[sbt] def testTask: Def.Initialize[Task[ClassLoader]] = Def.task { + 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 + 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 + ) + } + + private[sbt] def runner: Def.Initialize[Task[ScalaRun]] = Def.taskDyn { + val resolvedScope = resolvedScoped.value.scope + val instance = scalaInstance.value + val s = streams.value + val opts = forkOptions.value + val options = javaOptions.value + if (fork.value) { + s.log.debug(s"javaOptions: $options") + Def.task(new ForkRun(opts)) + } else { + Def.task { + if (options.nonEmpty) { + val mask = ScopeMask(project = false) + val showJavaOptions = Scope.displayMasked( + (javaOptions in resolvedScope).scopedKey.scope, + (javaOptions in resolvedScope).key.label, + mask + ) + val showFork = Scope.displayMasked( + (fork in resolvedScope).scopedKey.scope, + (fork in resolvedScope).key.label, + mask + ) + s.log.warn(s"$showJavaOptions will be ignored, $showFork is set to false") + } + val runtimeCache = (Runtime / classLoaderCache).value + val testCache = classLoaderCache.value + val exclude = dependencyJars(exportedProducts).value.toSet ++ instance.allJars + 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 + ) + } + new Run(newLoader, trapExit.value) + } + } + } + + /* + * Create a layered classloader. There are up to four layers: + * 1) the scala instance class loader + * 2) the runtime dependencies + * 3) the test dependencies + * 4) the rest of the classpath + * The first two layers may be optionally cached to reduce memory usage and improve + * start up latency. Because there may be mutually incompatible libraries in the runtime + * and test dependencies, it's important to be able to configure which layers are used. + */ + private def buildLayers( + strategy: ClassLoaderLayeringStrategy, + si: ScalaInstance, + fullCP: Seq[File], + rawRuntimeDependencies: Seq[File], + allDependencies: Set[File], + base: ClassLoader, + runtimeCache: ClassLoaderCache, + testCache: ClassLoaderCache, + resources: Map[String, String], + tmp: File, + scope: Scope + ): ClassLoader = { + val isTest = scope.config.toOption.map(_.name) == Option("test") + val raw = strategy match { + case Flat => flatLoader(fullCP, base) + case _ => + val (layerDependencies, layerTestDependencies) = strategy match { + case ShareRuntimeDependenciesLayerWithTestDependencies if isTest => (true, true) + case ScalaInstanceLayer => (false, false) + case RuntimeDependencies => (true, false) + case TestDependencies if isTest => (false, true) + case badStrategy => + val msg = s"Layering strategy $badStrategy is not valid for the classloader in " + + s"$scope. Valid options are: ClassLoaderLayeringStrategy.{ " + + "Flat, ScalaInstance, RuntimeDependencies }" + throw new IllegalArgumentException(msg) + } + // 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 allRuntimeDependencies = (if (layerDependencies) rawRuntimeDependencies else Nil).toSet + + // layer 2 + val runtimeDependencies = allDependencies intersect allRuntimeDependencies + val runtimeLayer = + layer(runtimeDependencies.toSeq, loader(si), runtimeCache, resources, tmp) + + // 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) + + // layer 4 + val dynamicClasspath = + fullCP.filterNot(testDependencies ++ runtimeDependencies ++ si.allJars) + if (dynamicClasspath.nonEmpty) + new LayeredClassLoader(dynamicClasspath, testLayer, resources, tmp) + else testLayer + } + ClasspathUtilities.filterByClasspath(fullCP, raw) + } + private def dependencyJars( + key: sbt.TaskKey[Seq[Attributed[File]]] + ): Def.Initialize[Task[Seq[File]]] = Def.task(data(key.value).filter(_.getName.endsWith(".jar"))) + + // Creates a one or two layered classloader for the provided classpaths depending on whether + // or not the classpath contains any snapshots. If it does, the snapshots are placed in a layer + // above the regular jar layer. This allows the snapshot layer to be invalidated without + // invalidating the regular jar layer. If the classpath is empty, it just returns the parent + // loader. + private def layer( + classpath: Seq[File], + parent: ClassLoader, + cache: ClassLoaderCache, + resources: Map[String, String], + tmp: File + ): ClassLoader = { + val (snapshots, jars) = classpath.partition(_.toString.contains("-SNAPSHOT")) + val jarLoader = if (jars.isEmpty) parent else cache.get((jars, parent, resources, tmp)) + 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) + ) { + override def equals(o: Any): Boolean = o match { + case that: WrappedDualLoader => this.parent == that.parent && this.child == that.child + case _ => false + } + override def hashCode: Int = (parent.hashCode * 31) ^ child.hashCode + override lazy val toString: String = string + } + 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 + } +} diff --git a/main/src/main/scala/sbt/internal/FileManagement.scala b/main/src/main/scala/sbt/internal/FileManagement.scala index a18d3ef03..ec6eb2b8c 100644 --- a/main/src/main/scala/sbt/internal/FileManagement.scala +++ b/main/src/main/scala/sbt/internal/FileManagement.scala @@ -26,7 +26,7 @@ private[sbt] object FileManagement { val interactive = remaining.contains("shell") || remaining.lastOption.contains("iflast shell") val scripted = remaining.contains("setUpScripted") - val continuous = remaining.exists(_.startsWith(ContinuousExecutePrefix)) + val continuous = remaining.lastOption.exists(_.startsWith(ContinuousExecutePrefix)) if (!scripted && (interactive || continuous)) { FileTreeViewConfig .default(watchAntiEntropy.value, pollInterval.value, pollingDirectories.value) diff --git a/main/src/main/scala/sbt/internal/JarClassPath.scala b/main/src/main/scala/sbt/internal/JarClassPath.scala new file mode 100644 index 000000000..93ed22690 --- /dev/null +++ b/main/src/main/scala/sbt/internal/JarClassPath.scala @@ -0,0 +1,55 @@ +/* + * sbt + * Copyright 2011 - 2018, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under Apache License 2.0 (see LICENSE) + */ + +package sbt.internal + +import java.io.File + +import sbt.io.IO + +private[internal] object JarClassPath { + class Snapshot private[internal] (val file: File, val lastModified: Long) + extends Comparable[JarClassPath.Snapshot] { + private[this] val _hash = (file.hashCode * 31) ^ java.lang.Long.valueOf(lastModified).hashCode() + def this(file: File) = this(file, IO.getModifiedTimeOrZero(file)) + override def equals(obj: Any): Boolean = obj match { + case that: JarClassPath.Snapshot => + this.lastModified == that.lastModified && this.file == that.file + case _ => false + } + override def hashCode: Int = _hash + override def compareTo(that: JarClassPath.Snapshot): Int = this.file.compareTo(that.file) + override def toString: String = + "Snapshot(path = " + file + ", lastModified = " + lastModified + ")" + } +} +private[internal] final class JarClassPath(val jars: Seq[File]) { + private[this] def isSnapshot(file: File): Boolean = file.getName contains "-SNAPSHOT" + private val jarSet = jars.toSet + val (snapshotJars, regularJars) = jars.partition(isSnapshot) + private val snapshots = snapshotJars.map(new JarClassPath.Snapshot(_)) + + override def equals(obj: Any): Boolean = obj match { + case that: JarClassPath => this.jarSet == that.jarSet + case _ => false + } + // The memoization is because codacy isn't smart enough to identify that + // `override lazy val hashCode: Int = jarSet.hashCode` does actually override hashCode and it + // complains that equals and hashCode were not implemented together. + private[this] lazy val _hashCode: Int = jarSet.hashCode + override def hashCode: Int = _hashCode + override def toString: String = + s"JarClassPath(\n jars =\n ${regularJars.mkString(",\n ")}" + + s", snapshots =\n${snapshots.mkString(",\n ")}\n)" + + /* + * This is a stricter equality requirement than equals that we can use for cache invalidation. + */ + private[internal] def strictEquals(that: JarClassPath): Boolean = { + this.equals(that) && !this.snapshots.view.zip(that.snapshots).exists { case (l, r) => l != r } + } +} diff --git a/main/src/main/scala/sbt/internal/LRUCache.scala b/main/src/main/scala/sbt/internal/LRUCache.scala new file mode 100644 index 000000000..a80a2837a --- /dev/null +++ b/main/src/main/scala/sbt/internal/LRUCache.scala @@ -0,0 +1,113 @@ +/* + * sbt + * Copyright 2011 - 2018, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under Apache License 2.0 (see LICENSE) + */ + +package sbt.internal + +import java.util.concurrent.atomic.AtomicInteger + +import scala.annotation.tailrec + +private[sbt] sealed trait LRUCache[K, V] extends AutoCloseable { + def get(key: K): Option[V] + def entries: Seq[(K, V)] + def maxSize: Int + def put(key: K, value: V): Option[V] + def remove(key: K): Option[V] + def size: Int +} + +private[sbt] object LRUCache { + private[this] class impl[K, V](override val maxSize: Int, onExpire: Option[((K, V)) => Unit]) + extends LRUCache[K, V] { + private[this] val elementsSortedByAccess: Array[(K, V)] = new Array[(K, V)](maxSize) + private[this] val lastIndex: AtomicInteger = new AtomicInteger(-1) + + override def close(): Unit = this.synchronized { + val f = onExpire.getOrElse((_: (K, V)) => Unit) + 0 until maxSize foreach { i => + elementsSortedByAccess(i) match { + case null => + case el => f(el) + } + elementsSortedByAccess(i) = null + } + lastIndex.set(-1) + } + override def entries: Seq[(K, V)] = this.synchronized { + (0 to lastIndex.get()).map(elementsSortedByAccess) + } + override def get(key: K): Option[V] = this.synchronized { + indexOf(key) match { + case -1 => None + case i => replace(i, key, elementsSortedByAccess(i)._2) + } + } + override def put(key: K, value: V): Option[V] = this.synchronized { + indexOf(key) match { + case -1 => + append(key, value) + None + case i => replace(i, key, value) + } + } + override def remove(key: K): Option[V] = this.synchronized { + indexOf(key) match { + case -1 => None + case i => remove(i, lastIndex.get, expire = false) + } + } + override def size: Int = lastIndex.get + 1 + override def toString: String = { + val values = 0 to lastIndex.get() map { i => + val (key, value) = elementsSortedByAccess(i) + s"$key -> $value" + } + s"LRUCache(${values mkString ", "})" + } + + private def indexOf(key: K): Int = + elementsSortedByAccess.view.take(lastIndex.get() + 1).indexWhere(_._1 == key) + private def replace(index: Int, key: K, value: V): Option[V] = { + val prev = remove(index, lastIndex.get(), expire = false) + append(key, value) + prev + } + private def append(key: K, value: V): Unit = { + while (lastIndex.get() >= maxSize - 1) { + remove(0, lastIndex.get(), expire = true) + } + val index = lastIndex.incrementAndGet() + elementsSortedByAccess(index) = (key, value) + } + private def remove(index: Int, endIndex: Int, expire: Boolean): Option[V] = { + @tailrec + def shift(i: Int): Unit = if (i < endIndex) { + elementsSortedByAccess(i) = elementsSortedByAccess(i + 1) + shift(i + 1) + } + val prev = elementsSortedByAccess(index) + shift(index) + lastIndex.set(endIndex - 1) + if (expire) onExpire.foreach(f => f(prev)) + Some(prev._2) + } + } + private def emptyCache[K, V]: LRUCache[K, V] = new LRUCache[K, V] { + override def get(key: K): Option[V] = None + override def entries: Seq[(K, V)] = Nil + override def maxSize: Int = 0 + override def put(key: K, value: V): Option[V] = None + override def remove(key: K): Option[V] = None + override def size: Int = 0 + override def close(): Unit = {} + override def toString = "EmptyLRUCache" + } + def apply[K, V](maxSize: Int): LRUCache[K, V] = + if (maxSize > 0) new impl(maxSize, None) else emptyCache + def apply[K, V](maxSize: Int, onExpire: (K, V) => Unit): LRUCache[K, V] = + if (maxSize > 0) new impl(maxSize, Some(onExpire.tupled)) else emptyCache +} diff --git a/main/src/main/scala/sbt/internal/LayeredClassLoader.scala b/main/src/main/scala/sbt/internal/LayeredClassLoader.scala new file mode 100644 index 000000000..a2c00de52 --- /dev/null +++ b/main/src/main/scala/sbt/internal/LayeredClassLoader.scala @@ -0,0 +1,68 @@ +/* + * sbt + * Copyright 2011 - 2018, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under Apache License 2.0 (see LICENSE) + */ + +package sbt.internal + +import java.io.File +import java.net.URLClassLoader +import java.{ util => jutil } +import scala.collection.JavaConverters._ + +import sbt.internal.inc.classpath._ +import sbt.io.IO + +private[sbt] class LayeredClassLoader( + classpath: Seq[File], + parent: ClassLoader, + override protected val resources: Map[String, String], + tempDir: File, +) extends URLClassLoader(classpath.toArray.map(_.toURI.toURL), parent) + with RawResources + with NativeCopyLoader + with AutoCloseable { + private[this] val nativeLibs = new jutil.HashSet[File]().asScala + override protected val config = new NativeCopyConfig( + tempDir, + classpath, + IO.parseClasspath(System.getProperty("java.library.path", "")) + ) + override def findLibrary(name: String): String = { + super.findLibrary(name) match { + case null => null + case l => + nativeLibs += new File(l) + l + } + } + override def close(): Unit = nativeLibs.foreach(NativeLibs.delete) + override def toString: String = s"""LayeredClassLoader( + | classpath = + | ${classpath mkString "\n "} + | parent = + | ${parent.toString.linesIterator.mkString("\n ")} + |)""".stripMargin +} + +private[internal] object NativeLibs { + private[this] val nativeLibs = new jutil.HashSet[File].asScala + Runtime.getRuntime.addShutdownHook(new Thread("sbt.internal.native-library-deletion") { + override def run(): Unit = { + nativeLibs.foreach(IO.delete) + IO.deleteIfEmpty(nativeLibs.map(_.getParentFile).toSet) + nativeLibs.clear() + } + }) + def addNativeLib(lib: String): Unit = { + nativeLibs.add(new File(lib)) + () + } + def delete(file: File): Unit = { + nativeLibs.remove(file) + file.delete() + () + } +} diff --git a/main/src/main/scala/sbt/internal/PrettyPrint.scala b/main/src/main/scala/sbt/internal/PrettyPrint.scala new file mode 100644 index 000000000..04683313b --- /dev/null +++ b/main/src/main/scala/sbt/internal/PrettyPrint.scala @@ -0,0 +1,15 @@ +/* + * sbt + * Copyright 2011 - 2018, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under Apache License 2.0 (see LICENSE) + */ + +package sbt.internal + +private[sbt] object PrettyPrint { + private[sbt] def indent(any: Any, level: Int): String = { + val i = " " * level + any.toString.linesIterator.mkString(i, "\n" + i, "") + } +} diff --git a/main/src/main/scala/sbt/internal/Repository.scala b/main/src/main/scala/sbt/internal/Repository.scala new file mode 100644 index 000000000..63f9f230e --- /dev/null +++ b/main/src/main/scala/sbt/internal/Repository.scala @@ -0,0 +1,58 @@ +/* + * sbt + * Copyright 2011 - 2018, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under Apache License 2.0 (see LICENSE) + */ + +package sbt.internal + +import java.util.concurrent.ConcurrentHashMap + +import scala.collection.JavaConverters._ + +/** + * Represents an abstract cache of values, accessible by a key. The interface is deliberately + * minimal to give maximum flexibility to the implementation classes. For example, one can construct + * a cache from a `sbt.io.FileTreeRepository` that automatically registers the paths with the + * cache (but does not clear the cache on close): + * {{{ + * val repository = sbt.io.FileTreeRepository.default(_.getPath) + * val fileCache = new Repository[Seq, (Path, Boolean), TypedPath] { + * override def get(key: (Path, Boolean)): Seq[TypedPath] = { + * val (path, recursive) = key + * val depth = if (recursive) Int.MaxValue else 0 + * repository.register(path, depth) + * repository.list(path, depth, AllPass) + * } + * override def close(): Unit = {} + * } + * }}} + * + * @tparam M the container type of the cache. This will most commonly be `Option` or `Seq`. + * @tparam K the key type + * @tparam V the value type + */ +trait Repository[M[_], K, V] extends AutoCloseable { + def get(key: K): M[V] +} + +private[sbt] final class MutableRepository[K, V] extends Repository[Option, K, V] { + private[this] val map = new ConcurrentHashMap[K, V].asScala + override def get(key: K): Option[V] = map.get(key) + def put(key: K, value: V): Unit = this.synchronized { + map.put(key, value) + () + } + def remove(key: K): Unit = this.synchronized { + map.remove(key) + () + } + override def close(): Unit = this.synchronized { + map.foreach { + case (_, v: AutoCloseable) => v.close() + case _ => + } + map.clear() + } +} diff --git a/main/src/main/scala/sbt/internal/TaskRepository.scala b/main/src/main/scala/sbt/internal/TaskRepository.scala new file mode 100644 index 000000000..ada63172d --- /dev/null +++ b/main/src/main/scala/sbt/internal/TaskRepository.scala @@ -0,0 +1,38 @@ +/* + * sbt + * Copyright 2011 - 2018, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under Apache License 2.0 (see LICENSE) + */ + +package sbt.internal + +import sbt.Keys.state +import sbt._ + +private[sbt] object TaskRepository { + private[sbt] type Repr = MutableRepository[TaskKey[_], Any] + private[sbt] def proxy[T: Manifest](taskKey: TaskKey[T], task: => T): Def.Setting[Task[T]] = + proxy(taskKey, Def.task(task)) + private[sbt] def proxy[T: Manifest]( + taskKey: TaskKey[T], + task: Def.Initialize[Task[T]] + ): Def.Setting[Task[T]] = + taskKey := Def.taskDyn { + val taskRepository = state.value + .get(Keys.taskRepository) + .getOrElse { + val msg = "TaskRepository.proxy called before state was initialized" + throw new IllegalStateException(msg) + } + taskRepository.get(taskKey) match { + case Some(value: T) => Def.task(value) + case _ => + Def.task { + val value = task.value + taskRepository.put(taskKey, value) + value + } + } + }.value +} diff --git a/main/src/test/scala/sbt/internal/ClassLoaderCacheTest.scala b/main/src/test/scala/sbt/internal/ClassLoaderCacheTest.scala new file mode 100644 index 000000000..201964666 --- /dev/null +++ b/main/src/test/scala/sbt/internal/ClassLoaderCacheTest.scala @@ -0,0 +1,68 @@ +/* + * sbt + * Copyright 2011 - 2018, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under Apache License 2.0 (see LICENSE) + */ + +package sbt.internal + +import java.io.File +import java.nio.file.Files + +import org.scalatest.{ FlatSpec, Matchers } +import sbt.io.IO + +object ClassLoaderCacheTest { + private val initLoader = this.getClass.getClassLoader + implicit class CacheOps(val c: ClassLoaderCache) { + def get(classpath: Seq[File]): ClassLoader = + c.get((classpath, initLoader, Map.empty, new File("/dev/null"))) + } +} +class ClassLoaderCacheTest extends FlatSpec with Matchers { + import ClassLoaderCacheTest._ + def withCache[R](size: Int)(f: CacheOps => R): R = { + val cache = ClassLoaderCache(size) + try f(new CacheOps(cache)) + finally cache.close() + } + "ClassLoaderCache.get" should "make a new loader when full" in withCache(0) { cache => + val classPath = Seq.empty[File] + val firstLoader = cache.get(classPath) + val secondLoader = cache.get(classPath) + assert(firstLoader != secondLoader) + } + it should "not make a new loader when it already exists" in withCache(1) { cache => + val classPath = Seq.empty[File] + val firstLoader = cache.get(classPath) + val secondLoader = cache.get(classPath) + assert(firstLoader == secondLoader) + } + it should "evict loaders" in withCache(2) { cache => + val firstClassPath = Seq.empty[File] + val secondClassPath = new File("foo") :: Nil + val thirdClassPath = new File("foo") :: new File("bar") :: Nil + val firstLoader = cache.get(firstClassPath) + val secondLoader = cache.get(secondClassPath) + val thirdLoader = cache.get(thirdClassPath) + assert(cache.get(thirdClassPath) == thirdLoader) + assert(cache.get(secondClassPath) == secondLoader) + assert(cache.get(firstClassPath) != firstLoader) + assert(cache.get(thirdClassPath) != thirdLoader) + } + "Snapshots" should "be invalidated" in IO.withTemporaryDirectory { dir => + val snapshotJar = Files.createFile(dir.toPath.resolve("foo-SNAPSHOT.jar")).toFile + val regularJar = Files.createFile(dir.toPath.resolve("regular.jar")).toFile + withCache(1) { cache => + val jarClassPath = snapshotJar :: regularJar :: Nil + val initLoader = cache.get(jarClassPath) + IO.setModifiedTimeOrFalse(snapshotJar, System.currentTimeMillis + 5000L) + val secondLoader = cache.get(jarClassPath) + assert(initLoader != secondLoader) + assert(initLoader.getParent == secondLoader.getParent) + assert(cache.get(jarClassPath) == secondLoader) + assert(cache.get(jarClassPath) != initLoader) + } + } +} diff --git a/main/src/test/scala/sbt/internal/LRUCacheTest.scala b/main/src/test/scala/sbt/internal/LRUCacheTest.scala new file mode 100644 index 000000000..06add1271 --- /dev/null +++ b/main/src/test/scala/sbt/internal/LRUCacheTest.scala @@ -0,0 +1,78 @@ +/* + * sbt + * Copyright 2011 - 2018, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under Apache License 2.0 (see LICENSE) + */ + +package sbt +package internal + +import java.util.concurrent.atomic.AtomicInteger + +import org.scalatest.{ FlatSpec, Matchers } + +class LRUCacheTest extends FlatSpec with Matchers { + "LRUCache" should "flush entries when full" in { + val cache = LRUCache[Int, Int](2) + cache.put(1, 1) + cache.put(2, 2) + cache.put(3, 3) + assert(cache.get(1).isEmpty) + assert(cache.get(2).contains(2)) + assert(cache.get(3).contains(3)) + + assert(cache.get(2).contains(2)) + cache.put(1, 1) + assert(cache.get(3).isEmpty) + assert(cache.get(2).contains(2)) + assert(cache.get(1).contains(1)) + } + it should "remove entries" in { + val cache = LRUCache[Int, Int](2) + cache.put(1, 1) + cache.put(2, 2) + assert(cache.get(1).contains(1)) + assert(cache.get(2).contains(2)) + + assert(cache.remove(1).getOrElse(-1) == 1) + assert(cache.get(1).isEmpty) + assert(cache.get(2).contains(2)) + } + it should "clear entries on close" in { + val cache = LRUCache[Int, Int](2) + cache.put(1, 1) + assert(cache.get(1).contains(1)) + cache.close() + assert(cache.get(1).isEmpty) + } + it should "call onExpire in close" in { + val count = new AtomicInteger(0) + val cache = + LRUCache[Int, Int]( + maxSize = 3, + onExpire = (_: Int, _: Int) => { count.getAndIncrement(); () } + ) + cache.put(1, 1) + cache.put(2, 2) + cache.put(3, 3) + cache.put(4, 4) + assert(count.get == 1) + cache.close() + assert(count.get == 4) + } + it should "apply on remove function" in { + val value = new AtomicInteger(0) + val cache = LRUCache[Int, Int](1, (k: Int, v: Int) => value.set(k + v)) + cache.put(1, 3) + cache.put(2, 2) + assert(value.get() == 4) + assert(cache.get(2).contains(2)) + } + it should "print sorted entries in toString" in { + val cache = LRUCache[Int, Int](2) + cache.put(2, 2) + cache.put(1, 1) + assert(cache.toString == s"LRUCache(2 -> 2, 1 -> 1)") + } +} diff --git a/run/src/main/scala/sbt/Run.scala b/run/src/main/scala/sbt/Run.scala index a09d3fc3f..754974e1e 100644 --- a/run/src/main/scala/sbt/Run.scala +++ b/run/src/main/scala/sbt/Run.scala @@ -58,7 +58,9 @@ class ForkRun(config: ForkOptions) extends ScalaRun { private def classpathOption(classpath: Seq[File]) = "-classpath" :: Path.makeString(classpath) :: Nil } -class Run(instance: ScalaInstance, trapExit: Boolean, nativeTmp: File) extends ScalaRun { +class Run(newLoader: Seq[File] => ClassLoader, trapExit: Boolean) extends ScalaRun { + def this(instance: ScalaInstance, trapExit: Boolean, nativeTmp: File) = + this((cp: Seq[File]) => ClasspathUtilities.makeLoader(cp, instance, nativeTmp), trapExit) /** Runs the class 'mainClass' using the given classpath and options using the scala runner.*/ def run(mainClass: String, classpath: Seq[File], options: Seq[String], log: Logger): Try[Unit] = { @@ -87,7 +89,7 @@ class Run(instance: ScalaInstance, trapExit: Boolean, nativeTmp: File) extends S log: Logger ): Unit = { log.debug(" Classpath:\n\t" + classpath.mkString("\n\t")) - val loader = ClasspathUtilities.makeLoader(classpath, instance, nativeTmp) + val loader = newLoader(classpath) val main = getMainMethod(mainClassName, loader) invokeMain(loader, main, options) } diff --git a/sbt/src/sbt-test/classloader-cache/akka-actor-system/build.sbt b/sbt/src/sbt-test/classloader-cache/akka-actor-system/build.sbt new file mode 100644 index 000000000..9be9015dc --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/akka-actor-system/build.sbt @@ -0,0 +1,9 @@ +val akkaTest = (project in file(".")).settings( + name := "akka-test", + scalaVersion := "2.12.8", + libraryDependencies ++= Seq( + "com.typesafe.akka" %% "akka-actor" % "2.5.16", + "com.lihaoyi" %% "utest" % "0.6.6" % "test" + ), + testFrameworks := Seq(new TestFramework("utest.runner.Framework")) +) diff --git a/sbt/src/sbt-test/classloader-cache/akka-actor-system/src/main/scala/AkkaTest.scala b/sbt/src/sbt-test/classloader-cache/akka-actor-system/src/main/scala/AkkaTest.scala new file mode 100644 index 000000000..971b0341c --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/akka-actor-system/src/main/scala/AkkaTest.scala @@ -0,0 +1,15 @@ +package sbt.scripted + +import akka.actor.ActorSystem +import scala.concurrent.Await +import scala.concurrent.duration._ + +object AkkaTest { + def main(args: Array[String]): Unit = { + val now = System.nanoTime + val system = ActorSystem("akka") + Await.result(system.terminate(), 5.seconds) + val elapsed = System.nanoTime - now + println(s"Run took ${elapsed / 1.0e6} ms") + } +} diff --git a/sbt/src/sbt-test/classloader-cache/akka-actor-system/src/test/scala/AkkaPerfTest.scala b/sbt/src/sbt-test/classloader-cache/akka-actor-system/src/test/scala/AkkaPerfTest.scala new file mode 100644 index 000000000..9e5bbe5f6 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/akka-actor-system/src/test/scala/AkkaPerfTest.scala @@ -0,0 +1,12 @@ +package sbt.scripted + +import utest._ + +object AkkaPerfTest extends TestSuite { + val tests: Tests = Tests { + 'run - { + AkkaTest.main(Array.empty[String]) + 1 ==> 1 + } + } +} diff --git a/sbt/src/sbt-test/classloader-cache/akka-actor-system/test b/sbt/src/sbt-test/classloader-cache/akka-actor-system/test new file mode 100644 index 000000000..abcb2b542 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/akka-actor-system/test @@ -0,0 +1,7 @@ +> set Test / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.ShareRuntimeDependenciesLayerWithTestDependencies + +> run + +> test + +> test diff --git a/sbt/src/sbt-test/classloader-cache/jni/build.sbt b/sbt/src/sbt-test/classloader-cache/jni/build.sbt new file mode 100644 index 000000000..e12fc74f2 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/jni/build.sbt @@ -0,0 +1,38 @@ +import java.nio.file._ +import scala.collection.JavaConverters._ + +val copyTestResources = inputKey[Unit]("Copy the native libraries to the base directory") +val appendToLibraryPath = taskKey[Unit]("Append the base directory to the java.library.path system property") +val dropLibraryPath = taskKey[Unit]("Drop the last path from the java.library.path system property") +val wrappedRun = taskKey[Unit]("Run with modified java.library.path") +val wrappedTest = taskKey[Unit]("Test with modified java.library.path") + +def wrap(task: InputKey[Unit]): Def.Initialize[Task[Unit]] = + Def.sequential(appendToLibraryPath, task.toTask(""), dropLibraryPath) + +val root = (project in file(".")).settings( + scalaVersion := "2.12.8", + javacOptions ++= Seq("-source", "1.8", "-target", "1.8", "-h", + sourceDirectory.value.toPath.resolve("main/native/include").toString), + libraryDependencies += "com.lihaoyi" %% "utest" % "0.6.6" % "test", + testFrameworks := Seq(new TestFramework("utest.runner.Framework")), + copyTestResources := { + val key = Def.spaceDelimited().parsed.head + val base = baseDirectory.value.toPath + val resources = (baseDirectory.value / "src" / "main" / "resources" / key).toPath + Files.walk(resources).iterator.asScala.foreach { p => + Files.copy(p, base.resolve(p.getFileName), StandardCopyOption.REPLACE_EXISTING) + } + }, + appendToLibraryPath := { + val cp = System.getProperty("java.library.path", "").split(":") + val newCp = if (cp.contains(".")) cp else cp :+ "." + System.setProperty("java.library.path", newCp.mkString(":")) + }, + dropLibraryPath := { + val cp = System.getProperty("java.library.path", "").split(":").dropRight(1) + System.setProperty("java.library.path", cp.mkString(":")) + }, + wrappedRun := wrap(Runtime / run).value, + wrappedTest := wrap(Test / testOnly).value +) diff --git a/sbt/src/sbt-test/classloader-cache/jni/changes/JniLibraryTest.scala b/sbt/src/sbt-test/classloader-cache/jni/changes/JniLibraryTest.scala new file mode 100644 index 000000000..aa15d4aaa --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/jni/changes/JniLibraryTest.scala @@ -0,0 +1,15 @@ +package sbt + +import java.nio.file._ +import utest._ + +object JniLibraryTest extends TestSuite { + val tests = Tests { + 'load - { + 'native - { + System.loadLibrary("sbt-jni-library-test0") + new JniLibrary().getIntegerValue ==> 2 + } + } + } +} diff --git a/sbt/src/sbt-test/classloader-cache/jni/src/main/java/sbt/JniLibrary.java b/sbt/src/sbt-test/classloader-cache/jni/src/main/java/sbt/JniLibrary.java new file mode 100644 index 000000000..35ff90ec6 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/jni/src/main/java/sbt/JniLibrary.java @@ -0,0 +1,5 @@ +package sbt; + +final class JniLibrary { + public native int getIntegerValue(); +} diff --git a/sbt/src/sbt-test/classloader-cache/jni/src/main/native/1/Makefile b/sbt/src/sbt-test/classloader-cache/jni/src/main/native/1/Makefile new file mode 100644 index 000000000..f1fa7c538 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/jni/src/main/native/1/Makefile @@ -0,0 +1,61 @@ +TARGET_DIR := ../../../target +BUILD_DIR := $(TARGET_DIR)/build +NATIVE_DIR := native/$(shell uname -sm | cut -d ' ' -f2 | tr '[:upper:]' '[:lower:]') +LIB_DIR := ../resources/$(NATIVE_DIR) +LIB_NAME := sbt-jni-library-test0 +POSIX_LIB_NAME := lib$(LIB_NAME) +SOURCE := sbt_JniLibrary +CC := clang + +UNAME_S := $(shell uname -s) + +ifeq ($(UNAME_S),Darwin) + JNI_INCLUDE := -I$(shell mdfind -name jni.h | grep jdk1.8 | tail -n 1 | xargs dirname)\ + -I$(shell mdfind -name jni_md.h | grep jdk1.8 | tail -n 1 | xargs dirname) + LD := /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++ + OBJS := $(BUILD_DIR)/x86_64/darwin/$(SOURCE).o + LIBS := $(TARGET_DIR)/x86_64/$(POSIX_LIB_NAME).dylib +endif + +ifeq ($(UNAME_S), Linux) + BASE_INCLUDE := $(shell locate jni.h | tail -n 1 | xargs dirname) + JNI_INCLUDE := -I$(BASE_INCLUDE) -I$(BASE_INCLUDE)/linux + OBJS := $(BUILD_DIR)/x86_64/linux/$(SOURCE).o + LIBS := $(TARGET_DIR)/x86_64/$(POSIX_LIB_NAME).so +endif + +LINUX_CCFLAGS := -fPIC +LINUX_LDFLAGS := -fPIC -shared + +CCFLAGS := -I./../include $(JNI_INCLUDE) -Wno-unused-command-line-argument -std=c++11 -O3 + +all: $(LIBS) + +.PHONY: clean all + +$(BUILD_DIR)/x86_64/linux/$(SOURCE).o: $(SOURCE).cc + mkdir -p $(BUILD_DIR)/x86_64/linux; \ + $(CC) -c $< $(CCFLAGS) $(JNI_INCLUDE) -fPIC -o $@ + +$(BUILD_DIR)/x86_64/darwin/$(SOURCE).o: $(SOURCE).cc + mkdir -p $(BUILD_DIR)/x86_64/darwin; \ + $(CC) -c $< $(CCFLAGS) -framework Carbon -o $@ + +$(TARGET_DIR)/x86_64/$(POSIX_LIB_NAME).dylib: $(BUILD_DIR)/x86_64/darwin/$(SOURCE).o + mkdir -p $(TARGET_DIR)/x86_64; \ + $(LD) -dynamiclib -framework Carbon $(CCFLAGS) -Wl,-headerpad_max_install_names -install_name @rpath/$(POSIX_LIB_NAME) \ + $(BUILD_DIR)/x86_64/darwin/$(SOURCE).o \ + -o $@ ; \ + mkdir -p ../../resources/1; \ + cp $(TARGET_DIR)/x86_64/$(POSIX_LIB_NAME).dylib ../../resources/1 + + + +$(TARGET_DIR)/x86_64/$(POSIX_LIB_NAME).so: $(BUILD_DIR)/x86_64/linux/$(SOURCE).o + mkdir -p $(TARGET_DIR)/x86_64; \ + $(CC) -shared $< $(CCFLAGS) -Wl,-headerpad_max_install_names -o $@; \ + mkdir -p ../../resources/1; \ + cp $(TARGET_DIR)/x86_64/$(POSIX_LIB_NAME).so ../../resources/1; + +clean: + rm -rf $(TARGET_DIR)/build $(TARGET_DIR)/$(NATIVE) diff --git a/sbt/src/sbt-test/classloader-cache/jni/src/main/native/1/sbt_JniLibrary.cc b/sbt/src/sbt-test/classloader-cache/jni/src/main/native/1/sbt_JniLibrary.cc new file mode 100644 index 000000000..46d048f60 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/jni/src/main/native/1/sbt_JniLibrary.cc @@ -0,0 +1,15 @@ +#include +#include "sbt_JniLibrary.h" + +extern "C" { + +/* + * Class: sbt_JniLibrary + * Method: getIntegerValue + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_sbt_JniLibrary_getIntegerValue + (JNIEnv *env, jobject obj) { + return 1; + } +} diff --git a/sbt/src/sbt-test/classloader-cache/jni/src/main/native/2/Makefile b/sbt/src/sbt-test/classloader-cache/jni/src/main/native/2/Makefile new file mode 100644 index 000000000..d0634d916 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/jni/src/main/native/2/Makefile @@ -0,0 +1,61 @@ +TARGET_DIR := ../../../target +BUILD_DIR := $(TARGET_DIR)/build +NATIVE_DIR := native/$(shell uname -sm | cut -d ' ' -f2 | tr '[:upper:]' '[:lower:]') +LIB_DIR := ../resources/$(NATIVE_DIR) +LIB_NAME := sbt-jni-library-test0 +POSIX_LIB_NAME := lib$(LIB_NAME) +SOURCE := sbt_JniLibrary +CC := clang + +UNAME_S := $(shell uname -s) + +ifeq ($(UNAME_S),Darwin) + JNI_INCLUDE := -I$(shell mdfind -name jni.h | grep jdk1.8 | tail -n 1 | xargs dirname)\ + -I$(shell mdfind -name jni_md.h | grep jdk1.8 | tail -n 1 | xargs dirname) + LD := /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++ + OBJS := $(BUILD_DIR)/x86_64/darwin/$(SOURCE).o + LIBS := $(TARGET_DIR)/x86_64/$(POSIX_LIB_NAME).dylib +endif + +ifeq ($(UNAME_S), Linux) + BASE_INCLUDE := $(shell locate jni.h | tail -n 1 | xargs dirname) + JNI_INCLUDE := -I$(BASE_INCLUDE) -I$(BASE_INCLUDE)/linux + OBJS := $(BUILD_DIR)/x86_64/linux/$(SOURCE).o + LIBS := $(TARGET_DIR)/x86_64/$(POSIX_LIB_NAME).so +endif + +LINUX_CCFLAGS := -fPIC +LINUX_LDFLAGS := -fPIC -shared + +CCFLAGS := -I./../include $(JNI_INCLUDE) -Wno-unused-command-line-argument -std=c++11 -O3 + +all: $(LIBS) + +.PHONY: clean all + +$(BUILD_DIR)/x86_64/linux/$(SOURCE).o: $(SOURCE).cc + mkdir -p $(BUILD_DIR)/x86_64/linux; \ + $(CC) -c $< $(CCFLAGS) $(JNI_INCLUDE) -fPIC -o $@ + +$(BUILD_DIR)/x86_64/darwin/$(SOURCE).o: $(SOURCE).cc + mkdir -p $(BUILD_DIR)/x86_64/darwin; \ + $(CC) -c $< $(CCFLAGS) -framework Carbon -o $@ + +$(TARGET_DIR)/x86_64/$(POSIX_LIB_NAME).dylib: $(BUILD_DIR)/x86_64/darwin/$(SOURCE).o + mkdir -p $(TARGET_DIR)/x86_64; \ + $(LD) -dynamiclib -framework Carbon $(CCFLAGS) -Wl,-headerpad_max_install_names -install_name @rpath/$(POSIX_LIB_NAME) \ + $(BUILD_DIR)/x86_64/darwin/$(SOURCE).o \ + -o $@ ; \ + mkdir -p ../../resources/2; \ + cp $(TARGET_DIR)/x86_64/$(POSIX_LIB_NAME).dylib ../../resources/2 + + + +$(TARGET_DIR)/x86_64/$(POSIX_LIB_NAME).so: $(BUILD_DIR)/x86_64/linux/$(SOURCE).o + mkdir -p $(TARGET_DIR)/x86_64; \ + $(CC) -shared $< $(CCFLAGS) -Wl,-headerpad_max_install_names -o $@; \ + mkdir -p ../../resources/2; \ + cp $(TARGET_DIR)/x86_64/$(POSIX_LIB_NAME).so ../../resources/2; + +clean: + rm -rf $(TARGET_DIR)/build $(TARGET_DIR)/$(NATIVE) diff --git a/sbt/src/sbt-test/classloader-cache/jni/src/main/native/2/sbt_JniLibrary.cc b/sbt/src/sbt-test/classloader-cache/jni/src/main/native/2/sbt_JniLibrary.cc new file mode 100644 index 000000000..eeb97bfa2 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/jni/src/main/native/2/sbt_JniLibrary.cc @@ -0,0 +1,15 @@ +#include +#include "sbt_JniLibrary.h" + +extern "C" { + +/* + * Class: sbt_JniLibrary + * Method: getIntegerValue + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_sbt_JniLibrary_getIntegerValue + (JNIEnv *env, jobject obj) { + return 2; + } +} diff --git a/sbt/src/sbt-test/classloader-cache/jni/src/main/native/include/sbt_JniLibrary.h b/sbt/src/sbt-test/classloader-cache/jni/src/main/native/include/sbt_JniLibrary.h new file mode 100644 index 000000000..ccdb7bd80 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/jni/src/main/native/include/sbt_JniLibrary.h @@ -0,0 +1,21 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class sbt_JniLibrary */ + +#ifndef _Included_sbt_JniLibrary +#define _Included_sbt_JniLibrary +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: sbt_JniLibrary + * Method: getIntegerValue + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_sbt_JniLibrary_getIntegerValue + (JNIEnv *, jobject); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/sbt/src/sbt-test/classloader-cache/jni/src/main/resources/1/libsbt-jni-library-test0.dylib b/sbt/src/sbt-test/classloader-cache/jni/src/main/resources/1/libsbt-jni-library-test0.dylib new file mode 100755 index 000000000..660445b66 Binary files /dev/null and b/sbt/src/sbt-test/classloader-cache/jni/src/main/resources/1/libsbt-jni-library-test0.dylib differ diff --git a/sbt/src/sbt-test/classloader-cache/jni/src/main/resources/1/libsbt-jni-library-test0.so b/sbt/src/sbt-test/classloader-cache/jni/src/main/resources/1/libsbt-jni-library-test0.so new file mode 100755 index 000000000..6cac0d6fb Binary files /dev/null and b/sbt/src/sbt-test/classloader-cache/jni/src/main/resources/1/libsbt-jni-library-test0.so differ diff --git a/sbt/src/sbt-test/classloader-cache/jni/src/main/resources/2/libsbt-jni-library-test0.dylib b/sbt/src/sbt-test/classloader-cache/jni/src/main/resources/2/libsbt-jni-library-test0.dylib new file mode 100755 index 000000000..ed8916036 Binary files /dev/null and b/sbt/src/sbt-test/classloader-cache/jni/src/main/resources/2/libsbt-jni-library-test0.dylib differ diff --git a/sbt/src/sbt-test/classloader-cache/jni/src/main/resources/2/libsbt-jni-library-test0.so b/sbt/src/sbt-test/classloader-cache/jni/src/main/resources/2/libsbt-jni-library-test0.so new file mode 100755 index 000000000..ce87cb48d Binary files /dev/null and b/sbt/src/sbt-test/classloader-cache/jni/src/main/resources/2/libsbt-jni-library-test0.so differ diff --git a/sbt/src/sbt-test/classloader-cache/jni/src/main/resources/native/x86_64/libswoval-jni-library-test0.dylib b/sbt/src/sbt-test/classloader-cache/jni/src/main/resources/native/x86_64/libswoval-jni-library-test0.dylib new file mode 100755 index 000000000..472d59c2b Binary files /dev/null and b/sbt/src/sbt-test/classloader-cache/jni/src/main/resources/native/x86_64/libswoval-jni-library-test0.dylib differ diff --git a/sbt/src/sbt-test/classloader-cache/jni/src/main/scala/TestMain.scala b/sbt/src/sbt-test/classloader-cache/jni/src/main/scala/TestMain.scala new file mode 100644 index 000000000..523c37799 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/jni/src/main/scala/TestMain.scala @@ -0,0 +1,12 @@ +package sbt + +import java.nio.file._ +import scala.collection.JavaConverters._ + +object TestMain { + def main(args: Array[String]): Unit = { + val libraryPath = System.getProperty("java.library.path") + System.loadLibrary("sbt-jni-library-test0") + println(s"Native value is ${new JniLibrary().getIntegerValue}") + } +} \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/jni/src/test/scala/sbt/JniLibraryTest.scala b/sbt/src/sbt-test/classloader-cache/jni/src/test/scala/sbt/JniLibraryTest.scala new file mode 100644 index 000000000..c4b20e199 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/jni/src/test/scala/sbt/JniLibraryTest.scala @@ -0,0 +1,15 @@ +package sbt + +import java.nio.file._ +import utest._ + +object JniLibraryTest extends TestSuite { + val tests = Tests { + 'load - { + 'native - { + System.loadLibrary("sbt-jni-library-test0") + new JniLibrary().getIntegerValue ==> 1 + } + } + } +} diff --git a/sbt/src/sbt-test/classloader-cache/jni/test b/sbt/src/sbt-test/classloader-cache/jni/test new file mode 100644 index 000000000..7375b6eba --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/jni/test @@ -0,0 +1,6 @@ +> copyTestResources 1 +> wrappedRun +> wrappedTest +> copyTestResources 2 +$ copy-file changes/JniLibraryTest.scala src/test/scala/sbt/JniLibraryTest.scala +> wrappedTest diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/build.sbt b/sbt/src/sbt-test/classloader-cache/library-mismatch/build.sbt new file mode 100644 index 000000000..fefcd86a2 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/library-mismatch/build.sbt @@ -0,0 +1,9 @@ +val snapshot = (project in file(".")).settings( + name := "mismatched-libraries", + scalaVersion := "2.12.8", + libraryDependencies ++= Seq("com.lihaoyi" %% "utest" % "0.6.6" % "test"), + testFrameworks := Seq(TestFramework("utest.runner.Framework")), + resolvers += "Local Maven" at file("libraries/ivy").toURI.toURL.toString, + libraryDependencies += "sbt" % "transitive-lib" % "0.1.0", + libraryDependencies += "sbt" % "foo-lib" % "0.2.0" % "test", +) diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/foo-lib-0.1.0/build.sbt b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/foo-lib-0.1.0/build.sbt new file mode 100644 index 000000000..a450e3bb9 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/foo-lib-0.1.0/build.sbt @@ -0,0 +1,11 @@ +name := "foo-lib" + +organization := "sbt" + +publishTo := Some(Resolver.file("test-resolver", file("..").getCanonicalFile / "ivy")) + +version := "0.1.0" + +crossPaths := false + +autoScalaLibrary := false diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/foo-lib-0.1.0/project/build.properties b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/foo-lib-0.1.0/project/build.properties new file mode 100644 index 000000000..7c58a83ab --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/foo-lib-0.1.0/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.2.6 diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/foo-lib-0.1.0/src/main/java/sbt/foo/Foo.java b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/foo-lib-0.1.0/src/main/java/sbt/foo/Foo.java new file mode 100644 index 000000000..4f89c900b --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/foo-lib-0.1.0/src/main/java/sbt/foo/Foo.java @@ -0,0 +1,7 @@ +package sbt.foo; + +public class Foo { + static public int x() { + return 1; + } +} diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/foo-lib-0.2.0/build.sbt b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/foo-lib-0.2.0/build.sbt new file mode 100644 index 000000000..b399a06f5 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/foo-lib-0.2.0/build.sbt @@ -0,0 +1,11 @@ +name := "foo-lib" + +organization := "sbt" + +publishTo := Some(Resolver.file("test-resolver", file("..").getCanonicalFile / "ivy")) + +version := "0.2.0" + +crossPaths := false + +autoScalaLibrary := false diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/foo-lib-0.2.0/project/build.properties b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/foo-lib-0.2.0/project/build.properties new file mode 100644 index 000000000..7c58a83ab --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/foo-lib-0.2.0/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.2.6 diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/foo-lib-0.2.0/src/main/java/sbt/foo/Foo.java b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/foo-lib-0.2.0/src/main/java/sbt/foo/Foo.java new file mode 100644 index 000000000..66595cf6f --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/foo-lib-0.2.0/src/main/java/sbt/foo/Foo.java @@ -0,0 +1,10 @@ +package sbt.foo; + +public class Foo { + static public int x() { + return 2; + } + static public int y() { + return 3; + } +} diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.1.0/foo-lib-0.1.0-javadoc.jar b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.1.0/foo-lib-0.1.0-javadoc.jar new file mode 100644 index 000000000..13319bbfb Binary files /dev/null and b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.1.0/foo-lib-0.1.0-javadoc.jar differ diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.1.0/foo-lib-0.1.0-javadoc.jar.md5 b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.1.0/foo-lib-0.1.0-javadoc.jar.md5 new file mode 100644 index 000000000..e8c91778b --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.1.0/foo-lib-0.1.0-javadoc.jar.md5 @@ -0,0 +1 @@ +66124717bb8840ce6f60567b19634a4b \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.1.0/foo-lib-0.1.0-javadoc.jar.sha1 b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.1.0/foo-lib-0.1.0-javadoc.jar.sha1 new file mode 100644 index 000000000..7df850585 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.1.0/foo-lib-0.1.0-javadoc.jar.sha1 @@ -0,0 +1 @@ +ca2193d86495f120496370cf2e75355f2b10b2a3 \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.1.0/foo-lib-0.1.0-sources.jar b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.1.0/foo-lib-0.1.0-sources.jar new file mode 100644 index 000000000..d675c2cb2 Binary files /dev/null and b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.1.0/foo-lib-0.1.0-sources.jar differ diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.1.0/foo-lib-0.1.0-sources.jar.md5 b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.1.0/foo-lib-0.1.0-sources.jar.md5 new file mode 100644 index 000000000..e16a6727e --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.1.0/foo-lib-0.1.0-sources.jar.md5 @@ -0,0 +1 @@ +8bb4c394490df92c43361e47d98cba52 \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.1.0/foo-lib-0.1.0-sources.jar.sha1 b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.1.0/foo-lib-0.1.0-sources.jar.sha1 new file mode 100644 index 000000000..8eb308f6d --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.1.0/foo-lib-0.1.0-sources.jar.sha1 @@ -0,0 +1 @@ +86ce6f4d12e075e8ef4ba80b671783673a8a0443 \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.1.0/foo-lib-0.1.0.jar b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.1.0/foo-lib-0.1.0.jar new file mode 100644 index 000000000..5eb0d2b98 Binary files /dev/null and b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.1.0/foo-lib-0.1.0.jar differ diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.1.0/foo-lib-0.1.0.jar.md5 b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.1.0/foo-lib-0.1.0.jar.md5 new file mode 100644 index 000000000..4119cd027 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.1.0/foo-lib-0.1.0.jar.md5 @@ -0,0 +1 @@ +4cd195abad8639ba6df345c125068058 \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.1.0/foo-lib-0.1.0.jar.sha1 b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.1.0/foo-lib-0.1.0.jar.sha1 new file mode 100644 index 000000000..3a4ae59c3 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.1.0/foo-lib-0.1.0.jar.sha1 @@ -0,0 +1 @@ +e18f4c7a4b52d288733f123008d98985610da4f3 \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.1.0/foo-lib-0.1.0.pom b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.1.0/foo-lib-0.1.0.pom new file mode 100644 index 000000000..fc90d29ec --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.1.0/foo-lib-0.1.0.pom @@ -0,0 +1,13 @@ + + + 4.0.0 + sbt + foo-lib + jar + foo-lib + 0.1.0 + foo-lib + + sbt + + \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.1.0/foo-lib-0.1.0.pom.md5 b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.1.0/foo-lib-0.1.0.pom.md5 new file mode 100644 index 000000000..2cb48799b --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.1.0/foo-lib-0.1.0.pom.md5 @@ -0,0 +1 @@ +22ebca3d8b32a4c0cb40341501b798e2 \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.1.0/foo-lib-0.1.0.pom.sha1 b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.1.0/foo-lib-0.1.0.pom.sha1 new file mode 100644 index 000000000..4e5efcb01 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.1.0/foo-lib-0.1.0.pom.sha1 @@ -0,0 +1 @@ +575258d8c9fecff7e8beefee2637c2928328e0dc \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.2.0/foo-lib-0.2.0-javadoc.jar b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.2.0/foo-lib-0.2.0-javadoc.jar new file mode 100644 index 000000000..1037d4546 Binary files /dev/null and b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.2.0/foo-lib-0.2.0-javadoc.jar differ diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.2.0/foo-lib-0.2.0-javadoc.jar.md5 b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.2.0/foo-lib-0.2.0-javadoc.jar.md5 new file mode 100644 index 000000000..6c94e7950 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.2.0/foo-lib-0.2.0-javadoc.jar.md5 @@ -0,0 +1 @@ +ce4dcb6be9c11c1356e6aeaa024c80f1 \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.2.0/foo-lib-0.2.0-javadoc.jar.sha1 b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.2.0/foo-lib-0.2.0-javadoc.jar.sha1 new file mode 100644 index 000000000..17453e3b8 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.2.0/foo-lib-0.2.0-javadoc.jar.sha1 @@ -0,0 +1 @@ +cc1a6b548cb9efd6e43c7fb5fa44728fc3f50d45 \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.2.0/foo-lib-0.2.0-sources.jar b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.2.0/foo-lib-0.2.0-sources.jar new file mode 100644 index 000000000..99410b913 Binary files /dev/null and b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.2.0/foo-lib-0.2.0-sources.jar differ diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.2.0/foo-lib-0.2.0-sources.jar.md5 b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.2.0/foo-lib-0.2.0-sources.jar.md5 new file mode 100644 index 000000000..ddafa9ebb --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.2.0/foo-lib-0.2.0-sources.jar.md5 @@ -0,0 +1 @@ +f18c726e5009cf205ca83cf96a28345b \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.2.0/foo-lib-0.2.0-sources.jar.sha1 b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.2.0/foo-lib-0.2.0-sources.jar.sha1 new file mode 100644 index 000000000..4dd6ee7f1 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.2.0/foo-lib-0.2.0-sources.jar.sha1 @@ -0,0 +1 @@ +af0ac149e4c810884bb921bb3826e495faa5854b \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.2.0/foo-lib-0.2.0.jar b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.2.0/foo-lib-0.2.0.jar new file mode 100644 index 000000000..948dd983f Binary files /dev/null and b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.2.0/foo-lib-0.2.0.jar differ diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.2.0/foo-lib-0.2.0.jar.md5 b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.2.0/foo-lib-0.2.0.jar.md5 new file mode 100644 index 000000000..cf6b26db1 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.2.0/foo-lib-0.2.0.jar.md5 @@ -0,0 +1 @@ +fcadc4b2d6965cec0f5e5efb0a59226d \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.2.0/foo-lib-0.2.0.jar.sha1 b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.2.0/foo-lib-0.2.0.jar.sha1 new file mode 100644 index 000000000..086f63d7f --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.2.0/foo-lib-0.2.0.jar.sha1 @@ -0,0 +1 @@ +585422ad5196b83f83bbca644e62a39a2e4171e0 \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.2.0/foo-lib-0.2.0.pom b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.2.0/foo-lib-0.2.0.pom new file mode 100644 index 000000000..4ab3397ba --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.2.0/foo-lib-0.2.0.pom @@ -0,0 +1,13 @@ + + + 4.0.0 + sbt + foo-lib + jar + foo-lib + 0.2.0 + foo-lib + + sbt + + \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.2.0/foo-lib-0.2.0.pom.md5 b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.2.0/foo-lib-0.2.0.pom.md5 new file mode 100644 index 000000000..087635a3c --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.2.0/foo-lib-0.2.0.pom.md5 @@ -0,0 +1 @@ +790acd1d77316ff2c0310bb88d01dc72 \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.2.0/foo-lib-0.2.0.pom.sha1 b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.2.0/foo-lib-0.2.0.pom.sha1 new file mode 100644 index 000000000..0c74509e8 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/foo-lib/0.2.0/foo-lib-0.2.0.pom.sha1 @@ -0,0 +1 @@ +94eec0571d936004eb5682b8b12268d1bcb2be40 \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/transitive-lib/0.1.0/transitive-lib-0.1.0-javadoc.jar b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/transitive-lib/0.1.0/transitive-lib-0.1.0-javadoc.jar new file mode 100644 index 000000000..39f0eea9a Binary files /dev/null and b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/transitive-lib/0.1.0/transitive-lib-0.1.0-javadoc.jar differ diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/transitive-lib/0.1.0/transitive-lib-0.1.0-javadoc.jar.md5 b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/transitive-lib/0.1.0/transitive-lib-0.1.0-javadoc.jar.md5 new file mode 100644 index 000000000..ce5c94c76 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/transitive-lib/0.1.0/transitive-lib-0.1.0-javadoc.jar.md5 @@ -0,0 +1 @@ +31f61c992ede7185d70d7c8a9ed19340 \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/transitive-lib/0.1.0/transitive-lib-0.1.0-javadoc.jar.sha1 b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/transitive-lib/0.1.0/transitive-lib-0.1.0-javadoc.jar.sha1 new file mode 100644 index 000000000..c0b7f1add --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/transitive-lib/0.1.0/transitive-lib-0.1.0-javadoc.jar.sha1 @@ -0,0 +1 @@ +3083e0439d988b1cce30be566f8c95448c310eca \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/transitive-lib/0.1.0/transitive-lib-0.1.0-sources.jar b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/transitive-lib/0.1.0/transitive-lib-0.1.0-sources.jar new file mode 100644 index 000000000..3cc549138 Binary files /dev/null and b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/transitive-lib/0.1.0/transitive-lib-0.1.0-sources.jar differ diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/transitive-lib/0.1.0/transitive-lib-0.1.0-sources.jar.md5 b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/transitive-lib/0.1.0/transitive-lib-0.1.0-sources.jar.md5 new file mode 100644 index 000000000..9669bc52f --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/transitive-lib/0.1.0/transitive-lib-0.1.0-sources.jar.md5 @@ -0,0 +1 @@ +b9e2ead915294bf9e79622fe4ad9eadb \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/transitive-lib/0.1.0/transitive-lib-0.1.0-sources.jar.sha1 b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/transitive-lib/0.1.0/transitive-lib-0.1.0-sources.jar.sha1 new file mode 100644 index 000000000..981beaca9 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/transitive-lib/0.1.0/transitive-lib-0.1.0-sources.jar.sha1 @@ -0,0 +1 @@ +5454c981af36713e2f0cedce98a83da2b0e4b982 \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/transitive-lib/0.1.0/transitive-lib-0.1.0.jar b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/transitive-lib/0.1.0/transitive-lib-0.1.0.jar new file mode 100644 index 000000000..45223c30c Binary files /dev/null and b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/transitive-lib/0.1.0/transitive-lib-0.1.0.jar differ diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/transitive-lib/0.1.0/transitive-lib-0.1.0.jar.md5 b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/transitive-lib/0.1.0/transitive-lib-0.1.0.jar.md5 new file mode 100644 index 000000000..19d0fe484 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/transitive-lib/0.1.0/transitive-lib-0.1.0.jar.md5 @@ -0,0 +1 @@ +c7b4fbba100656a2798c39ca1d88317f \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/transitive-lib/0.1.0/transitive-lib-0.1.0.jar.sha1 b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/transitive-lib/0.1.0/transitive-lib-0.1.0.jar.sha1 new file mode 100644 index 000000000..84f5adbb1 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/transitive-lib/0.1.0/transitive-lib-0.1.0.jar.sha1 @@ -0,0 +1 @@ +f0fcc50b65e83f42210bbd3f366e2cc439177aa8 \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/transitive-lib/0.1.0/transitive-lib-0.1.0.pom b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/transitive-lib/0.1.0/transitive-lib-0.1.0.pom new file mode 100644 index 000000000..a3306d5b1 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/transitive-lib/0.1.0/transitive-lib-0.1.0.pom @@ -0,0 +1,20 @@ + + + 4.0.0 + sbt + transitive-lib + jar + transitive-lib + 0.1.0 + transitive-lib + + sbt + + + + sbt + foo-lib + 0.1.0 + + + \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/transitive-lib/0.1.0/transitive-lib-0.1.0.pom.md5 b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/transitive-lib/0.1.0/transitive-lib-0.1.0.pom.md5 new file mode 100644 index 000000000..b370e9b87 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/transitive-lib/0.1.0/transitive-lib-0.1.0.pom.md5 @@ -0,0 +1 @@ +460393fd256f121413f943d085887278 \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/transitive-lib/0.1.0/transitive-lib-0.1.0.pom.sha1 b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/transitive-lib/0.1.0/transitive-lib-0.1.0.pom.sha1 new file mode 100644 index 000000000..9956e4112 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/ivy/sbt/transitive-lib/0.1.0/transitive-lib-0.1.0.pom.sha1 @@ -0,0 +1 @@ +4cb84c1daf7152544065849db0c1ce2d8d47c334 \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/transitive-lib-0.1.0/build.sbt b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/transitive-lib-0.1.0/build.sbt new file mode 100644 index 000000000..5bb0f4ae8 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/transitive-lib-0.1.0/build.sbt @@ -0,0 +1,15 @@ +name := "transitive-lib" + +organization := "sbt" + +resolvers += "Local Maven" at file("../ivy").getCanonicalFile.toURI.toURL.toString + +publishTo := Some(Resolver.file("test-resolver", file("..").getCanonicalFile / "ivy")) + +version := "0.1.0" + +libraryDependencies += "sbt" % "foo-lib" % "0.1.0" + +crossPaths := false + +autoScalaLibrary := false diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/transitive-lib-0.1.0/project/build.properties b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/transitive-lib-0.1.0/project/build.properties new file mode 100644 index 000000000..7c58a83ab --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/transitive-lib-0.1.0/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.2.6 diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/transitive-lib-0.1.0/src/main/java/sbt/transitive/Transitive.java b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/transitive-lib-0.1.0/src/main/java/sbt/transitive/Transitive.java new file mode 100644 index 000000000..bdc984690 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/library-mismatch/libraries/transitive-lib-0.1.0/src/main/java/sbt/transitive/Transitive.java @@ -0,0 +1,7 @@ +package sbt.transitive; + +public class Transitive { + public static int x() { + return sbt.foo.Foo.x(); + } +} diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/src/main/scala/Main.scala b/sbt/src/sbt-test/classloader-cache/library-mismatch/src/main/scala/Main.scala new file mode 100644 index 000000000..7a4455526 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/library-mismatch/src/main/scala/Main.scala @@ -0,0 +1,7 @@ +package sbt + +object TestMain { + def main(args: Array[String]) { + println(transitive.Transitive.x) + } +} diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/src/test/scala/sbt/MismatchedLibrariesTest.scala b/sbt/src/sbt-test/classloader-cache/library-mismatch/src/test/scala/sbt/MismatchedLibrariesTest.scala new file mode 100644 index 000000000..9bc5a9a86 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/library-mismatch/src/test/scala/sbt/MismatchedLibrariesTest.scala @@ -0,0 +1,11 @@ +package sbt + +import utest._ + +object MismatchedLibrariesTest extends TestSuite { + val tests: Tests = Tests { + 'check - { + assert(foo.Foo.y == 3) + } + } +} diff --git a/sbt/src/sbt-test/classloader-cache/library-mismatch/test b/sbt/src/sbt-test/classloader-cache/library-mismatch/test new file mode 100644 index 000000000..abdaa10fa --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/library-mismatch/test @@ -0,0 +1,15 @@ +> set Test / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.ShareRuntimeDependenciesLayerWithTestDependencies + +> run + +# This fails because the runtime layer includes an old version of the foo-lib library that doesn't +# have the sbt.foo.Foo.y method defined. +> test + +> set Test / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.TestDependencies + +> run + +> test + +> test diff --git a/sbt/src/sbt-test/classloader-cache/runtime-layers/build.sbt b/sbt/src/sbt-test/classloader-cache/runtime-layers/build.sbt new file mode 100644 index 000000000..36e333515 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/runtime-layers/build.sbt @@ -0,0 +1,6 @@ +val layeringStrategyTest = (project in file(".")).settings( + name := "layering-strategy-test", + scalaVersion := "2.12.8", + organization := "sbt", + libraryDependencies += "com.typesafe.akka" %% "akka-actor" % "2.5.16", +) diff --git a/sbt/src/sbt-test/classloader-cache/runtime-layers/src/main/scala/AkkaTest.scala b/sbt/src/sbt-test/classloader-cache/runtime-layers/src/main/scala/AkkaTest.scala new file mode 100644 index 000000000..971b0341c --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/runtime-layers/src/main/scala/AkkaTest.scala @@ -0,0 +1,15 @@ +package sbt.scripted + +import akka.actor.ActorSystem +import scala.concurrent.Await +import scala.concurrent.duration._ + +object AkkaTest { + def main(args: Array[String]): Unit = { + val now = System.nanoTime + val system = ActorSystem("akka") + Await.result(system.terminate(), 5.seconds) + val elapsed = System.nanoTime - now + println(s"Run took ${elapsed / 1.0e6} ms") + } +} diff --git a/sbt/src/sbt-test/classloader-cache/runtime-layers/src/test/scala/TestAkkaTest.scala b/sbt/src/sbt-test/classloader-cache/runtime-layers/src/test/scala/TestAkkaTest.scala new file mode 100644 index 000000000..e17c9962f --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/runtime-layers/src/test/scala/TestAkkaTest.scala @@ -0,0 +1,15 @@ +package sbt.scripted + +import akka.actor.ActorSystem +import scala.concurrent.Await +import scala.concurrent.duration._ + +object TestAkkaTest { + def main(args: Array[String]): Unit = { + val now = System.nanoTime + val system = ActorSystem("akka") + Await.result(system.terminate(), 5.seconds) + val elapsed = System.nanoTime - now + println(s"Test run took ${elapsed / 1.0e6} ms") + } +} diff --git a/sbt/src/sbt-test/classloader-cache/runtime-layers/test b/sbt/src/sbt-test/classloader-cache/runtime-layers/test new file mode 100644 index 000000000..6a598d358 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/runtime-layers/test @@ -0,0 +1,37 @@ +> run + +> set Compile / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.Flat + +> run + +> set Compile / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.ScalaInstance + +> run + +> set Compile / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.RuntimeDependencies + +> run + +> set Compile / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.TestDependencies + +-> run + +> set Test / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.Flat + +> Test / runMain sbt.scripted.TestAkkaTest + +> set Test / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.ScalaInstance + +> Test / runMain sbt.scripted.TestAkkaTest + +> set Test / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.RuntimeDependencies + +> Test / runMain sbt.scripted.TestAkkaTest + +> set Test / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.ShareRuntimeDependenciesLayerWithTestDependencies + +> Test / runMain sbt.scripted.TestAkkaTest + +> set Test / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.TestDependencies + +> Test / runMain sbt.scripted.TestAkkaTest diff --git a/sbt/src/sbt-test/classloader-cache/scalatest/build.sbt b/sbt/src/sbt-test/classloader-cache/scalatest/build.sbt new file mode 100644 index 000000000..9f62ac7ad --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/scalatest/build.sbt @@ -0,0 +1,3 @@ +val test = (project in file(".")).settings( + libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.4" % "test" +) \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/scalatest/changes/ScalatestTest.scala b/sbt/src/sbt-test/classloader-cache/scalatest/changes/ScalatestTest.scala new file mode 100644 index 000000000..508806fe4 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/scalatest/changes/ScalatestTest.scala @@ -0,0 +1,9 @@ +package sbt + +import org.scalatest.{ FlatSpec, Matchers } + +class ScalatestTest extends FlatSpec with Matchers { + "scalatest" should "fail" in { + 1 shouldBe 2 + } +} diff --git a/sbt/src/sbt-test/classloader-cache/scalatest/src/test/scala/sbt/ScalatestTest.scala b/sbt/src/sbt-test/classloader-cache/scalatest/src/test/scala/sbt/ScalatestTest.scala new file mode 100644 index 000000000..afdcfe9e4 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/scalatest/src/test/scala/sbt/ScalatestTest.scala @@ -0,0 +1,9 @@ +package sbt + +import org.scalatest.{ FlatSpec, Matchers } + +class ScalatestTest extends FlatSpec with Matchers { + "scalatest" should "work" in { + 1 shouldBe 1 + } +} diff --git a/sbt/src/sbt-test/classloader-cache/scalatest/test b/sbt/src/sbt-test/classloader-cache/scalatest/test new file mode 100644 index 000000000..5ad5d9538 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/scalatest/test @@ -0,0 +1,5 @@ +> test + +$ copy-file changes/ScalatestTest.scala src/test/scala/sbt/ScalatestTest.scala + +-> test \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/snapshot/build.sbt b/sbt/src/sbt-test/classloader-cache/snapshot/build.sbt new file mode 100644 index 000000000..73ebc2aa9 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/snapshot/build.sbt @@ -0,0 +1,24 @@ +import java.nio.file.Files +import java.nio.file.attribute.FileTime + +import scala.collection.JavaConverters._ +val rewriteIvy = inputKey[Unit]("Rewrite ivy directory") + +val snapshot = (project in file(".")).settings( + name := "akka-test", + scalaVersion := "2.12.8", + libraryDependencies ++= Seq( + "com.lihaoyi" %% "utest" % "0.6.6" % "test" + ), + testFrameworks += TestFramework("utest.runner.Framework"), + resolvers += "Local Maven" at file("ivy").toURI.toURL.toString, + libraryDependencies += "sbt" %% "foo-lib" % "0.1.0-SNAPSHOT", + rewriteIvy := { + val dir = Def.spaceDelimited().parsed.head + sbt.IO.delete(file("ivy")) + sbt.IO.copyDirectory(file(s"libraries/library-$dir/ivy"), file("ivy")) + Files.walk(file("ivy").getCanonicalFile.toPath).iterator.asScala.foreach { f => + Files.setLastModifiedTime(f, FileTime.fromMillis(System.currentTimeMillis + 3000)) + } + } +) diff --git a/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/build.sbt b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/build.sbt new file mode 100644 index 000000000..55109c35f --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/build.sbt @@ -0,0 +1,5 @@ +name := "foo-lib" + +organization := "sbt" + +publishTo := Some(Resolver.file("test-resolver", file("").getCanonicalFile / "ivy")) diff --git a/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-javadoc.jar b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-javadoc.jar new file mode 100644 index 000000000..6ca76e2fd Binary files /dev/null and b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-javadoc.jar differ diff --git a/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-javadoc.jar.md5 b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-javadoc.jar.md5 new file mode 100644 index 000000000..bc15f9443 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-javadoc.jar.md5 @@ -0,0 +1 @@ +397429ea4a937c9ad21268ac7f294c9b \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-javadoc.jar.sha1 b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-javadoc.jar.sha1 new file mode 100644 index 000000000..c36a79d59 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-javadoc.jar.sha1 @@ -0,0 +1 @@ +183362cade58c89ae813465e7c509a3346515e39 \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-sources.jar b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-sources.jar new file mode 100644 index 000000000..f7acb17ad Binary files /dev/null and b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-sources.jar differ diff --git a/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-sources.jar.md5 b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-sources.jar.md5 new file mode 100644 index 000000000..9cadb3a11 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-sources.jar.md5 @@ -0,0 +1 @@ +9c46dbde2cd9c996c37c7ba4461eec7c \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-sources.jar.sha1 b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-sources.jar.sha1 new file mode 100644 index 000000000..b4a982ea0 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-sources.jar.sha1 @@ -0,0 +1 @@ +b5725ff80281d86491166550058b56c6b0b7dd2d \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.jar b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.jar new file mode 100644 index 000000000..197c6e591 Binary files /dev/null and b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.jar differ diff --git a/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.jar.md5 b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.jar.md5 new file mode 100644 index 000000000..0861ac32f --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.jar.md5 @@ -0,0 +1 @@ +c39385e52b24880f549e1c01642ed010 \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.jar.sha1 b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.jar.sha1 new file mode 100644 index 000000000..5d81c7d99 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.jar.sha1 @@ -0,0 +1 @@ +de497479331da6c5dff72b6c1cfa5fca1634933f \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.pom b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.pom new file mode 100644 index 000000000..42de5d5d5 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.pom @@ -0,0 +1,20 @@ + + + 4.0.0 + sbt + foo-lib_2.12 + jar + foo-lib + 0.1.0-SNAPSHOT + foo-lib + + sbt + + + + org.scala-lang + scala-library + 2.12.8 + + + \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.pom.md5 b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.pom.md5 new file mode 100644 index 000000000..394b7d529 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.pom.md5 @@ -0,0 +1 @@ +ada0c2e0276459449bf25a4f99aab3f6 \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.pom.sha1 b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.pom.sha1 new file mode 100644 index 000000000..7162aec5e --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.pom.sha1 @@ -0,0 +1 @@ +466216207d16d3e0daf0b2b18e67b20882f7f1b5 \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/project/build.properties b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/project/build.properties new file mode 100644 index 000000000..7c58a83ab --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.2.6 diff --git a/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/src/main/scala/sbt/Foo.scala b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/src/main/scala/sbt/Foo.scala new file mode 100644 index 000000000..7b95dd3d2 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-1/src/main/scala/sbt/Foo.scala @@ -0,0 +1,5 @@ +package sbt + +object Foo { + def x: Int = 1 +} diff --git a/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/build.sbt b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/build.sbt new file mode 100644 index 000000000..55109c35f --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/build.sbt @@ -0,0 +1,5 @@ +name := "foo-lib" + +organization := "sbt" + +publishTo := Some(Resolver.file("test-resolver", file("").getCanonicalFile / "ivy")) diff --git a/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-javadoc.jar b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-javadoc.jar new file mode 100644 index 000000000..8505dca48 Binary files /dev/null and b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-javadoc.jar differ diff --git a/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-javadoc.jar.md5 b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-javadoc.jar.md5 new file mode 100644 index 000000000..249153493 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-javadoc.jar.md5 @@ -0,0 +1 @@ +260c1aab64032676c2c5b7d3c2a8e385 \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-javadoc.jar.sha1 b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-javadoc.jar.sha1 new file mode 100644 index 000000000..ceb465ee3 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-javadoc.jar.sha1 @@ -0,0 +1 @@ +44c78fcc5a2918bc4b33afb7419ac9d643bbac26 \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-sources.jar b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-sources.jar new file mode 100644 index 000000000..5ef80e248 Binary files /dev/null and b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-sources.jar differ diff --git a/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-sources.jar.md5 b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-sources.jar.md5 new file mode 100644 index 000000000..8a81b3699 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-sources.jar.md5 @@ -0,0 +1 @@ +39c7358e50708bc3da53ffe34eb876d6 \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-sources.jar.sha1 b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-sources.jar.sha1 new file mode 100644 index 000000000..263e42fd8 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT-sources.jar.sha1 @@ -0,0 +1 @@ +64e078d3fed51c0ef3d8abdb096ab1406e44ee1c \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.jar b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.jar new file mode 100644 index 000000000..c41eddd31 Binary files /dev/null and b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.jar differ diff --git a/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.jar.md5 b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.jar.md5 new file mode 100644 index 000000000..c7bb182af --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.jar.md5 @@ -0,0 +1 @@ +763822fba242624131cf0706be56dabf \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.jar.sha1 b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.jar.sha1 new file mode 100644 index 000000000..6c5f9adf1 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.jar.sha1 @@ -0,0 +1 @@ +22192abf4b52e85b9f979aa883ab42ce33bc51cd \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.pom b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.pom new file mode 100644 index 000000000..42de5d5d5 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.pom @@ -0,0 +1,20 @@ + + + 4.0.0 + sbt + foo-lib_2.12 + jar + foo-lib + 0.1.0-SNAPSHOT + foo-lib + + sbt + + + + org.scala-lang + scala-library + 2.12.8 + + + \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.pom.md5 b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.pom.md5 new file mode 100644 index 000000000..394b7d529 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.pom.md5 @@ -0,0 +1 @@ +ada0c2e0276459449bf25a4f99aab3f6 \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.pom.sha1 b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.pom.sha1 new file mode 100644 index 000000000..7162aec5e --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/ivy/sbt/foo-lib_2.12/0.1.0-SNAPSHOT/foo-lib_2.12-0.1.0-SNAPSHOT.pom.sha1 @@ -0,0 +1 @@ +466216207d16d3e0daf0b2b18e67b20882f7f1b5 \ No newline at end of file diff --git a/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/project/build.properties b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/project/build.properties new file mode 100644 index 000000000..7c58a83ab --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.2.6 diff --git a/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/src/main/scala/sbt/Foo.scala b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/src/main/scala/sbt/Foo.scala new file mode 100644 index 000000000..75cca6b19 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/snapshot/libraries/library-2/src/main/scala/sbt/Foo.scala @@ -0,0 +1,5 @@ +package sbt + +object Foo { + def x: Int = 2 +} diff --git a/sbt/src/sbt-test/classloader-cache/snapshot/src/test/scala/sbt/SnapshotTest.scala b/sbt/src/sbt-test/classloader-cache/snapshot/src/test/scala/sbt/SnapshotTest.scala new file mode 100644 index 000000000..fe242766a --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/snapshot/src/test/scala/sbt/SnapshotTest.scala @@ -0,0 +1,11 @@ +package sbt + +import utest._ + +object SnapshotTest extends TestSuite { + val tests: Tests = Tests { + 'foo - { + com.swoval.Foo.x ==> 1 + } + } +} diff --git a/sbt/src/sbt-test/classloader-cache/snapshot/test b/sbt/src/sbt-test/classloader-cache/snapshot/test new file mode 100644 index 000000000..179d9c36c --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/snapshot/test @@ -0,0 +1,5 @@ +> rewriteIvy 1 +# If the ClassLoaderCache is not correctly set up, then the sbt testing framework fails to work correctly +> test +> rewriteIvy 2 +-> test diff --git a/sbt/src/sbt-test/classloader-cache/utest/build.sbt b/sbt/src/sbt-test/classloader-cache/utest/build.sbt new file mode 100644 index 000000000..b0d82f379 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/utest/build.sbt @@ -0,0 +1,8 @@ +val utestTest = (project in file(".")).settings( + name := "utest-test", + scalaVersion := "2.12.8", + libraryDependencies ++= Seq( + "com.lihaoyi" %% "utest" % "0.6.6" % "test" + ), + testFrameworks += TestFramework("utest.runner.Framework") +) diff --git a/sbt/src/sbt-test/classloader-cache/utest/src/test/scala/sbt/UtestTest.scala b/sbt/src/sbt-test/classloader-cache/utest/src/test/scala/sbt/UtestTest.scala new file mode 100644 index 000000000..caa8ff809 --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/utest/src/test/scala/sbt/UtestTest.scala @@ -0,0 +1,11 @@ +package sbt + +import utest._ + +object UtestTest extends TestSuite { + val tests: Tests = Tests { + 'foo - { + 1 ==> 1 + } + } +} diff --git a/sbt/src/sbt-test/classloader-cache/utest/test b/sbt/src/sbt-test/classloader-cache/utest/test new file mode 100644 index 000000000..a9bb439cf --- /dev/null +++ b/sbt/src/sbt-test/classloader-cache/utest/test @@ -0,0 +1,2 @@ +# If the ClassLoaderCache is not correctly set up, then the sbt testing framework fails to work correctly +> test diff --git a/sbt/src/sbt-test/source-dependencies/export-jars/test b/sbt/src/sbt-test/source-dependencies/export-jars/test index 3676fac5d..f1a605f00 100644 --- a/sbt/src/sbt-test/source-dependencies/export-jars/test +++ b/sbt/src/sbt-test/source-dependencies/export-jars/test @@ -5,11 +5,9 @@ $ copy-file changes/A1.scala a/A.scala $ copy-file changes/A2.scala a/A.scala # done this way because last modified times often have ~1s resolution -$ sleep 2000 > run 2 $ copy-file changes/A3.scala a/A.scala -$ sleep 2000 > run 3 $ copy-file changes/build2.sbt build2.sbt @@ -19,9 +17,7 @@ $ copy-file changes/A1.scala a/A.scala > run 1 $ copy-file changes/A2.scala a/A.scala -$ sleep 2000 > run 2 $ copy-file changes/A3.scala a/A.scala -$ sleep 2000 > run 3 diff --git a/sbt/src/sbt-test/tests/scala-instance-classloader/src/test/scala/Test.scala b/sbt/src/sbt-test/tests/scala-instance-classloader/src/test/scala/Test.scala index 6006e8257..7fdff3651 100644 --- a/sbt/src/sbt-test/tests/scala-instance-classloader/src/test/scala/Test.scala +++ b/sbt/src/sbt-test/tests/scala-instance-classloader/src/test/scala/Test.scala @@ -10,11 +10,10 @@ class BadTest { // * Load something from our own classloader that's INSIDE the scala library // * Try to load that same something from the THREAD CONTEXT classloader. // * Ensure we can do both, i.e. the second used to be filtered and broken. - val current = Thread.currentThread.getContextClassLoader - val mine = this.getClass.getClassLoader val system = ActorSystem() def evilGetThreadExectionContextName = system.asInstanceOf[ActorSystemImpl].internalCallingThreadExecutionContext.getClass.getName + system.terminate() val expected = "scala.concurrent.Future$InternalCallbackExecutor$" Assert.assertEquals("Failed to grab appropriate Akka name", expected, evilGetThreadExectionContextName) } diff --git a/scripted-sbt-redux/src/main/scala/sbt/scriptedtest/ScriptedTests.scala b/scripted-sbt-redux/src/main/scala/sbt/scriptedtest/ScriptedTests.scala index eb8cda4e5..91373e1d0 100644 --- a/scripted-sbt-redux/src/main/scala/sbt/scriptedtest/ScriptedTests.scala +++ b/scripted-sbt-redux/src/main/scala/sbt/scriptedtest/ScriptedTests.scala @@ -170,6 +170,10 @@ final class ScriptedTests( case "actions/external-doc" => LauncherBased // sbt/Package$ case "actions/input-task" => LauncherBased // sbt/Package$ case "actions/input-task-dyn" => LauncherBased // sbt/Package$ + case "classloader-cache/akka-actor-system" => LauncherBased // sbt/Package$ + case "classloader-cache/jni" => LauncherBased // sbt/Package$ + case "classloader-cache/library-mismatch" => LauncherBased // sbt/Package$ + case "classloader-cache/runtime-layers" => 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$ diff --git a/testing/src/main/scala/sbt/TestFramework.scala b/testing/src/main/scala/sbt/TestFramework.scala index ad8b8d91f..3f7c68341 100644 --- a/testing/src/main/scala/sbt/TestFramework.scala +++ b/testing/src/main/scala/sbt/TestFramework.scala @@ -46,6 +46,17 @@ final class TestFramework(val implClassNames: String*) extends Serializable { log: ManagedLogger, frameworkClassNames: List[String] ): Option[Framework] = { + def logError(e: Throwable): Option[Framework] = { + log.error( + s"Error loading test framework ($e). This usually means that you are" + + " 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 / classLoaderLayeringStrategy := new ClassLoaderLayeringStrategy.Test(false, true)\nor\n" + + "Test / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.Flat" + ) + None + } frameworkClassNames match { case head :: tail => try { @@ -54,6 +65,8 @@ final class TestFramework(val implClassNames: String*) extends Serializable { case oldFramework: OldFramework => new FrameworkWrapper(oldFramework) }) } catch { + case e: NoClassDefFoundError => logError(e) + case e: MatchError => logError(e) case _: ClassNotFoundException => log.debug("Framework implementation '" + head + "' not present.") createFramework(loader, log, tail) @@ -245,6 +258,7 @@ object TestFramework { Thread.currentThread.setContextClassLoader(loader) try { eval } finally { Thread.currentThread.setContextClassLoader(oldLoader) } } + @deprecated("1.3.0", "This has been replaced by the ClassLoaders.test task.") def createTestLoader( classpath: Seq[File], scalaInstance: ScalaInstance, diff --git a/testing/src/main/scala/sbt/TestReportListener.scala b/testing/src/main/scala/sbt/TestReportListener.scala index 6a02c35e0..e16f3c8ec 100644 --- a/testing/src/main/scala/sbt/TestReportListener.scala +++ b/testing/src/main/scala/sbt/TestReportListener.scala @@ -50,8 +50,30 @@ final class SuiteResult( val skippedCount: Int, val ignoredCount: Int, val canceledCount: Int, - val pendingCount: Int + val pendingCount: Int, + val throwables: Seq[Throwable] ) { + def this( + result: TestResult, + passedCount: Int, + failureCount: Int, + errorCount: Int, + skippedCount: Int, + ignoredCount: Int, + canceledCount: Int, + pendingCount: Int, + ) = + this( + result, + passedCount, + failureCount, + errorCount, + skippedCount, + ignoredCount, + canceledCount, + pendingCount, + Nil + ) def +(other: SuiteResult): SuiteResult = { val combinedTestResult = (result, other.result) match { @@ -68,7 +90,8 @@ final class SuiteResult( skippedCount + other.skippedCount, ignoredCount + other.ignoredCount, canceledCount + other.canceledCount, - pendingCount + other.pendingCount + pendingCount + other.pendingCount, + throwables ++ other.throwables ) } } @@ -86,7 +109,8 @@ object SuiteResult { count(TStatus.Skipped), count(TStatus.Ignored), count(TStatus.Canceled), - count(TStatus.Pending) + count(TStatus.Pending), + events.collect { case e if e.throwable.isDefined => e.throwable.get } ) }