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/*2of3"
|
||||
- SBT_CMD="scripted source-dependencies/*3of3"
|
||||
- SBT_CMD="scripted tests/* watch/*"
|
||||
- SBT_CMD="scripted tests/* watch/* classloader-cache/*"
|
||||
- SBT_CMD="repoOverrideTest:scripted dependency-management/*"
|
||||
|
||||
matrix:
|
||||
|
|
@ -53,7 +53,7 @@ install:
|
|||
|
||||
script:
|
||||
# It doesn't need that much memory because compile and run are forked
|
||||
- sbt -J-XX:ReservedCodeCacheSize=128m -J-Xmx800M -J-Xms800M -J-server "$SBT_CMD"
|
||||
- sbt -Dsbt.version=1.2.6 -J-XX:ReservedCodeCacheSize=128m -J-Xmx800M -J-Xms800M -J-server "$SBT_CMD"
|
||||
|
||||
before_cache:
|
||||
- find $HOME/.ivy2 -name "ivydata-*.properties" -delete
|
||||
|
|
|
|||
|
|
@ -233,8 +233,6 @@ $AliasCommand name=
|
|||
def continuousDetail: String = "Executes the specified command whenever source files change."
|
||||
def continuousBriefHelp: (String, String) =
|
||||
(ContinuousExecutePrefix + " <command>", continuousDetail)
|
||||
def FlushFileTreeRepository: String = "flushFileTreeRepository"
|
||||
def FlushDetailed: String =
|
||||
"Resets the global file repository in the event that the repository has become inconsistent " +
|
||||
"with the file system."
|
||||
def ClearCaches: String = "clearCaches"
|
||||
def ClearCachesDetailed: String = "Clears all of sbt's internal caches."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
package sbt
|
||||
|
||||
import java.io.File
|
||||
|
||||
import sbt.internal.util.AttributeKey
|
||||
import sbt.internal.inc.classpath.ClassLoaderCache
|
||||
import sbt.internal.server.ServerHandler
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
defaultTestTasks(test) ++ defaultTestTasks(testOnly) ++ defaultTestTasks(testQuick) ++ Seq(
|
||||
excludeFilter :== HiddenFileFilter
|
||||
) ++ globalIvyCore ++ globalJvmCore
|
||||
excludeFilter :== HiddenFileFilter,
|
||||
classLoaderCache := ClassLoaderCache(4),
|
||||
) ++ TaskRepository
|
||||
.proxy(GlobalScope / classLoaderCache, ClassLoaderCache(4)) ++ globalIvyCore ++ globalJvmCore
|
||||
) ++ globalSbtCore
|
||||
|
||||
private[sbt] lazy val globalJvmCore: Seq[Setting[_]] =
|
||||
|
|
@ -790,11 +792,7 @@ object Defaults extends BuildCommon {
|
|||
: Seq[Setting[_]] = testTaskOptions(test) ++ testTaskOptions(testOnly) ++ testTaskOptions(
|
||||
testQuick
|
||||
) ++ testDefaults ++ Seq(
|
||||
testLoader := TestFramework.createTestLoader(
|
||||
data(fullClasspath.value),
|
||||
scalaInstance.value,
|
||||
IO.createUniqueDirectory(taskTemporaryDirectory.value)
|
||||
),
|
||||
testLoader := ClassLoaders.testTask.value,
|
||||
loadedTestFrameworks := {
|
||||
val loader = testLoader.value
|
||||
val log = streams.value.log
|
||||
|
|
@ -813,7 +811,8 @@ object Defaults extends BuildCommon {
|
|||
(testExecution in test).value,
|
||||
(fullClasspath in test).value,
|
||||
testForkedParallel.value,
|
||||
(javaOptions in test).value
|
||||
(javaOptions in test).value,
|
||||
(classLoaderLayeringStrategy).value
|
||||
)
|
||||
}
|
||||
).value,
|
||||
|
|
@ -979,7 +978,8 @@ object Defaults extends BuildCommon {
|
|||
newConfig,
|
||||
fullClasspath.value,
|
||||
testForkedParallel.value,
|
||||
javaOptions.value
|
||||
javaOptions.value,
|
||||
classLoaderLayeringStrategy.value
|
||||
)
|
||||
val taskName = display.show(resolvedScoped.value)
|
||||
val trl = testResultLogger.value
|
||||
|
|
@ -1022,7 +1022,8 @@ object Defaults extends BuildCommon {
|
|||
config,
|
||||
cp,
|
||||
forkedParallelExecution = false,
|
||||
javaOptions = Nil
|
||||
javaOptions = Nil,
|
||||
strategy = ClassLoaderLayeringStrategy.TestDependencies,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -1043,7 +1044,8 @@ object Defaults extends BuildCommon {
|
|||
config,
|
||||
cp,
|
||||
forkedParallelExecution,
|
||||
javaOptions = Nil
|
||||
javaOptions = Nil,
|
||||
strategy = ClassLoaderLayeringStrategy.TestDependencies,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -1055,7 +1057,8 @@ object Defaults extends BuildCommon {
|
|||
config: Tests.Execution,
|
||||
cp: Classpath,
|
||||
forkedParallelExecution: Boolean,
|
||||
javaOptions: Seq[String]
|
||||
javaOptions: Seq[String],
|
||||
strategy: ClassLoaderLayeringStrategy,
|
||||
): Initialize[Task[Tests.Output]] = {
|
||||
val runners = createTestRunners(frameworks, loader, config)
|
||||
val groupTasks = groups map {
|
||||
|
|
@ -1083,6 +1086,25 @@ object Defaults extends BuildCommon {
|
|||
}
|
||||
val output = Tests.foldTasks(groupTasks, config.parallel)
|
||||
val result = output map { out =>
|
||||
out.events.foreach {
|
||||
case (suite, e) =>
|
||||
e.throwables
|
||||
.collectFirst {
|
||||
case t
|
||||
if t
|
||||
.isInstanceOf[NoClassDefFoundError] && strategy != ClassLoaderLayeringStrategy.Flat =>
|
||||
t
|
||||
}
|
||||
.foreach { t =>
|
||||
s.log.error(
|
||||
s"Test suite $suite failed with $t. This may be due to the ClassLoaderLayeringStrategy"
|
||||
+ s" ($strategy) used by your task. This issue may be resolved by changing the"
|
||||
+ " ClassLoaderLayeringStrategy in your configuration (generally Test or IntegrationTest),"
|
||||
+ "e.g.:\nTest / classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.Flat\n"
|
||||
+ "See ClassLoaderLayeringStrategy.scala for the full list of options."
|
||||
)
|
||||
}
|
||||
}
|
||||
val summaries =
|
||||
runners map {
|
||||
case (tf, r) =>
|
||||
|
|
@ -1384,37 +1406,11 @@ object Defaults extends BuildCommon {
|
|||
}
|
||||
}
|
||||
|
||||
@deprecated("This is no longer used internally by sbt.", "1.3.0")
|
||||
def runnerTask: Setting[Task[ScalaRun]] = runner := runnerInit.value
|
||||
|
||||
def runnerInit: Initialize[Task[ScalaRun]] = Def.task {
|
||||
val tmp = taskTemporaryDirectory.value
|
||||
val resolvedScope = resolvedScoped.value.scope
|
||||
val si = scalaInstance.value
|
||||
val s = streams.value
|
||||
val opts = forkOptions.value
|
||||
val options = javaOptions.value
|
||||
val trap = trapExit.value
|
||||
if (fork.value) {
|
||||
s.log.debug(s"javaOptions: $options")
|
||||
new ForkRun(opts)
|
||||
} else {
|
||||
if (options.nonEmpty) {
|
||||
val mask = ScopeMask(project = false)
|
||||
val showJavaOptions = Scope.displayMasked(
|
||||
(javaOptions in resolvedScope).scopedKey.scope,
|
||||
(javaOptions in resolvedScope).key.label,
|
||||
mask
|
||||
)
|
||||
val showFork = Scope.displayMasked(
|
||||
(fork in resolvedScope).scopedKey.scope,
|
||||
(fork in resolvedScope).key.label,
|
||||
mask
|
||||
)
|
||||
s.log.warn(s"$showJavaOptions will be ignored, $showFork is set to false")
|
||||
}
|
||||
new Run(si, trap, tmp)
|
||||
}
|
||||
}
|
||||
@deprecated("This is no longer used internally by sbt.", "1.3.0")
|
||||
def runnerInit: Initialize[Task[ScalaRun]] = ClassLoaders.runner
|
||||
|
||||
private def foreachJobTask(
|
||||
f: (BackgroundJobService, JobHandle) => Unit
|
||||
|
|
@ -1764,7 +1760,11 @@ object Defaults extends BuildCommon {
|
|||
|
||||
// 1. runnerSettings is added unscoped via JvmPlugin.
|
||||
// 2. In addition it's added scoped to run task.
|
||||
lazy val runnerSettings: Seq[Setting[_]] = Seq(runnerTask, forkOptions := forkOptionsTask.value)
|
||||
lazy val runnerSettings: Seq[Setting[_]] = {
|
||||
val unscoped: Seq[Def.Setting[_]] =
|
||||
Seq(runner := ClassLoaders.runner.value, forkOptions := forkOptionsTask.value)
|
||||
inConfig(Compile)(unscoped) ++ inConfig(Test)(unscoped)
|
||||
}
|
||||
|
||||
lazy val baseTasks: Seq[Setting[_]] = projectTasks ++ packageBase
|
||||
|
||||
|
|
@ -1772,16 +1772,32 @@ object Defaults extends BuildCommon {
|
|||
Classpaths.configSettings ++ configTasks ++ configPaths ++ packageConfig ++
|
||||
Classpaths.compilerPluginConfig ++ deprecationSettings
|
||||
|
||||
private val runtimeLayeringSettings: Seq[Setting[_]] = TaskRepository.proxy(
|
||||
classLoaderCache,
|
||||
// We need a cache of size four so that the subset of the runtime dependencies that are used
|
||||
// by the test task layers may be cached without evicting the runtime classloader layers. The
|
||||
// cache size should be a multiple of two to support snapshot layers.
|
||||
ClassLoaderCache(4)
|
||||
) :+ (classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.RuntimeDependencies)
|
||||
|
||||
lazy val compileSettings: Seq[Setting[_]] =
|
||||
configSettings ++
|
||||
(mainBgRunMainTask +: mainBgRunTask +: FileManagement.appendBaseSources) ++
|
||||
Classpaths.addUnmanagedLibrary
|
||||
Classpaths.addUnmanagedLibrary ++ runtimeLayeringSettings
|
||||
|
||||
private val testLayeringSettings: Seq[Setting[_]] = TaskRepository.proxy(
|
||||
classLoaderCache,
|
||||
// We need a cache of size two for the test dependency layers (regular and snapshot).
|
||||
ClassLoaderCache(2)
|
||||
) :+ (classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.TestDependencies)
|
||||
lazy val testSettings: Seq[Setting[_]] = configSettings ++ testTasks
|
||||
|
||||
lazy val itSettings: Seq[Setting[_]] = inConfig(IntegrationTest)(testSettings)
|
||||
lazy val itSettings: Seq[Setting[_]] = inConfig(IntegrationTest) {
|
||||
testSettings :+ (classLoaderLayeringStrategy := ClassLoaderLayeringStrategy.RuntimeDependencies)
|
||||
}
|
||||
lazy val defaultConfigs: Seq[Setting[_]] = inConfig(Compile)(compileSettings) ++
|
||||
inConfig(Test)(testSettings) ++ inConfig(Runtime)(Classpaths.configSettings)
|
||||
inConfig(Test)(testSettings ++ testLayeringSettings) ++
|
||||
inConfig(Runtime)(Classpaths.configSettings)
|
||||
|
||||
// These are project level settings that MUST be on every project.
|
||||
lazy val coreDefaultSettings: Seq[Setting[_]] =
|
||||
|
|
@ -3556,17 +3572,19 @@ trait BuildExtra extends BuildCommon with DefExtra {
|
|||
InputTask.apply(Def.value((s: State) => Def.spaceDelimited()))(f)
|
||||
|
||||
Vector(
|
||||
scoped := (inputTask { result =>
|
||||
(initScoped(scoped.scopedKey, runnerInit)
|
||||
.zipWith(Def.task { ((fullClasspath in config).value, streams.value, result.value) })) {
|
||||
scoped := inputTask { result =>
|
||||
initScoped(
|
||||
scoped.scopedKey,
|
||||
ClassLoaders.runner mapReferenced Project.mapScope(s => s.in(config))
|
||||
).zipWith(Def.task { ((fullClasspath in config).value, streams.value, result.value) }) {
|
||||
(rTask, t) =>
|
||||
(t, rTask) map {
|
||||
case ((cp, s, args), r) =>
|
||||
r.run(mainClass, data(cp), baseArguments ++ args, s.log).get
|
||||
}
|
||||
}
|
||||
}).evaluated
|
||||
) ++ inTask(scoped)(forkOptions := forkOptionsTask.value)
|
||||
}.evaluated
|
||||
) ++ inTask(scoped)(forkOptions in config := forkOptionsTask.value)
|
||||
}
|
||||
|
||||
// public API
|
||||
|
|
@ -3578,16 +3596,18 @@ trait BuildExtra extends BuildCommon with DefExtra {
|
|||
arguments: String*
|
||||
): Vector[Setting[_]] =
|
||||
Vector(
|
||||
scoped := ((initScoped(scoped.scopedKey, runnerInit)
|
||||
.zipWith(Def.task { ((fullClasspath in config).value, streams.value) })) {
|
||||
scoped := initScoped(
|
||||
scoped.scopedKey,
|
||||
ClassLoaders.runner mapReferenced Project.mapScope(s => s.in(config))
|
||||
).zipWith(Def.task { ((fullClasspath in config).value, streams.value) }) {
|
||||
case (rTask, t) =>
|
||||
(t, rTask) map {
|
||||
case ((cp, s), r) =>
|
||||
r.run(mainClass, data(cp), arguments, s.log).get
|
||||
}
|
||||
})
|
||||
}
|
||||
.value
|
||||
) ++ inTask(scoped)(forkOptions := forkOptionsTask.value)
|
||||
) ++ inTask(scoped)(forkOptions in config := forkOptionsTask.value)
|
||||
|
||||
def initScoped[T](sk: ScopedKey[_], i: Initialize[T]): Initialize[T] =
|
||||
initScope(fillTaskAxis(sk.scope, sk.key), i)
|
||||
|
|
|
|||
|
|
@ -264,6 +264,7 @@ object Keys {
|
|||
val bgRunMain = inputKey[JobHandle]("Start a provided main class as a background job")
|
||||
val fgRunMain = inputKey[Unit]("Start a provided main class as a foreground job")
|
||||
val bgCopyClasspath = settingKey[Boolean]("Copies classpath on bgRun to prevent conflict.")
|
||||
val classLoaderLayeringStrategy = settingKey[ClassLoaderLayeringStrategy]("Creates the classloader layering strategy for the particular configuration.")
|
||||
|
||||
// Test Keys
|
||||
val testLoader = taskKey[ClassLoader]("Provides the class loader used for testing.").withRank(DTask)
|
||||
|
|
@ -460,6 +461,8 @@ object Keys {
|
|||
val resolvedScoped = Def.resolvedScoped
|
||||
val pluginData = taskKey[PluginData]("Information from the plugin build needed in the main build definition.").withRank(DTask)
|
||||
val globalPluginUpdate = taskKey[UpdateReport]("A hook to get the UpdateReport of the global plugin.").withRank(DTask)
|
||||
val classLoaderCache = taskKey[internal.ClassLoaderCache]("The cache of ClassLoaders to be used for layering in tasks that invoke other java code").withRank(DTask)
|
||||
private[sbt] val taskRepository = AttributeKey[TaskRepository.Repr]("task-repository", "A repository that can be used to cache arbitrary values for a given task key that can be read or filled during task evaluation.", 10000)
|
||||
|
||||
// wrapper to work around SI-2915
|
||||
private[sbt] final class TaskProgress(val progress: ExecuteProgress[Task])
|
||||
|
|
|
|||
|
|
@ -214,7 +214,7 @@ object BuiltinCommands {
|
|||
BasicCommands.multi,
|
||||
act,
|
||||
continuous,
|
||||
flushFileTreeRepository
|
||||
clearCaches
|
||||
) ++ allBasicCommands
|
||||
|
||||
def DefaultBootCommands: Seq[String] =
|
||||
|
|
@ -830,7 +830,7 @@ object BuiltinCommands {
|
|||
|
||||
val session = Load.initialSession(structure, eval, s0)
|
||||
SessionSettings.checkSession(session, s)
|
||||
registerGlobalFileRepository(Project.setProject(session, structure, s))
|
||||
registerGlobalCaches(Project.setProject(session, structure, s))
|
||||
}
|
||||
|
||||
def registerCompilerCache(s: State): State = {
|
||||
|
|
@ -848,26 +848,31 @@ object BuiltinCommands {
|
|||
}
|
||||
s.put(Keys.stateCompilerCache, cache)
|
||||
}
|
||||
def registerGlobalFileRepository(s: State): State = {
|
||||
private[sbt] def registerGlobalCaches(s: State): State = {
|
||||
val extracted = Project.extract(s)
|
||||
try {
|
||||
val (_, config: FileTreeViewConfig) = extracted.runTask(Keys.fileTreeViewConfig, s)
|
||||
val view: FileTreeDataView[StampedFile] = config.newDataView()
|
||||
val newState = s.addExitHook {
|
||||
def cleanup(): Unit = {
|
||||
s.get(BasicKeys.globalFileTreeView).foreach(_.close())
|
||||
s.attributes.remove(BasicKeys.globalFileTreeView)
|
||||
s.get(Keys.taskRepository).foreach(_.close())
|
||||
s.attributes.remove(Keys.taskRepository)
|
||||
()
|
||||
}
|
||||
newState.get(BasicKeys.globalFileTreeView).foreach(_.close())
|
||||
newState.put(BasicKeys.globalFileTreeView, view)
|
||||
val (_, config: FileTreeViewConfig) = extracted.runTask(Keys.fileTreeViewConfig, s)
|
||||
val view: FileTreeDataView[StampedFile] = config.newDataView()
|
||||
val newState = s.addExitHook(cleanup())
|
||||
cleanup()
|
||||
newState
|
||||
.put(BasicKeys.globalFileTreeView, view)
|
||||
.put(Keys.taskRepository, new TaskRepository.Repr)
|
||||
} catch {
|
||||
case NonFatal(_) => s
|
||||
}
|
||||
}
|
||||
|
||||
def flushFileTreeRepository: Command = {
|
||||
val help = Help.more(FlushFileTreeRepository, FlushDetailed)
|
||||
Command.command(FlushFileTreeRepository, help)(registerGlobalFileRepository)
|
||||
def clearCaches: Command = {
|
||||
val help = Help.more(ClearCaches, ClearCachesDetailed)
|
||||
Command.command(ClearCaches, help)(registerGlobalCaches)
|
||||
}
|
||||
|
||||
def shell: Command = Command.command(Shell, Help.more(Shell, ShellDetailed)) { s0 =>
|
||||
|
|
|
|||
|
|
@ -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 scripted = remaining.contains("setUpScripted")
|
||||
|
||||
val continuous = remaining.exists(_.startsWith(ContinuousExecutePrefix))
|
||||
val continuous = remaining.lastOption.exists(_.startsWith(ContinuousExecutePrefix))
|
||||
if (!scripted && (interactive || continuous)) {
|
||||
FileTreeViewConfig
|
||||
.default(watchAntiEntropy.value, pollInterval.value, pollingDirectories.value)
|
||||
|
|
|
|||
|
|
@ -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]) =
|
||||
"-classpath" :: Path.makeString(classpath) :: Nil
|
||||
}
|
||||
class Run(instance: ScalaInstance, trapExit: Boolean, nativeTmp: File) extends ScalaRun {
|
||||
class Run(newLoader: Seq[File] => ClassLoader, trapExit: Boolean) extends ScalaRun {
|
||||
def this(instance: ScalaInstance, trapExit: Boolean, nativeTmp: File) =
|
||||
this((cp: Seq[File]) => ClasspathUtilities.makeLoader(cp, instance, nativeTmp), trapExit)
|
||||
|
||||
/** Runs the class 'mainClass' using the given classpath and options using the scala runner.*/
|
||||
def run(mainClass: String, classpath: Seq[File], options: Seq[String], log: Logger): Try[Unit] = {
|
||||
|
|
@ -87,7 +89,7 @@ class Run(instance: ScalaInstance, trapExit: Boolean, nativeTmp: File) extends S
|
|||
log: Logger
|
||||
): Unit = {
|
||||
log.debug(" Classpath:\n\t" + classpath.mkString("\n\t"))
|
||||
val loader = ClasspathUtilities.makeLoader(classpath, instance, nativeTmp)
|
||||
val loader = newLoader(classpath)
|
||||
val main = getMainMethod(mainClassName, loader)
|
||||
invokeMain(loader, main, options)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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