Introduce GetScriptingClassByClassLoader interface
It is needed to override default JVM behaviour
This commit is contained in:
committed by
Ilya Chernikov
parent
65ce7cd0c2
commit
89bba93615
+148
@@ -0,0 +1,148 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package kotlin.script.experimental.jvmhost.test
|
||||||
|
|
||||||
|
import junit.framework.TestCase
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import java.io.BufferedOutputStream
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
import kotlin.script.experimental.api.*
|
||||||
|
import kotlin.script.experimental.host.ScriptingHostConfiguration
|
||||||
|
import kotlin.script.experimental.host.getScriptingClass
|
||||||
|
import kotlin.script.experimental.host.with
|
||||||
|
import kotlin.script.experimental.jvm.*
|
||||||
|
import kotlin.script.experimental.jvm.impl.KJvmCompiledModuleInMemory
|
||||||
|
import kotlin.script.experimental.jvm.impl.KJvmCompiledScript
|
||||||
|
import kotlin.script.experimental.jvmhost.JvmScriptCompiler
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This test shows an ability of using KClasses loaded with classloaders
|
||||||
|
* other than default one, in the role of implicit receivers. For this reason,
|
||||||
|
* specific [GetScriptingClassByClassLoader] was implemented. Actually,
|
||||||
|
* as we use previously compiled snippets as implicits, we could achieve the same
|
||||||
|
* thing if we change the used compiler to one of the REPL ones. But if we are limited
|
||||||
|
* in our choice of compiler and only can tune the configuration, this is the only way.
|
||||||
|
*
|
||||||
|
* This test may be deleted or at least simplified when the option
|
||||||
|
* in [ScriptCompilationConfiguration] for saving previous classes in
|
||||||
|
* underlying module will be introduced.
|
||||||
|
*/
|
||||||
|
class ImplicitsFromScriptResultTest : TestCase() {
|
||||||
|
fun testImplicits() {
|
||||||
|
val host = CompilerHost()
|
||||||
|
|
||||||
|
val snippets = listOf(
|
||||||
|
"val xyz0 = 42",
|
||||||
|
"fun f() = xyz0",
|
||||||
|
"val finalRes = xyz0 + f()",
|
||||||
|
)
|
||||||
|
for (snippet in snippets) {
|
||||||
|
val res = host.compile(snippet)
|
||||||
|
assertTrue(res is ResultWithDiagnostics.Success)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun interface PreviousScriptClassesProvider {
|
||||||
|
fun get(): List<KClass<*>>
|
||||||
|
}
|
||||||
|
|
||||||
|
class GetScriptClassForImplicits(
|
||||||
|
private val previousScriptClassesProvider: PreviousScriptClassesProvider
|
||||||
|
) : GetScriptingClassByClassLoader {
|
||||||
|
private val getScriptingClass = JvmGetScriptingClass()
|
||||||
|
|
||||||
|
private val lastClassLoader
|
||||||
|
get() = previousScriptClassesProvider.get().lastOrNull()?.java?.classLoader
|
||||||
|
|
||||||
|
override fun invoke(
|
||||||
|
classType: KotlinType,
|
||||||
|
contextClass: KClass<*>,
|
||||||
|
hostConfiguration: ScriptingHostConfiguration
|
||||||
|
): KClass<*> {
|
||||||
|
return getScriptingClass(classType, lastClassLoader ?: contextClass.java.classLoader, hostConfiguration)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun invoke(
|
||||||
|
classType: KotlinType,
|
||||||
|
contextClassLoader: ClassLoader?,
|
||||||
|
hostConfiguration: ScriptingHostConfiguration
|
||||||
|
): KClass<*> {
|
||||||
|
return getScriptingClass(classType, lastClassLoader ?: contextClassLoader, hostConfiguration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CompilerHost {
|
||||||
|
private var counter = 0
|
||||||
|
private val implicits = mutableListOf<KClass<*>>()
|
||||||
|
private val outputDir: Path = Files.createTempDirectory("kotlin-scripting-jvm")
|
||||||
|
private val classWriter = ClassWriter(outputDir)
|
||||||
|
|
||||||
|
init {
|
||||||
|
outputDir.toFile().deleteOnExit()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val myHostConfiguration = defaultJvmScriptingHostConfiguration.with {
|
||||||
|
getScriptingClass(GetScriptClassForImplicits(::getImplicitsClasses))
|
||||||
|
}
|
||||||
|
|
||||||
|
private val compileConfiguration = ScriptCompilationConfiguration {
|
||||||
|
hostConfiguration(myHostConfiguration)
|
||||||
|
|
||||||
|
jvm {
|
||||||
|
dependencies(JvmDependency(outputDir.toFile()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val evaluationConfiguration = ScriptEvaluationConfiguration()
|
||||||
|
|
||||||
|
private val compiler = JvmScriptCompiler(myHostConfiguration)
|
||||||
|
|
||||||
|
private fun getImplicitsClasses(): List<KClass<*>> = implicits
|
||||||
|
|
||||||
|
fun compile(code: String): ResultWithDiagnostics<CompiledScript> {
|
||||||
|
val source = SourceCodeTestImpl(counter++, code)
|
||||||
|
val refinedConfig = compileConfiguration.with {
|
||||||
|
implicitReceivers(*implicits.toTypedArray())
|
||||||
|
}
|
||||||
|
val result = runBlocking { compiler.invoke(source, refinedConfig) }
|
||||||
|
val compiledScript = result.valueOrThrow() as KJvmCompiledScript
|
||||||
|
|
||||||
|
classWriter.writeCompiledSnippet(compiledScript)
|
||||||
|
|
||||||
|
val kClass = runBlocking { compiledScript.getClass(evaluationConfiguration) }.valueOrThrow()
|
||||||
|
implicits.add(kClass)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SourceCodeTestImpl(number: Int, override val text: String) : SourceCode {
|
||||||
|
override val name: String = "Line_$number"
|
||||||
|
override val locationId: String = "location_$number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ClassWriter(private val outputDir: Path) {
|
||||||
|
fun writeCompiledSnippet(snippet: KJvmCompiledScript) {
|
||||||
|
val moduleInMemory = snippet.getCompiledModule() as KJvmCompiledModuleInMemory
|
||||||
|
moduleInMemory.compilerOutputFiles.forEach { (name, bytes) ->
|
||||||
|
if (name.endsWith(".class")) {
|
||||||
|
writeClass(bytes, outputDir.resolve(name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun writeClass(classBytes: ByteArray, path: Path) {
|
||||||
|
FileOutputStream(path.toAbsolutePath().toString()).use { fos ->
|
||||||
|
BufferedOutputStream(fos).use { out ->
|
||||||
|
out.write(classBytes)
|
||||||
|
out.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
-2
@@ -45,7 +45,11 @@ val defaultJvmScriptingHostConfiguration
|
|||||||
getScriptingClass(JvmGetScriptingClass())
|
getScriptingClass(JvmGetScriptingClass())
|
||||||
}
|
}
|
||||||
|
|
||||||
class JvmGetScriptingClass : GetScriptingClass, Serializable {
|
interface GetScriptingClassByClassLoader : GetScriptingClass {
|
||||||
|
operator fun invoke(classType: KotlinType, contextClassLoader: ClassLoader?, hostConfiguration: ScriptingHostConfiguration): KClass<*>
|
||||||
|
}
|
||||||
|
|
||||||
|
class JvmGetScriptingClass : GetScriptingClassByClassLoader, Serializable {
|
||||||
|
|
||||||
@Transient
|
@Transient
|
||||||
private var dependencies: List<ScriptDependency>? = null
|
private var dependencies: List<ScriptDependency>? = null
|
||||||
@@ -64,7 +68,11 @@ class JvmGetScriptingClass : GetScriptingClass, Serializable {
|
|||||||
invoke(classType, contextClass.java.classLoader, hostConfiguration)
|
invoke(classType, contextClass.java.classLoader, hostConfiguration)
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
operator fun invoke(classType: KotlinType, contextClassLoader: ClassLoader?, hostConfiguration: ScriptingHostConfiguration): KClass<*> {
|
override operator fun invoke(
|
||||||
|
classType: KotlinType,
|
||||||
|
contextClassLoader: ClassLoader?,
|
||||||
|
hostConfiguration: ScriptingHostConfiguration
|
||||||
|
): KClass<*> {
|
||||||
|
|
||||||
// checking if class already loaded in the same context
|
// checking if class already loaded in the same context
|
||||||
val fromClass = classType.fromClass
|
val fromClass = classType.fromClass
|
||||||
|
|||||||
+2
-2
@@ -352,8 +352,8 @@ fun getScriptCollectedData(
|
|||||||
val hostConfiguration =
|
val hostConfiguration =
|
||||||
compilationConfiguration[ScriptCompilationConfiguration.hostConfiguration] ?: defaultJvmScriptingHostConfiguration
|
compilationConfiguration[ScriptCompilationConfiguration.hostConfiguration] ?: defaultJvmScriptingHostConfiguration
|
||||||
val getScriptingClass = hostConfiguration[ScriptingHostConfiguration.getScriptingClass]
|
val getScriptingClass = hostConfiguration[ScriptingHostConfiguration.getScriptingClass]
|
||||||
val jvmGetScriptingClass = (getScriptingClass as? JvmGetScriptingClass)
|
val jvmGetScriptingClass = (getScriptingClass as? GetScriptingClassByClassLoader)
|
||||||
?: throw IllegalArgumentException("Expecting JvmGetScriptingClass in the hostConfiguration[getScriptingClass], got $getScriptingClass")
|
?: throw IllegalArgumentException("Expecting class implementing GetScriptingClassByClassLoader in the hostConfiguration[getScriptingClass], got $getScriptingClass")
|
||||||
val acceptedAnnotations =
|
val acceptedAnnotations =
|
||||||
compilationConfiguration[ScriptCompilationConfiguration.refineConfigurationOnAnnotations]?.flatMap {
|
compilationConfiguration[ScriptCompilationConfiguration.refineConfigurationOnAnnotations]?.flatMap {
|
||||||
it.annotations.mapNotNull { ann ->
|
it.annotations.mapNotNull { ann ->
|
||||||
|
|||||||
Reference in New Issue
Block a user