From c896b4873bb1ba65ff5d807b2ecc4509a2eaac26 Mon Sep 17 00:00:00 2001 From: Ilya Chernikov Date: Fri, 5 Apr 2019 15:24:36 +0200 Subject: [PATCH] Rearrange scripting host classes, so the runner part is independent from the compiler --- .../jvmhost/BasicJvmScriptingHost.kt | 1 + .../impl/KJvmCompiledModuleInMemory.kt | 21 +++ .../jvmhost/impl/KJvmCompiledScript.kt | 76 --------- .../jvmhost/impl/KJvmCompilerImpl.kt | 1 + .../jvmhost/jvmScriptEvaluation.kt | 159 ------------------ .../experimental/jvmhost/jvmScriptSaving.kt | 2 +- .../jvmhost/obsoleteJvmScriptEvaluation.kt | 32 ++++ .../jvmhost/test/ScriptingHostTest.kt | 3 +- .../jvm/BasicJvmScriptEvaluator.kt | 91 ++++++++++ .../jvm}/impl/KJvmCompiledModule.kt | 14 +- .../jvm/impl/KJvmCompiledScript.kt | 129 ++++++++++++++ .../experimental/jvm/jvmScriptEvaluation.kt | 36 ++++ 12 files changed, 315 insertions(+), 250 deletions(-) create mode 100644 libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/KJvmCompiledModuleInMemory.kt delete mode 100644 libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/KJvmCompiledScript.kt delete mode 100644 libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/jvmScriptEvaluation.kt create mode 100644 libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/obsoleteJvmScriptEvaluation.kt create mode 100644 libraries/scripting/jvm/src/kotlin/script/experimental/jvm/BasicJvmScriptEvaluator.kt rename libraries/scripting/{jvm-host/src/kotlin/script/experimental/jvmhost => jvm/src/kotlin/script/experimental/jvm}/impl/KJvmCompiledModule.kt (65%) create mode 100644 libraries/scripting/jvm/src/kotlin/script/experimental/jvm/impl/KJvmCompiledScript.kt create mode 100644 libraries/scripting/jvm/src/kotlin/script/experimental/jvm/jvmScriptEvaluation.kt diff --git a/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/BasicJvmScriptingHost.kt b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/BasicJvmScriptingHost.kt index 16ab08ad192..40cfad86898 100644 --- a/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/BasicJvmScriptingHost.kt +++ b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/BasicJvmScriptingHost.kt @@ -10,6 +10,7 @@ import kotlin.script.experimental.host.ScriptingHostConfiguration import kotlin.script.experimental.host.createCompilationConfigurationFromTemplate import kotlin.script.experimental.host.BasicScriptingHost import kotlin.script.experimental.host.createEvaluationConfigurationFromTemplate +import kotlin.script.experimental.jvm.BasicJvmScriptEvaluator import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration open class BasicJvmScriptingHost( diff --git a/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/KJvmCompiledModuleInMemory.kt b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/KJvmCompiledModuleInMemory.kt new file mode 100644 index 00000000000..486e55bb292 --- /dev/null +++ b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/KJvmCompiledModuleInMemory.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. 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.impl + +import java.io.Serializable +import kotlin.script.experimental.jvm.impl.KJvmCompiledModule + +class KJvmCompiledModuleInMemory(val compilerOutputFiles: Map) : KJvmCompiledModule, + Serializable { + + override fun createClassLoader(baseClassLoader: ClassLoader?): ClassLoader = + CompiledScriptClassLoader(baseClassLoader, compilerOutputFiles) + + companion object { + @JvmStatic + private val serialVersionUID = 0L + } +} \ No newline at end of file diff --git a/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/KJvmCompiledScript.kt b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/KJvmCompiledScript.kt deleted file mode 100644 index 469cd639e6c..00000000000 --- a/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/KJvmCompiledScript.kt +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2010-2018 JetBrains s.r.o. 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.impl - -import java.io.ObjectInputStream -import java.io.ObjectOutputStream -import java.io.Serializable -import kotlin.reflect.KClass -import kotlin.script.experimental.api.* -import kotlin.script.experimental.jvmhost.getOrCreateActualClassloader - -class KJvmCompiledScript( - sourceLocationId: String?, - compilationConfiguration: ScriptCompilationConfiguration, - private var scriptClassFQName: String, - otherScripts: List> = emptyList(), - internal var compiledModule: KJvmCompiledModule -) : CompiledScript, Serializable { - - private var _sourceLocationId: String? = sourceLocationId - - override val sourceLocationId: String? - get() = _sourceLocationId - - private var _compilationConfiguration: ScriptCompilationConfiguration? = compilationConfiguration - - override val compilationConfiguration: ScriptCompilationConfiguration - get() = _compilationConfiguration!! - - private var _otherScripts: List> = otherScripts - - override val otherScripts: List> - get() = _otherScripts - - override suspend fun getClass(scriptEvaluationConfiguration: ScriptEvaluationConfiguration?): ResultWithDiagnostics> = try { - // ensuring proper defaults are used - val actualEvaluationConfiguration = scriptEvaluationConfiguration ?: ScriptEvaluationConfiguration() - val classLoader = getOrCreateActualClassloader(actualEvaluationConfiguration) - - val clazz = classLoader.loadClass(scriptClassFQName).kotlin - clazz.asSuccess() - } catch (e: Throwable) { - ResultWithDiagnostics.Failure( - ScriptDiagnostic( - "Unable to instantiate class $scriptClassFQName", - sourcePath = sourceLocationId, - exception = e - ) - ) - } - - private fun writeObject(outputStream: ObjectOutputStream) { - outputStream.writeObject(compilationConfiguration) - outputStream.writeObject(sourceLocationId) - outputStream.writeObject(otherScripts) - outputStream.writeObject(compiledModule) - outputStream.writeObject(scriptClassFQName) - } - - @Suppress("UNCHECKED_CAST") - private fun readObject(inputStream: ObjectInputStream) { - _compilationConfiguration = inputStream.readObject() as ScriptCompilationConfiguration - _sourceLocationId = inputStream.readObject() as String? - _otherScripts = inputStream.readObject() as List> - compiledModule = inputStream.readObject() as KJvmCompiledModuleInMemory - scriptClassFQName = inputStream.readObject() as String - } - - companion object { - @JvmStatic - private val serialVersionUID = 2L - } -} \ No newline at end of file diff --git a/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/KJvmCompilerImpl.kt b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/KJvmCompilerImpl.kt index 051a3fb703f..34d46af5802 100644 --- a/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/KJvmCompilerImpl.kt +++ b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/KJvmCompilerImpl.kt @@ -62,6 +62,7 @@ import kotlin.script.experimental.host.getMergedScriptText import kotlin.script.experimental.host.getScriptingClass import kotlin.script.experimental.jvm.JvmDependency import kotlin.script.experimental.jvm.impl.BridgeDependenciesResolver +import kotlin.script.experimental.jvm.impl.KJvmCompiledScript import kotlin.script.experimental.jvm.jdkHome import kotlin.script.experimental.jvm.jvm import kotlin.script.experimental.jvm.util.KotlinJars diff --git a/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/jvmScriptEvaluation.kt b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/jvmScriptEvaluation.kt deleted file mode 100644 index 76802cce22d..00000000000 --- a/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/jvmScriptEvaluation.kt +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license - * that can be found in the license/LICENSE.txt file. - */ - -@file:Suppress("unused") - -package kotlin.script.experimental.jvmhost - -import java.net.URLClassLoader -import kotlin.reflect.KClass -import kotlin.script.experimental.api.* -import kotlin.script.experimental.jvm.JvmDependency -import kotlin.script.experimental.jvmhost.impl.KJvmCompiledScript -import kotlin.script.experimental.util.PropertiesCollection - -interface JvmScriptEvaluationConfigurationKeys - -open class JvmScriptEvaluationConfigurationBuilder : PropertiesCollection.Builder(), JvmScriptEvaluationConfigurationKeys { - - companion object : JvmScriptEvaluationConfigurationBuilder() -} - -/** - * The base classloader to use for script classes loading - */ -val JvmScriptEvaluationConfigurationKeys.baseClassLoader by PropertiesCollection.key(Thread.currentThread().contextClassLoader) - -/** - * Load script dependencies before evaluation, true by default - * If false, it is assumed that the all dependencies will be provided via baseClassLoader - */ -val JvmScriptEvaluationConfigurationKeys.loadDependencies by PropertiesCollection.key(true) - -internal val JvmScriptEvaluationConfigurationKeys.actualClassLoader by PropertiesCollection.key() - -internal val JvmScriptEvaluationConfigurationKeys.scriptsInstancesSharingMap by PropertiesCollection.key, EvaluationResult>>() - -val ScriptEvaluationConfigurationKeys.jvm get() = JvmScriptEvaluationConfigurationBuilder() - -class JvmScriptEvaluationContext( - val classLoader: ClassLoader, - val scriptsInstancesSharingMap: MutableMap, EvaluationResult>? -) - -open class BasicJvmScriptEvaluator : ScriptEvaluator { - - override suspend operator fun invoke( - compiledScript: CompiledScript<*>, - scriptEvaluationConfiguration: ScriptEvaluationConfiguration - ): ResultWithDiagnostics = try { - val configuration = getConfigurationWithClassloader(compiledScript, scriptEvaluationConfiguration) - - compiledScript.getClass(configuration).onSuccess { scriptClass -> - // in the future, when (if) we'll stop to compile everything into constructor - // run as SAM - // return res - - val sharedScripts = configuration[ScriptEvaluationConfiguration.jvm.scriptsInstancesSharingMap] - - sharedScripts?.get(scriptClass)?.asSuccess() - ?: compiledScript.otherScripts.mapSuccess { - invoke(it, configuration) - }.onSuccess { importedScriptsEvalResults -> - - val refinedEvalConfiguration = - configuration[ScriptEvaluationConfiguration.refineConfigurationBeforeEvaluate] - ?.handler?.invoke(ScriptEvaluationConfigurationRefinementContext(compiledScript, configuration)) - ?.onFailure { - return@invoke ResultWithDiagnostics.Failure(it.reports) - } - ?.resultOrNull() - ?: configuration - - scriptClass.evalWithConfigAndOtherScriptsResults(refinedEvalConfiguration, importedScriptsEvalResults).let { - sharedScripts?.put(scriptClass, it) - ResultWithDiagnostics.Success(it) - } - } - } - } catch (e: Throwable) { - ResultWithDiagnostics.Failure(e.asDiagnostics("Error evaluating script", path = compiledScript.sourceLocationId)) - } - - private fun getConfigurationWithClassloader( - script: CompiledScript<*>, baseConfiguration: ScriptEvaluationConfiguration - ): ScriptEvaluationConfiguration = - if (baseConfiguration.containsKey(ScriptEvaluationConfiguration.jvm.actualClassLoader)) - baseConfiguration - else { - val jvmScript = (script as? KJvmCompiledScript<*>) - ?: throw IllegalArgumentException("Unexpected compiled script type: $script") - - val classloader = jvmScript.getOrCreateActualClassloader(baseConfiguration) - - ScriptEvaluationConfiguration(baseConfiguration) { - ScriptEvaluationConfiguration.jvm.actualClassLoader(classloader) - if (baseConfiguration[ScriptEvaluationConfiguration.scriptsInstancesSharing] == true) { - ScriptEvaluationConfiguration.jvm.scriptsInstancesSharingMap(mutableMapOf()) - } - } - } - - private fun KClass<*>.evalWithConfigAndOtherScriptsResults( - refinedEvalConfiguration: ScriptEvaluationConfiguration, - importedScriptsEvalResults: List - ): EvaluationResult { - val args = ArrayList() - - refinedEvalConfiguration[ScriptEvaluationConfiguration.constructorArgs]?.let { - args.addAll(it) - } - refinedEvalConfiguration[ScriptEvaluationConfiguration.implicitReceivers]?.let { - args.addAll(it) - } - refinedEvalConfiguration[ScriptEvaluationConfiguration.providedProperties]?.forEach { - args.add(it.value) - } - - importedScriptsEvalResults.forEach { - args.add((it.returnValue as ResultValue.Value).scriptInstance) - } - - val ctor = java.constructors.single() - val instance = ctor.newInstance(*args.toArray()) - - return EvaluationResult(ResultValue.Value("", instance, "", instance), refinedEvalConfiguration) - } -} - -internal fun KJvmCompiledScript<*>.getOrCreateActualClassloader(evaluationConfiguration: ScriptEvaluationConfiguration): ClassLoader = - evaluationConfiguration[ScriptEvaluationConfiguration.jvm.actualClassLoader] ?: run { - val baseClassLoader = evaluationConfiguration[ScriptEvaluationConfiguration.jvm.baseClassLoader] - val classLoaderWithDeps = - if (evaluationConfiguration[ScriptEvaluationConfiguration.jvm.loadDependencies] == false) baseClassLoader - else makeClassLoaderFromDependencies(baseClassLoader) - compiledModule.createClassLoader(classLoaderWithDeps) - } - -private fun CompiledScript<*>.makeClassLoaderFromDependencies(baseClassLoader: ClassLoader?): ClassLoader? { - val processedScripts = mutableSetOf>() - fun seq(res: Sequence>, script: CompiledScript<*>): Sequence> { - if (processedScripts.contains(script)) return res - processedScripts.add(script) - return script.otherScripts.asSequence().fold(res + script, ::seq) - } - - val dependencies = seq(emptySequence(), this).flatMap { script -> - script.compilationConfiguration[ScriptCompilationConfiguration.dependencies] - ?.asSequence() - ?.flatMap { dep -> - (dep as? JvmDependency)?.classpath?.asSequence()?.map { it.toURI().toURL() } ?: emptySequence() - } - ?: emptySequence() - }.distinct() - // TODO: previous dependencies and classloaders should be taken into account here - return if (dependencies.none()) baseClassLoader - else URLClassLoader(dependencies.toList().toTypedArray(), baseClassLoader) -} diff --git a/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/jvmScriptSaving.kt b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/jvmScriptSaving.kt index 8260e5d313d..52527b7609b 100644 --- a/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/jvmScriptSaving.kt +++ b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/jvmScriptSaving.kt @@ -13,7 +13,7 @@ import java.util.jar.Manifest import kotlin.script.experimental.api.* import kotlin.script.experimental.jvm.JvmDependency import kotlin.script.experimental.jvmhost.impl.KJvmCompiledModuleInMemory -import kotlin.script.experimental.jvmhost.impl.KJvmCompiledScript +import kotlin.script.experimental.jvm.impl.KJvmCompiledScript // TODO: generate execution code (main) diff --git a/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/obsoleteJvmScriptEvaluation.kt b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/obsoleteJvmScriptEvaluation.kt new file mode 100644 index 00000000000..499a0113b75 --- /dev/null +++ b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/obsoleteJvmScriptEvaluation.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +@file:Suppress("unused") + +package kotlin.script.experimental.jvmhost + +import kotlin.script.experimental.api.ScriptEvaluationConfiguration +import kotlin.script.experimental.api.ScriptEvaluationConfigurationKeys +import kotlin.script.experimental.jvm.baseClassLoader +import kotlin.script.experimental.jvm.jvm +import kotlin.script.experimental.util.PropertiesCollection + +@Deprecated("use the same definitions from kotlin.script.experimental.jvm package", level = DeprecationLevel.WARNING) +interface JvmScriptEvaluationConfigurationKeys + +@Deprecated("use the same definitions from kotlin.script.experimental.jvm package", level = DeprecationLevel.WARNING) +open class JvmScriptEvaluationConfigurationBuilder : PropertiesCollection.Builder(), JvmScriptEvaluationConfigurationKeys { + + companion object : JvmScriptEvaluationConfigurationBuilder() +} + +@Deprecated("use the same definitions from kotlin.script.experimental.jvm package", level = DeprecationLevel.ERROR) +val JvmScriptEvaluationConfigurationKeys.baseClassLoader get() = ScriptEvaluationConfiguration.jvm.baseClassLoader + +@Deprecated("use the same definitions from kotlin.script.experimental.jvm package", level = DeprecationLevel.ERROR) +val ScriptEvaluationConfigurationKeys.jvm get() = ScriptEvaluationConfiguration.jvm + +@Deprecated("use the same definitions from kotlin.script.experimental.jvm package", level = DeprecationLevel.ERROR) +open class BasicJvmScriptEvaluator : kotlin.script.experimental.jvm.BasicJvmScriptEvaluator() diff --git a/libraries/scripting/jvm-host/test/kotlin/script/experimental/jvmhost/test/ScriptingHostTest.kt b/libraries/scripting/jvm-host/test/kotlin/script/experimental/jvmhost/test/ScriptingHostTest.kt index fbb2f249fd2..ade7792f5bd 100644 --- a/libraries/scripting/jvm-host/test/kotlin/script/experimental/jvmhost/test/ScriptingHostTest.kt +++ b/libraries/scripting/jvm-host/test/kotlin/script/experimental/jvmhost/test/ScriptingHostTest.kt @@ -18,10 +18,11 @@ import kotlin.script.experimental.api.* import kotlin.script.experimental.host.BasicScriptingHost import kotlin.script.experimental.host.FileScriptSource import kotlin.script.experimental.host.toScriptSource +import kotlin.script.experimental.jvm.BasicJvmScriptEvaluator import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration import kotlin.script.experimental.jvmhost.* import kotlin.script.experimental.jvmhost.impl.CompiledScriptClassLoader -import kotlin.script.experimental.jvmhost.impl.KJvmCompiledScript +import kotlin.script.experimental.jvm.impl.KJvmCompiledScript import kotlin.script.templates.standard.SimpleScriptTemplate class ScriptingHostTest : TestCase() { diff --git a/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/BasicJvmScriptEvaluator.kt b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/BasicJvmScriptEvaluator.kt new file mode 100644 index 00000000000..0b18dada7cc --- /dev/null +++ b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/BasicJvmScriptEvaluator.kt @@ -0,0 +1,91 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. 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.jvm + +import kotlin.reflect.KClass +import kotlin.script.experimental.api.* +import kotlin.script.experimental.jvm.impl.getConfigurationWithClassloader +import kotlin.script.experimental.jvm.impl.sharedScripts + +open class BasicJvmScriptEvaluator : ScriptEvaluator { + + override suspend operator fun invoke( + compiledScript: CompiledScript<*>, + scriptEvaluationConfiguration: ScriptEvaluationConfiguration + ): ResultWithDiagnostics = try { + val configuration = getConfigurationWithClassloader(compiledScript, scriptEvaluationConfiguration) + + compiledScript.getClass(configuration).onSuccess { scriptClass -> + // in the future, when (if) we'll stop to compile everything into constructor + // run as SAM + // return res + + val sharedScripts = configuration.sharedScripts + + sharedScripts?.get(scriptClass)?.asSuccess() + ?: compiledScript.otherScripts.mapSuccess { + invoke(it, configuration) + }.onSuccess { importedScriptsEvalResults -> + + val refinedEvalConfiguration = + configuration[ScriptEvaluationConfiguration.refineConfigurationBeforeEvaluate] + ?.handler?.invoke( + ScriptEvaluationConfigurationRefinementContext( + compiledScript, + configuration + ) + ) + ?.onFailure { + return@invoke ResultWithDiagnostics.Failure(it.reports) + } + ?.resultOrNull() + ?: configuration + + scriptClass.evalWithConfigAndOtherScriptsResults(refinedEvalConfiguration, importedScriptsEvalResults).let { + sharedScripts?.put(scriptClass, it) + ResultWithDiagnostics.Success(it) + } + } + } + } catch (e: Throwable) { + ResultWithDiagnostics.Failure( + e.asDiagnostics( + "Error evaluating script", + path = compiledScript.sourceLocationId + ) + ) + } + + private fun KClass<*>.evalWithConfigAndOtherScriptsResults( + refinedEvalConfiguration: ScriptEvaluationConfiguration, + importedScriptsEvalResults: List + ): EvaluationResult { + val args = ArrayList() + + refinedEvalConfiguration[ScriptEvaluationConfiguration.constructorArgs]?.let { + args.addAll(it) + } + refinedEvalConfiguration[ScriptEvaluationConfiguration.implicitReceivers]?.let { + args.addAll(it) + } + refinedEvalConfiguration[ScriptEvaluationConfiguration.providedProperties]?.forEach { + args.add(it.value) + } + + importedScriptsEvalResults.forEach { + args.add((it.returnValue as ResultValue.Value).scriptInstance) + } + + val ctor = java.constructors.single() + val instance = ctor.newInstance(*args.toArray()) + + return EvaluationResult( + ResultValue.Value("", instance, "", instance), + refinedEvalConfiguration + ) + } +} + diff --git a/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/KJvmCompiledModule.kt b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/impl/KJvmCompiledModule.kt similarity index 65% rename from libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/KJvmCompiledModule.kt rename to libraries/scripting/jvm/src/kotlin/script/experimental/jvm/impl/KJvmCompiledModule.kt index df64f8813e5..b809239d95d 100644 --- a/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/KJvmCompiledModule.kt +++ b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/impl/KJvmCompiledModule.kt @@ -3,27 +3,15 @@ * that can be found in the license/LICENSE.txt file. */ -package kotlin.script.experimental.jvmhost.impl +package kotlin.script.experimental.jvm.impl import java.io.File -import java.io.Serializable import java.net.URLClassLoader interface KJvmCompiledModule { fun createClassLoader(baseClassLoader: ClassLoader?): ClassLoader } -class KJvmCompiledModuleInMemory(val compilerOutputFiles: Map) : KJvmCompiledModule, Serializable { - - override fun createClassLoader(baseClassLoader: ClassLoader?): ClassLoader = - CompiledScriptClassLoader(baseClassLoader, compilerOutputFiles) - - companion object { - @JvmStatic - private val serialVersionUID = 0L - } -} - class KJvmCompiledModuleFromClassPath(val classpath: Collection) : KJvmCompiledModule { override fun createClassLoader(baseClassLoader: ClassLoader?): ClassLoader = diff --git a/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/impl/KJvmCompiledScript.kt b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/impl/KJvmCompiledScript.kt new file mode 100644 index 00000000000..1ae08f907c1 --- /dev/null +++ b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/impl/KJvmCompiledScript.kt @@ -0,0 +1,129 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. 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.jvm.impl + +import java.io.ObjectInputStream +import java.io.ObjectOutputStream +import java.io.Serializable +import java.net.URLClassLoader +import kotlin.reflect.KClass +import kotlin.script.experimental.api.* +import kotlin.script.experimental.jvm.* +import kotlin.script.experimental.jvm.actualClassLoader + +class KJvmCompiledScript( + sourceLocationId: String?, + compilationConfiguration: ScriptCompilationConfiguration, + private var scriptClassFQName: String, + otherScripts: List> = emptyList(), + var compiledModule: KJvmCompiledModule +) : CompiledScript, Serializable { + + private var _sourceLocationId: String? = sourceLocationId + + override val sourceLocationId: String? + get() = _sourceLocationId + + private var _compilationConfiguration: ScriptCompilationConfiguration? = compilationConfiguration + + override val compilationConfiguration: ScriptCompilationConfiguration + get() = _compilationConfiguration!! + + private var _otherScripts: List> = otherScripts + + override val otherScripts: List> + get() = _otherScripts + + override suspend fun getClass(scriptEvaluationConfiguration: ScriptEvaluationConfiguration?): ResultWithDiagnostics> = try { + // ensuring proper defaults are used + val actualEvaluationConfiguration = scriptEvaluationConfiguration ?: ScriptEvaluationConfiguration() + val classLoader = getOrCreateActualClassloader(actualEvaluationConfiguration) + + val clazz = classLoader.loadClass(scriptClassFQName).kotlin + clazz.asSuccess() + } catch (e: Throwable) { + ResultWithDiagnostics.Failure( + ScriptDiagnostic( + "Unable to instantiate class $scriptClassFQName", + sourcePath = sourceLocationId, + exception = e + ) + ) + } + + private fun writeObject(outputStream: ObjectOutputStream) { + outputStream.writeObject(compilationConfiguration) + outputStream.writeObject(sourceLocationId) + outputStream.writeObject(otherScripts) + outputStream.writeObject(compiledModule) + outputStream.writeObject(scriptClassFQName) + } + + @Suppress("UNCHECKED_CAST") + private fun readObject(inputStream: ObjectInputStream) { + _compilationConfiguration = inputStream.readObject() as ScriptCompilationConfiguration + _sourceLocationId = inputStream.readObject() as String? + _otherScripts = inputStream.readObject() as List> + compiledModule = inputStream.readObject() as KJvmCompiledModule + scriptClassFQName = inputStream.readObject() as String + } + + companion object { + @JvmStatic + private val serialVersionUID = 2L + } +} + +fun KJvmCompiledScript<*>.getOrCreateActualClassloader(evaluationConfiguration: ScriptEvaluationConfiguration): ClassLoader = + evaluationConfiguration[ScriptEvaluationConfiguration.jvm.actualClassLoader] ?: run { + val baseClassLoader = evaluationConfiguration[ScriptEvaluationConfiguration.jvm.baseClassLoader] + val classLoaderWithDeps = + if (evaluationConfiguration[ScriptEvaluationConfiguration.jvm.loadDependencies] == false) baseClassLoader + else makeClassLoaderFromDependencies(baseClassLoader) + compiledModule.createClassLoader(classLoaderWithDeps) + } + +fun getConfigurationWithClassloader( + script: CompiledScript<*>, baseConfiguration: ScriptEvaluationConfiguration +): ScriptEvaluationConfiguration = + if (baseConfiguration.containsKey(ScriptEvaluationConfiguration.jvm.actualClassLoader)) + baseConfiguration + else { + val jvmScript = (script as? KJvmCompiledScript<*>) + ?: throw IllegalArgumentException("Unexpected compiled script type: $script") + + val classloader = jvmScript.getOrCreateActualClassloader(baseConfiguration) + + ScriptEvaluationConfiguration(baseConfiguration) { + ScriptEvaluationConfiguration.jvm.actualClassLoader(classloader) + if (baseConfiguration[ScriptEvaluationConfiguration.scriptsInstancesSharing] == true) { + ScriptEvaluationConfiguration.jvm.scriptsInstancesSharingMap(mutableMapOf()) + } + } + } + +val ScriptEvaluationConfiguration.sharedScripts get() = get(ScriptEvaluationConfiguration.jvm.scriptsInstancesSharingMap) + +private fun CompiledScript<*>.makeClassLoaderFromDependencies(baseClassLoader: ClassLoader?): ClassLoader? { + val processedScripts = mutableSetOf>() + fun seq(res: Sequence>, script: CompiledScript<*>): Sequence> { + if (processedScripts.contains(script)) return res + processedScripts.add(script) + return script.otherScripts.asSequence().fold(res + script, ::seq) + } + + val dependencies = seq(emptySequence(), this).flatMap { script -> + script.compilationConfiguration[ScriptCompilationConfiguration.dependencies] + ?.asSequence() + ?.flatMap { dep -> + (dep as? JvmDependency)?.classpath?.asSequence()?.map { it.toURI().toURL() } ?: emptySequence() + } + ?: emptySequence() + }.distinct() + // TODO: previous dependencies and classloaders should be taken into account here + return if (dependencies.none()) baseClassLoader + else URLClassLoader(dependencies.toList().toTypedArray(), baseClassLoader) +} diff --git a/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/jvmScriptEvaluation.kt b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/jvmScriptEvaluation.kt new file mode 100644 index 00000000000..7fcc42d94c7 --- /dev/null +++ b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/jvmScriptEvaluation.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +@file:Suppress("unused") + +package kotlin.script.experimental.jvm + +import kotlin.reflect.KClass +import kotlin.script.experimental.api.* +import kotlin.script.experimental.util.PropertiesCollection + +interface JvmScriptEvaluationConfigurationKeys + +open class JvmScriptEvaluationConfigurationBuilder : PropertiesCollection.Builder(), JvmScriptEvaluationConfigurationKeys { + + companion object : JvmScriptEvaluationConfigurationBuilder() +} + +/** + * The base classloader to use for script classes loading + */ +val JvmScriptEvaluationConfigurationKeys.baseClassLoader by PropertiesCollection.key(Thread.currentThread().contextClassLoader) + +/** + * Load script dependencies before evaluation, true by default + * If false, it is assumed that the all dependencies will be provided via baseClassLoader + */ +val JvmScriptEvaluationConfigurationKeys.loadDependencies by PropertiesCollection.key(true) + +internal val JvmScriptEvaluationConfigurationKeys.actualClassLoader by PropertiesCollection.key() + +internal val JvmScriptEvaluationConfigurationKeys.scriptsInstancesSharingMap by PropertiesCollection.key, EvaluationResult>>() + +val ScriptEvaluationConfigurationKeys.jvm get() = JvmScriptEvaluationConfigurationBuilder()