mirror of https://github.com/sbt/sbt.git
Merge pull request #4476 from eatkins/classloaders
Add cache of layered classloaders for test and run tasks
This commit is contained in:
commit
6e5caa90c1
|
|
@ -23,7 +23,7 @@ env:
|
||||||
- SBT_CMD="scripted source-dependencies/*1of3"
|
- SBT_CMD="scripted source-dependencies/*1of3"
|
||||||
- SBT_CMD="scripted source-dependencies/*2of3"
|
- SBT_CMD="scripted source-dependencies/*2of3"
|
||||||
- SBT_CMD="scripted source-dependencies/*3of3"
|
- SBT_CMD="scripted source-dependencies/*3of3"
|
||||||
- SBT_CMD="scripted tests/* watch/*"
|
- SBT_CMD="scripted tests/* watch/* classloader-cache/*"
|
||||||
- SBT_CMD="repoOverrideTest:scripted dependency-management/*"
|
- SBT_CMD="repoOverrideTest:scripted dependency-management/*"
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
|
|
@ -53,7 +53,7 @@ install:
|
||||||
|
|
||||||
script:
|
script:
|
||||||
# It doesn't need that much memory because compile and run are forked
|
# 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:
|
before_cache:
|
||||||
- find $HOME/.ivy2 -name "ivydata-*.properties" -delete
|
- find $HOME/.ivy2 -name "ivydata-*.properties" -delete
|
||||||
|
|
|
||||||
|
|
@ -233,8 +233,6 @@ $AliasCommand name=
|
||||||
def continuousDetail: String = "Executes the specified command whenever source files change."
|
def continuousDetail: String = "Executes the specified command whenever source files change."
|
||||||
def continuousBriefHelp: (String, String) =
|
def continuousBriefHelp: (String, String) =
|
||||||
(ContinuousExecutePrefix + " <command>", continuousDetail)
|
(ContinuousExecutePrefix + " <command>", continuousDetail)
|
||||||
def FlushFileTreeRepository: String = "flushFileTreeRepository"
|
def ClearCaches: String = "clearCaches"
|
||||||
def FlushDetailed: String =
|
def ClearCachesDetailed: String = "Clears all of sbt's internal caches."
|
||||||
"Resets the global file repository in the event that the repository has become inconsistent " +
|
|
||||||
"with the file system."
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
package sbt
|
package sbt
|
||||||
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
import sbt.internal.util.AttributeKey
|
import sbt.internal.util.AttributeKey
|
||||||
import sbt.internal.inc.classpath.ClassLoaderCache
|
import sbt.internal.inc.classpath.ClassLoaderCache
|
||||||
import sbt.internal.server.ServerHandler
|
import sbt.internal.server.ServerHandler
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -139,8 +139,10 @@ object Defaults extends BuildCommon {
|
||||||
)
|
)
|
||||||
private[sbt] lazy val globalCore: Seq[Setting[_]] = globalDefaults(
|
private[sbt] lazy val globalCore: Seq[Setting[_]] = globalDefaults(
|
||||||
defaultTestTasks(test) ++ defaultTestTasks(testOnly) ++ defaultTestTasks(testQuick) ++ Seq(
|
defaultTestTasks(test) ++ defaultTestTasks(testOnly) ++ defaultTestTasks(testQuick) ++ Seq(
|
||||||
excludeFilter :== HiddenFileFilter
|
excludeFilter :== HiddenFileFilter,
|
||||||
) ++ globalIvyCore ++ globalJvmCore
|
classLoaderCache := ClassLoaderCache(4),
|
||||||
|
) ++ TaskRepository
|
||||||
|
.proxy(GlobalScope / classLoaderCache, ClassLoaderCache(4)) ++ globalIvyCore ++ globalJvmCore
|
||||||
) ++ globalSbtCore
|
) ++ globalSbtCore
|
||||||
|
|
||||||
private[sbt] lazy val globalJvmCore: Seq[Setting[_]] =
|
private[sbt] lazy val globalJvmCore: Seq[Setting[_]] =
|
||||||
|
|
@ -790,11 +792,7 @@ object Defaults extends BuildCommon {
|
||||||
: Seq[Setting[_]] = testTaskOptions(test) ++ testTaskOptions(testOnly) ++ testTaskOptions(
|
: Seq[Setting[_]] = testTaskOptions(test) ++ testTaskOptions(testOnly) ++ testTaskOptions(
|
||||||
testQuick
|
testQuick
|
||||||
) ++ testDefaults ++ Seq(
|
) ++ testDefaults ++ Seq(
|
||||||
testLoader := TestFramework.createTestLoader(
|
testLoader := ClassLoaders.testTask.value,
|
||||||
data(fullClasspath.value),
|
|
||||||
scalaInstance.value,
|
|
||||||
IO.createUniqueDirectory(taskTemporaryDirectory.value)
|
|
||||||
),
|
|
||||||
loadedTestFrameworks := {
|
loadedTestFrameworks := {
|
||||||
val loader = testLoader.value
|
val loader = testLoader.value
|
||||||
val log = streams.value.log
|
val log = streams.value.log
|
||||||
|
|
@ -813,7 +811,8 @@ object Defaults extends BuildCommon {
|
||||||
(testExecution in test).value,
|
(testExecution in test).value,
|
||||||
(fullClasspath in test).value,
|
(fullClasspath in test).value,
|
||||||
testForkedParallel.value,
|
testForkedParallel.value,
|
||||||
(javaOptions in test).value
|
(javaOptions in test).value,
|
||||||
|
(classLoaderLayeringStrategy).value
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
).value,
|
).value,
|
||||||
|
|
@ -979,7 +978,8 @@ object Defaults extends BuildCommon {
|
||||||
newConfig,
|
newConfig,
|
||||||
fullClasspath.value,
|
fullClasspath.value,
|
||||||
testForkedParallel.value,
|
testForkedParallel.value,
|
||||||
javaOptions.value
|
javaOptions.value,
|
||||||
|
classLoaderLayeringStrategy.value
|
||||||
)
|
)
|
||||||
val taskName = display.show(resolvedScoped.value)
|
val taskName = display.show(resolvedScoped.value)
|
||||||
val trl = testResultLogger.value
|
val trl = testResultLogger.value
|
||||||
|
|
@ -1022,7 +1022,8 @@ object Defaults extends BuildCommon {
|
||||||
config,
|
config,
|
||||||
cp,
|
cp,
|
||||||
forkedParallelExecution = false,
|
forkedParallelExecution = false,
|
||||||
javaOptions = Nil
|
javaOptions = Nil,
|
||||||
|
strategy = ClassLoaderLayeringStrategy.TestDependencies,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1043,7 +1044,8 @@ object Defaults extends BuildCommon {
|
||||||
config,
|
config,
|
||||||
cp,
|
cp,
|
||||||
forkedParallelExecution,
|
forkedParallelExecution,
|
||||||
javaOptions = Nil
|
javaOptions = Nil,
|
||||||
|
strategy = ClassLoaderLayeringStrategy.TestDependencies,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1055,7 +1057,8 @@ object Defaults extends BuildCommon {
|
||||||
config: Tests.Execution,
|
config: Tests.Execution,
|
||||||
cp: Classpath,
|
cp: Classpath,
|
||||||
forkedParallelExecution: Boolean,
|
forkedParallelExecution: Boolean,
|
||||||
javaOptions: Seq[String]
|
javaOptions: Seq[String],
|
||||||
|
strategy: ClassLoaderLayeringStrategy,
|
||||||
): Initialize[Task[Tests.Output]] = {
|
): Initialize[Task[Tests.Output]] = {
|
||||||
val runners = createTestRunners(frameworks, loader, config)
|
val runners = createTestRunners(frameworks, loader, config)
|
||||||
val groupTasks = groups map {
|
val groupTasks = groups map {
|
||||||
|
|
@ -1083,6 +1086,25 @@ object Defaults extends BuildCommon {
|
||||||
}
|
}
|
||||||
val output = Tests.foldTasks(groupTasks, config.parallel)
|
val output = Tests.foldTasks(groupTasks, config.parallel)
|
||||||
val result = output map { out =>
|
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 =
|
val summaries =
|
||||||
runners map {
|
runners map {
|
||||||
case (tf, r) =>
|
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 runnerTask: Setting[Task[ScalaRun]] = runner := runnerInit.value
|
||||||
|
|
||||||
def runnerInit: Initialize[Task[ScalaRun]] = Def.task {
|
@deprecated("This is no longer used internally by sbt.", "1.3.0")
|
||||||
val tmp = taskTemporaryDirectory.value
|
def runnerInit: Initialize[Task[ScalaRun]] = ClassLoaders.runner
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def foreachJobTask(
|
private def foreachJobTask(
|
||||||
f: (BackgroundJobService, JobHandle) => Unit
|
f: (BackgroundJobService, JobHandle) => Unit
|
||||||
|
|
@ -1764,7 +1760,11 @@ object Defaults extends BuildCommon {
|
||||||
|
|
||||||
// 1. runnerSettings is added unscoped via JvmPlugin.
|
// 1. runnerSettings is added unscoped via JvmPlugin.
|
||||||
// 2. In addition it's added scoped to run task.
|
// 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
|
lazy val baseTasks: Seq[Setting[_]] = projectTasks ++ packageBase
|
||||||
|
|
||||||
|
|
@ -1772,16 +1772,32 @@ object Defaults extends BuildCommon {
|
||||||
Classpaths.configSettings ++ configTasks ++ configPaths ++ packageConfig ++
|
Classpaths.configSettings ++ configTasks ++ configPaths ++ packageConfig ++
|
||||||
Classpaths.compilerPluginConfig ++ deprecationSettings
|
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[_]] =
|
lazy val compileSettings: Seq[Setting[_]] =
|
||||||
configSettings ++
|
configSettings ++
|
||||||
(mainBgRunMainTask +: mainBgRunTask +: FileManagement.appendBaseSources) ++
|
(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 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) ++
|
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.
|
// These are project level settings that MUST be on every project.
|
||||||
lazy val coreDefaultSettings: Seq[Setting[_]] =
|
lazy val coreDefaultSettings: Seq[Setting[_]] =
|
||||||
|
|
@ -3556,17 +3572,19 @@ trait BuildExtra extends BuildCommon with DefExtra {
|
||||||
InputTask.apply(Def.value((s: State) => Def.spaceDelimited()))(f)
|
InputTask.apply(Def.value((s: State) => Def.spaceDelimited()))(f)
|
||||||
|
|
||||||
Vector(
|
Vector(
|
||||||
scoped := (inputTask { result =>
|
scoped := inputTask { result =>
|
||||||
(initScoped(scoped.scopedKey, runnerInit)
|
initScoped(
|
||||||
.zipWith(Def.task { ((fullClasspath in config).value, streams.value, result.value) })) {
|
scoped.scopedKey,
|
||||||
|
ClassLoaders.runner mapReferenced Project.mapScope(s => s.in(config))
|
||||||
|
).zipWith(Def.task { ((fullClasspath in config).value, streams.value, result.value) }) {
|
||||||
(rTask, t) =>
|
(rTask, t) =>
|
||||||
(t, rTask) map {
|
(t, rTask) map {
|
||||||
case ((cp, s, args), r) =>
|
case ((cp, s, args), r) =>
|
||||||
r.run(mainClass, data(cp), baseArguments ++ args, s.log).get
|
r.run(mainClass, data(cp), baseArguments ++ args, s.log).get
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).evaluated
|
}.evaluated
|
||||||
) ++ inTask(scoped)(forkOptions := forkOptionsTask.value)
|
) ++ inTask(scoped)(forkOptions in config := forkOptionsTask.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// public API
|
// public API
|
||||||
|
|
@ -3578,16 +3596,18 @@ trait BuildExtra extends BuildCommon with DefExtra {
|
||||||
arguments: String*
|
arguments: String*
|
||||||
): Vector[Setting[_]] =
|
): Vector[Setting[_]] =
|
||||||
Vector(
|
Vector(
|
||||||
scoped := ((initScoped(scoped.scopedKey, runnerInit)
|
scoped := initScoped(
|
||||||
.zipWith(Def.task { ((fullClasspath in config).value, streams.value) })) {
|
scoped.scopedKey,
|
||||||
|
ClassLoaders.runner mapReferenced Project.mapScope(s => s.in(config))
|
||||||
|
).zipWith(Def.task { ((fullClasspath in config).value, streams.value) }) {
|
||||||
case (rTask, t) =>
|
case (rTask, t) =>
|
||||||
(t, rTask) map {
|
(t, rTask) map {
|
||||||
case ((cp, s), r) =>
|
case ((cp, s), r) =>
|
||||||
r.run(mainClass, data(cp), arguments, s.log).get
|
r.run(mainClass, data(cp), arguments, s.log).get
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
.value
|
.value
|
||||||
) ++ inTask(scoped)(forkOptions := forkOptionsTask.value)
|
) ++ inTask(scoped)(forkOptions in config := forkOptionsTask.value)
|
||||||
|
|
||||||
def initScoped[T](sk: ScopedKey[_], i: Initialize[T]): Initialize[T] =
|
def initScoped[T](sk: ScopedKey[_], i: Initialize[T]): Initialize[T] =
|
||||||
initScope(fillTaskAxis(sk.scope, sk.key), i)
|
initScope(fillTaskAxis(sk.scope, sk.key), i)
|
||||||
|
|
|
||||||
|
|
@ -264,6 +264,7 @@ object Keys {
|
||||||
val bgRunMain = inputKey[JobHandle]("Start a provided main class as a background job")
|
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 fgRunMain = inputKey[Unit]("Start a provided main class as a foreground job")
|
||||||
val bgCopyClasspath = settingKey[Boolean]("Copies classpath on bgRun to prevent conflict.")
|
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
|
// Test Keys
|
||||||
val testLoader = taskKey[ClassLoader]("Provides the class loader used for testing.").withRank(DTask)
|
val testLoader = taskKey[ClassLoader]("Provides the class loader used for testing.").withRank(DTask)
|
||||||
|
|
@ -460,6 +461,8 @@ object Keys {
|
||||||
val resolvedScoped = Def.resolvedScoped
|
val resolvedScoped = Def.resolvedScoped
|
||||||
val pluginData = taskKey[PluginData]("Information from the plugin build needed in the main build definition.").withRank(DTask)
|
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 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
|
// wrapper to work around SI-2915
|
||||||
private[sbt] final class TaskProgress(val progress: ExecuteProgress[Task])
|
private[sbt] final class TaskProgress(val progress: ExecuteProgress[Task])
|
||||||
|
|
|
||||||
|
|
@ -214,7 +214,7 @@ object BuiltinCommands {
|
||||||
BasicCommands.multi,
|
BasicCommands.multi,
|
||||||
act,
|
act,
|
||||||
continuous,
|
continuous,
|
||||||
flushFileTreeRepository
|
clearCaches
|
||||||
) ++ allBasicCommands
|
) ++ allBasicCommands
|
||||||
|
|
||||||
def DefaultBootCommands: Seq[String] =
|
def DefaultBootCommands: Seq[String] =
|
||||||
|
|
@ -830,7 +830,7 @@ object BuiltinCommands {
|
||||||
|
|
||||||
val session = Load.initialSession(structure, eval, s0)
|
val session = Load.initialSession(structure, eval, s0)
|
||||||
SessionSettings.checkSession(session, s)
|
SessionSettings.checkSession(session, s)
|
||||||
registerGlobalFileRepository(Project.setProject(session, structure, s))
|
registerGlobalCaches(Project.setProject(session, structure, s))
|
||||||
}
|
}
|
||||||
|
|
||||||
def registerCompilerCache(s: State): State = {
|
def registerCompilerCache(s: State): State = {
|
||||||
|
|
@ -848,26 +848,31 @@ object BuiltinCommands {
|
||||||
}
|
}
|
||||||
s.put(Keys.stateCompilerCache, cache)
|
s.put(Keys.stateCompilerCache, cache)
|
||||||
}
|
}
|
||||||
def registerGlobalFileRepository(s: State): State = {
|
private[sbt] def registerGlobalCaches(s: State): State = {
|
||||||
val extracted = Project.extract(s)
|
val extracted = Project.extract(s)
|
||||||
try {
|
try {
|
||||||
val (_, config: FileTreeViewConfig) = extracted.runTask(Keys.fileTreeViewConfig, s)
|
def cleanup(): Unit = {
|
||||||
val view: FileTreeDataView[StampedFile] = config.newDataView()
|
|
||||||
val newState = s.addExitHook {
|
|
||||||
s.get(BasicKeys.globalFileTreeView).foreach(_.close())
|
s.get(BasicKeys.globalFileTreeView).foreach(_.close())
|
||||||
s.attributes.remove(BasicKeys.globalFileTreeView)
|
s.attributes.remove(BasicKeys.globalFileTreeView)
|
||||||
|
s.get(Keys.taskRepository).foreach(_.close())
|
||||||
|
s.attributes.remove(Keys.taskRepository)
|
||||||
()
|
()
|
||||||
}
|
}
|
||||||
newState.get(BasicKeys.globalFileTreeView).foreach(_.close())
|
val (_, config: FileTreeViewConfig) = extracted.runTask(Keys.fileTreeViewConfig, s)
|
||||||
newState.put(BasicKeys.globalFileTreeView, view)
|
val view: FileTreeDataView[StampedFile] = config.newDataView()
|
||||||
|
val newState = s.addExitHook(cleanup())
|
||||||
|
cleanup()
|
||||||
|
newState
|
||||||
|
.put(BasicKeys.globalFileTreeView, view)
|
||||||
|
.put(Keys.taskRepository, new TaskRepository.Repr)
|
||||||
} catch {
|
} catch {
|
||||||
case NonFatal(_) => s
|
case NonFatal(_) => s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def flushFileTreeRepository: Command = {
|
def clearCaches: Command = {
|
||||||
val help = Help.more(FlushFileTreeRepository, FlushDetailed)
|
val help = Help.more(ClearCaches, ClearCachesDetailed)
|
||||||
Command.command(FlushFileTreeRepository, help)(registerGlobalFileRepository)
|
Command.command(ClearCaches, help)(registerGlobalCaches)
|
||||||
}
|
}
|
||||||
|
|
||||||
def shell: Command = Command.command(Shell, Help.more(Shell, ShellDetailed)) { s0 =>
|
def shell: Command = Command.command(Shell, Help.more(Shell, ShellDetailed)) { s0 =>
|
||||||
|
|
|
||||||
|
|
@ -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 = {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -26,7 +26,7 @@ private[sbt] object FileManagement {
|
||||||
val interactive = remaining.contains("shell") || remaining.lastOption.contains("iflast shell")
|
val interactive = remaining.contains("shell") || remaining.lastOption.contains("iflast shell")
|
||||||
val scripted = remaining.contains("setUpScripted")
|
val scripted = remaining.contains("setUpScripted")
|
||||||
|
|
||||||
val continuous = remaining.exists(_.startsWith(ContinuousExecutePrefix))
|
val continuous = remaining.lastOption.exists(_.startsWith(ContinuousExecutePrefix))
|
||||||
if (!scripted && (interactive || continuous)) {
|
if (!scripted && (interactive || continuous)) {
|
||||||
FileTreeViewConfig
|
FileTreeViewConfig
|
||||||
.default(watchAntiEntropy.value, pollInterval.value, pollingDirectories.value)
|
.default(watchAntiEntropy.value, pollInterval.value, pollingDirectories.value)
|
||||||
|
|
|
||||||
|
|
@ -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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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()
|
||||||
|
()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -58,7 +58,9 @@ class ForkRun(config: ForkOptions) extends ScalaRun {
|
||||||
private def classpathOption(classpath: Seq[File]) =
|
private def classpathOption(classpath: Seq[File]) =
|
||||||
"-classpath" :: Path.makeString(classpath) :: Nil
|
"-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.*/
|
/** 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] = {
|
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
|
log: Logger
|
||||||
): Unit = {
|
): Unit = {
|
||||||
log.debug(" Classpath:\n\t" + classpath.mkString("\n\t"))
|
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)
|
val main = getMainMethod(mainClassName, loader)
|
||||||
invokeMain(loader, main, options)
|
invokeMain(loader, main, options)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"))
|
||||||
|
)
|
||||||
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
> set Test / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.ShareRuntimeDependenciesLayerWithTestDependencies
|
||||||
|
|
||||||
|
> run
|
||||||
|
|
||||||
|
> test
|
||||||
|
|
||||||
|
> test
|
||||||
|
|
@ -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
|
||||||
|
)
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package sbt;
|
||||||
|
|
||||||
|
final class JniLibrary {
|
||||||
|
public native int getIntegerValue();
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
#include <jni.h>
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
#include <jni.h>
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
/* DO NOT EDIT THIS FILE - it is machine generated */
|
||||||
|
#include <jni.h>
|
||||||
|
/* 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
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -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}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
> copyTestResources 1
|
||||||
|
> wrappedRun
|
||||||
|
> wrappedTest
|
||||||
|
> copyTestResources 2
|
||||||
|
$ copy-file changes/JniLibraryTest.scala src/test/scala/sbt/JniLibraryTest.scala
|
||||||
|
> wrappedTest
|
||||||
|
|
@ -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",
|
||||||
|
)
|
||||||
|
|
@ -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
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
sbt.version=1.2.6
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package sbt.foo;
|
||||||
|
|
||||||
|
public class Foo {
|
||||||
|
static public int x() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
sbt.version=1.2.6
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
package sbt.foo;
|
||||||
|
|
||||||
|
public class Foo {
|
||||||
|
static public int x() {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
static public int y() {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
|
@ -0,0 +1 @@
|
||||||
|
66124717bb8840ce6f60567b19634a4b
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
ca2193d86495f120496370cf2e75355f2b10b2a3
|
||||||
Binary file not shown.
|
|
@ -0,0 +1 @@
|
||||||
|
8bb4c394490df92c43361e47d98cba52
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
86ce6f4d12e075e8ef4ba80b671783673a8a0443
|
||||||
Binary file not shown.
|
|
@ -0,0 +1 @@
|
||||||
|
4cd195abad8639ba6df345c125068058
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
e18f4c7a4b52d288733f123008d98985610da4f3
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>sbt</groupId>
|
||||||
|
<artifactId>foo-lib</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<description>foo-lib</description>
|
||||||
|
<version>0.1.0</version>
|
||||||
|
<name>foo-lib</name>
|
||||||
|
<organization>
|
||||||
|
<name>sbt</name>
|
||||||
|
</organization>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
22ebca3d8b32a4c0cb40341501b798e2
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
575258d8c9fecff7e8beefee2637c2928328e0dc
|
||||||
Binary file not shown.
|
|
@ -0,0 +1 @@
|
||||||
|
ce4dcb6be9c11c1356e6aeaa024c80f1
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
cc1a6b548cb9efd6e43c7fb5fa44728fc3f50d45
|
||||||
Binary file not shown.
|
|
@ -0,0 +1 @@
|
||||||
|
f18c726e5009cf205ca83cf96a28345b
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
af0ac149e4c810884bb921bb3826e495faa5854b
|
||||||
Binary file not shown.
|
|
@ -0,0 +1 @@
|
||||||
|
fcadc4b2d6965cec0f5e5efb0a59226d
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
585422ad5196b83f83bbca644e62a39a2e4171e0
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>sbt</groupId>
|
||||||
|
<artifactId>foo-lib</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<description>foo-lib</description>
|
||||||
|
<version>0.2.0</version>
|
||||||
|
<name>foo-lib</name>
|
||||||
|
<organization>
|
||||||
|
<name>sbt</name>
|
||||||
|
</organization>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
790acd1d77316ff2c0310bb88d01dc72
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
94eec0571d936004eb5682b8b12268d1bcb2be40
|
||||||
Binary file not shown.
|
|
@ -0,0 +1 @@
|
||||||
|
31f61c992ede7185d70d7c8a9ed19340
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
3083e0439d988b1cce30be566f8c95448c310eca
|
||||||
Binary file not shown.
|
|
@ -0,0 +1 @@
|
||||||
|
b9e2ead915294bf9e79622fe4ad9eadb
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
5454c981af36713e2f0cedce98a83da2b0e4b982
|
||||||
Binary file not shown.
|
|
@ -0,0 +1 @@
|
||||||
|
c7b4fbba100656a2798c39ca1d88317f
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
f0fcc50b65e83f42210bbd3f366e2cc439177aa8
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>sbt</groupId>
|
||||||
|
<artifactId>transitive-lib</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<description>transitive-lib</description>
|
||||||
|
<version>0.1.0</version>
|
||||||
|
<name>transitive-lib</name>
|
||||||
|
<organization>
|
||||||
|
<name>sbt</name>
|
||||||
|
</organization>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>sbt</groupId>
|
||||||
|
<artifactId>foo-lib</artifactId>
|
||||||
|
<version>0.1.0</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
460393fd256f121413f943d085887278
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
4cb84c1daf7152544065849db0c1ce2d8d47c334
|
||||||
|
|
@ -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
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
sbt.version=1.2.6
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package sbt.transitive;
|
||||||
|
|
||||||
|
public class Transitive {
|
||||||
|
public static int x() {
|
||||||
|
return sbt.foo.Foo.x();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package sbt
|
||||||
|
|
||||||
|
object TestMain {
|
||||||
|
def main(args: Array[String]) {
|
||||||
|
println(transitive.Transitive.x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package sbt
|
||||||
|
|
||||||
|
import utest._
|
||||||
|
|
||||||
|
object MismatchedLibrariesTest extends TestSuite {
|
||||||
|
val tests: Tests = Tests {
|
||||||
|
'check - {
|
||||||
|
assert(foo.Foo.y == 3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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",
|
||||||
|
)
|
||||||
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
val test = (project in file(".")).settings(
|
||||||
|
libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.4" % "test"
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package sbt
|
||||||
|
|
||||||
|
import org.scalatest.{ FlatSpec, Matchers }
|
||||||
|
|
||||||
|
class ScalatestTest extends FlatSpec with Matchers {
|
||||||
|
"scalatest" should "fail" in {
|
||||||
|
1 shouldBe 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package sbt
|
||||||
|
|
||||||
|
import org.scalatest.{ FlatSpec, Matchers }
|
||||||
|
|
||||||
|
class ScalatestTest extends FlatSpec with Matchers {
|
||||||
|
"scalatest" should "work" in {
|
||||||
|
1 shouldBe 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
> test
|
||||||
|
|
||||||
|
$ copy-file changes/ScalatestTest.scala src/test/scala/sbt/ScalatestTest.scala
|
||||||
|
|
||||||
|
-> test
|
||||||
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
name := "foo-lib"
|
||||||
|
|
||||||
|
organization := "sbt"
|
||||||
|
|
||||||
|
publishTo := Some(Resolver.file("test-resolver", file("").getCanonicalFile / "ivy"))
|
||||||
Binary file not shown.
|
|
@ -0,0 +1 @@
|
||||||
|
397429ea4a937c9ad21268ac7f294c9b
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue