Add dependency classloader to the evaluation classloader:

allows to use dependencies from classloader in the scenario with
evaluation in the isolated environment
This commit is contained in:
Ilya Chernikov
2019-09-11 16:07:05 +02:00
parent 61d517fb31
commit 5426071102
3 changed files with 36 additions and 20 deletions
@@ -8,13 +8,12 @@ package kotlin.script.experimental.jvmhost.test
import junit.framework.TestCase
import org.junit.Test
import java.io.File
import kotlin.script.experimental.api.ResultValue
import kotlin.script.experimental.api.ScriptCompilationConfiguration
import kotlin.script.experimental.api.dependencies
import kotlin.script.experimental.api.valueOrThrow
import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.toScriptSource
import kotlin.script.experimental.jvm.JvmDependency
import kotlin.script.experimental.jvm.JvmDependencyFromClassLoader
import kotlin.script.experimental.jvm.baseClassLoader
import kotlin.script.experimental.jvm.jvm
import kotlin.script.experimental.jvmhost.BasicJvmScriptingHost
class ResolveFromClassloaderTest : TestCase() {
@@ -29,6 +28,25 @@ class ResolveFromClassloaderTest : TestCase() {
assertEquals(42, res.value)
}
@Test
fun testResolveFromClassloaderIsolated() {
val script = "${ShouldBeVisibleFromScript::class.java.name}().x".toScriptSource()
val compilationConfiguration = ScriptCompilationConfiguration {
dependencies(JvmDependencyFromClassLoader { ShouldBeVisibleFromScript::class.java.classLoader })
}
val evaluationConfiguration = ScriptEvaluationConfiguration {
jvm {
baseClassLoader(null)
}
}
val res = BasicJvmScriptingHost().eval(script, compilationConfiguration, evaluationConfiguration).valueOrThrow().returnValue
when (res) {
is ResultValue.Value -> assertEquals(42, res.value)
is ResultValue.Error -> throw res.error
else -> throw Exception("Unexpected evaluation result: $res")
}
}
@Test
fun testResolveFromClassloaderAndClassPath() {
val script = """
@@ -28,7 +28,7 @@ class KJvmCompiledModuleFromClassLoader(val moduleClassLoader: ClassLoader) : KJ
else DualClassLoader(moduleClassLoader, baseClassLoader)
}
private class DualClassLoader(fallbackLoader: ClassLoader, parentLoader: ClassLoader?) :
internal class DualClassLoader(fallbackLoader: ClassLoader, parentLoader: ClassLoader?) :
ClassLoader(singleClassLoader(fallbackLoader, parentLoader) ?: parentLoader) {
private class Wrapper(parent: ClassLoader) : ClassLoader(parent) {
@@ -141,23 +141,21 @@ fun getConfigurationWithClassloader(
private fun CompiledScript<*>.makeClassLoaderFromDependencies(baseClassLoader: ClassLoader?): ClassLoader? {
val processedScripts = mutableSetOf<CompiledScript<*>>()
fun seq(res: Sequence<CompiledScript<*>>, script: CompiledScript<*>): Sequence<CompiledScript<*>> {
if (processedScripts.contains(script)) return res
processedScripts.add(script)
return script.otherScripts.asSequence().fold(res + script, ::seq)
}
fun recursiveScriptsSeq(res: Sequence<CompiledScript<*>>, script: CompiledScript<*>): Sequence<CompiledScript<*>> =
if (processedScripts.add(script)) script.otherScripts.asSequence().fold(res + script, ::recursiveScriptsSeq)
else res
val dependencies = seq(emptySequence(), this).flatMap { script ->
val dependenciesWithConfigurations = recursiveScriptsSeq(emptySequence(), this).flatMap { script ->
script.compilationConfiguration[ScriptCompilationConfiguration.dependencies]
?.asSequence()
?.flatMap { dep ->
(dep as? JvmDependency)?.classpath?.asSequence()?.map { it.toURI().toURL() } ?: emptySequence()
}
?: emptySequence()
}.distinct().toList()
// TODO: previous dependencies and classloaders should be taken into account here
return if (dependencies.isEmpty()) baseClassLoader
else URLClassLoader(dependencies.toTypedArray(), baseClassLoader)
?.asSequence()?.map { script.compilationConfiguration to it } ?: emptySequence()
}
return dependenciesWithConfigurations.fold(baseClassLoader) { parentClassLoader, (compilationConfiguration, scriptDependency) ->
when (scriptDependency) {
is JvmDependency -> URLClassLoader(scriptDependency.classpath.map { it.toURI().toURL() }.toTypedArray(), parentClassLoader)
is JvmDependencyFromClassLoader -> DualClassLoader(scriptDependency.getClassLoader(compilationConfiguration), baseClassLoader)
else -> baseClassLoader
}
}
}
const val KOTLIN_SCRIPT_METADATA_PATH = "META-INF/kotlin/script"