From 4b032a14af3c42eba30ca57b4748fc42cc5a024f Mon Sep 17 00:00:00 2001 From: Ilya Chernikov Date: Wed, 13 May 2020 21:44:39 +0200 Subject: [PATCH] Refactor host configuration handling and script definition creation so it is possible now to create custom host configuration with template and all configurations are handled consistenly. Also introduce and use new ScriptDefinition class wrapping compilation and evaluation configurations, that could be consistenly created from a template. --- .../annotations/scriptAnnotations.kt | 4 +- .../host/configurationFromTemplate.kt | 144 +++++++++++++----- .../experimental/host/hostConfiguration.kt | 2 +- .../KotlinJsr223DefaultScriptEngineFactory.kt | 14 +- .../jvmhost/test/ScriptingHostTest.kt | 27 ++-- .../jvmhost/BasicJvmScriptingHost.kt | 43 ++++-- .../kotlin/script/experimental/jvm/runner.kt | 17 +-- .../kotlin/mainKts/test/mainKtsTest.kt | 22 +-- .../KotlinJsr223MainKtsScriptEngineFactory.kt | 17 +-- .../org/jetbrains/kotlin/mainKts/scriptDef.kt | 47 +++--- ...LazyScriptDefinitionFromDiscoveredClass.kt | 47 +++--- .../scripting/definitions/ScriptDefinition.kt | 21 +-- 12 files changed, 246 insertions(+), 159 deletions(-) diff --git a/libraries/scripting/common/src/kotlin/script/experimental/annotations/scriptAnnotations.kt b/libraries/scripting/common/src/kotlin/script/experimental/annotations/scriptAnnotations.kt index d3f2eedc5ab..94517c2810d 100644 --- a/libraries/scripting/common/src/kotlin/script/experimental/annotations/scriptAnnotations.kt +++ b/libraries/scripting/common/src/kotlin/script/experimental/annotations/scriptAnnotations.kt @@ -10,6 +10,7 @@ package kotlin.script.experimental.annotations import kotlin.reflect.KClass import kotlin.script.experimental.api.ScriptCompilationConfiguration import kotlin.script.experimental.api.ScriptEvaluationConfiguration +import kotlin.script.experimental.host.ScriptingHostConfiguration /** * The annotation for declaring a script definition (template) @@ -52,6 +53,7 @@ annotation class KotlinScript( val fileExtension: String = "kts", val filePathPattern: String = "", val compilationConfiguration: KClass = ScriptCompilationConfiguration.Default::class, - val evaluationConfiguration: KClass = ScriptEvaluationConfiguration.Default::class + val evaluationConfiguration: KClass = ScriptEvaluationConfiguration.Default::class, + val hostConfiguration: KClass = ScriptingHostConfiguration::class ) diff --git a/libraries/scripting/common/src/kotlin/script/experimental/host/configurationFromTemplate.kt b/libraries/scripting/common/src/kotlin/script/experimental/host/configurationFromTemplate.kt index 1cefd368696..a5e9e4be9e4 100644 --- a/libraries/scripting/common/src/kotlin/script/experimental/host/configurationFromTemplate.kt +++ b/libraries/scripting/common/src/kotlin/script/experimental/host/configurationFromTemplate.kt @@ -10,18 +10,44 @@ import kotlin.script.experimental.annotations.KotlinScript import kotlin.script.experimental.api.* import kotlin.script.experimental.util.PropertiesCollection -private const val ERROR_MSG_PREFIX = "Unable to construct script definition: " +/** + * Script definition combines configuration data for script compilation and evaluation + */ +data class ScriptDefinition( + val compilationConfiguration: ScriptCompilationConfiguration, + val evaluationConfiguration: ScriptEvaluationConfiguration +) -private const val ILLEGAL_CONFIG_ANN_ARG = - "Illegal argument compilationConfiguration of the KotlinScript annotation: expecting an object or default-constructed class derived from ScriptCompilationConfiguration" +/** + * Creates script compilation and evaluation configuration from annotated script base class + * @param baseClassType the annotated script base class to construct the configuration from + * @param baseHostConfiguration base scripting host configuration properties + * @param contextClass optional context class to extract classloading strategy from + * @param compilation optional configuration function to add more properties to the compilation configuration + * @param evaluation optional configuration function to add more properties to the evaluation configuration + */ +fun createScriptDefinitionFromTemplate( + baseClassType: KotlinType, + baseHostConfiguration: ScriptingHostConfiguration, + contextClass: KClass<*> = ScriptDefinition::class, + compilation: ScriptCompilationConfiguration.Builder.() -> Unit = {}, + evaluation: ScriptEvaluationConfiguration.Builder.() -> Unit = {} +): ScriptDefinition { + val templateClass: KClass<*> = baseClassType.getTemplateClass(baseHostConfiguration, contextClass) + val mainAnnotation = templateClass.kotlinScriptAnnotation -private const val SCRIPT_RUNTIME_TEMPLATES_PACKAGE = "kotlin.script.templates.standard" + val hostConfiguration = constructHostConfiguration(mainAnnotation.hostConfiguration, baseHostConfiguration) {} -@KotlinScript -private abstract class DummyScriptTemplate + val compilationConfiguration = + constructCompilationConfiguration(mainAnnotation, hostConfiguration, templateClass, baseClassType, compilation) + val evaluationConfiguration = constructEvaluationConfiguration(mainAnnotation, hostConfiguration, evaluation) + + return ScriptDefinition(compilationConfiguration, evaluationConfiguration) +} /** * Creates compilation configuration from annotated script base class + * NOTE: it is preferable to use createScriptDefinitionFromTemplate for creating all configurations at once * @param baseClassType the annotated script base class to construct the configuration from * @param baseHostConfiguration scripting host configuration properties * @param contextClass optional context class to extract classloading strategy from @@ -33,20 +59,17 @@ fun createCompilationConfigurationFromTemplate( contextClass: KClass<*> = ScriptCompilationConfiguration::class, body: ScriptCompilationConfiguration.Builder.() -> Unit = {} ): ScriptCompilationConfiguration { - val templateClass: KClass<*> = baseClassType.getTemplateClass(baseHostConfiguration, contextClass) + val mainAnnotation = templateClass.kotlinScriptAnnotation - val mainAnnotation: KotlinScript = templateClass.kotlinScriptAnnotation + val hostConfiguration = constructHostConfiguration(mainAnnotation.hostConfiguration, baseHostConfiguration) {} - return ScriptCompilationConfiguration(scriptConfigInstance(mainAnnotation.compilationConfiguration)) { - hostConfiguration.update { it.withDefaultsFrom(baseHostConfiguration) } - propertiesFromTemplate(templateClass, baseClassType, mainAnnotation) - body() - } + return constructCompilationConfiguration(mainAnnotation, hostConfiguration, templateClass, baseClassType, body) } /** * Creates evaluation configuration from annotated script base class + * NOTE: it is preferable to use createScriptDefinitionFromTemplate for creating all configurations at once * @param baseClassType the annotated script base class to construct the configuration from * @param baseHostConfiguration scripting host configuration properties * @param contextClass optional context class to extract classloading strategy from @@ -58,17 +81,76 @@ fun createEvaluationConfigurationFromTemplate( contextClass: KClass<*> = ScriptEvaluationConfiguration::class, body: ScriptEvaluationConfiguration.Builder.() -> Unit = {} ): ScriptEvaluationConfiguration { - val templateClass: KClass<*> = baseClassType.getTemplateClass(baseHostConfiguration, contextClass) - val mainAnnotation = templateClass.kotlinScriptAnnotation - return ScriptEvaluationConfiguration(scriptConfigInstance(mainAnnotation.evaluationConfiguration)) { - hostConfiguration.update { it.withDefaultsFrom(baseHostConfiguration) } + val hostConfiguration = constructHostConfiguration(mainAnnotation.hostConfiguration, baseHostConfiguration) {} + + return constructEvaluationConfiguration(mainAnnotation, hostConfiguration, body) +} + +private const val ERROR_MSG_PREFIX = "Unable to construct script definition: " + +private const val SCRIPT_RUNTIME_TEMPLATES_PACKAGE = "kotlin.script.templates.standard" + +@KotlinScript +private abstract class DummyScriptTemplate + +private fun constructCompilationConfiguration( + mainAnnotation: KotlinScript, + hostConfiguration: ScriptingHostConfiguration, + templateClass: KClass<*>, + baseClassType: KotlinType, + body: ScriptCompilationConfiguration.Builder.() -> Unit +): ScriptCompilationConfiguration { + val compilationConfigurationInstance = scriptConfigInstance(mainAnnotation.compilationConfiguration) + ?: throw IllegalArgumentException("${ERROR_MSG_PREFIX}Illegal argument compilationConfiguration of the KotlinScript annotation: expecting an object or default-constructable class derived from ScriptCompilationConfiguration") + + return ScriptCompilationConfiguration(compilationConfigurationInstance) { + // TODO: consider deprecating host configuration updating here, it is better to do it via dedicated annotation parameter + this.hostConfiguration.update { it.withDefaultsFrom(hostConfiguration) } + propertiesFromTemplate(templateClass, baseClassType, mainAnnotation) body() } } +private fun constructEvaluationConfiguration( + mainAnnotation: KotlinScript, + hostConfiguration: ScriptingHostConfiguration, + body: ScriptEvaluationConfiguration.Builder.() -> Unit +): ScriptEvaluationConfiguration { + val evaluationConfigurationInstance = scriptConfigInstance(mainAnnotation.evaluationConfiguration) + ?: throw IllegalArgumentException("${ERROR_MSG_PREFIX}Illegal argument evaluationConfiguration of the KotlinScript annotation: expecting an object or default-constructable class derived from ScriptEvaluationConfiguration") + + return ScriptEvaluationConfiguration(evaluationConfigurationInstance) { + // TODO: consider deprecating host configuration updating here, it is better to do it via dedicated annotation parameter + this.hostConfiguration.update { it.withDefaultsFrom(hostConfiguration) } + body() + } +} + +private fun constructHostConfiguration( + hostConfigurationKClass: KClass, + baseHostConfiguration: ScriptingHostConfiguration, + body: ScriptingHostConfiguration.Builder.() -> Unit +): ScriptingHostConfiguration { + if (hostConfigurationKClass == ScriptingHostConfiguration::class) + return ScriptingHostConfiguration(body).withDefaultsFrom(baseHostConfiguration) + + val singleArgConstructor = hostConfigurationKClass.java.constructors.singleOrNull { + it.parameters.isNotEmpty() && it.parameters.first().type.isAssignableFrom(ScriptingHostConfiguration::class.java) + } + + val hostConfigurationInstance = + if (singleArgConstructor != null) singleArgConstructor.newInstance(baseHostConfiguration) as ScriptingHostConfiguration + else scriptConfigInstance(hostConfigurationKClass) + ?: throw IllegalArgumentException("${ERROR_MSG_PREFIX}Illegal argument hostConfiguration of the KotlinScript annotation: expecting an object or a class derived from ScriptingHostConfiguration constructable without arguments or from a base configuration") + + return hostConfigurationInstance.with { + body() + }.withDefaultsFrom(baseHostConfiguration) +} + private fun ScriptCompilationConfiguration.Builder.propertiesFromTemplate( templateClass: KClass<*>, baseClassType: KotlinType, mainAnnotation: KotlinScript ) { @@ -83,13 +165,14 @@ private val KClass<*>.kotlinScriptAnnotation: KotlinScript ?: when (this@kotlinScriptAnnotation.qualifiedName) { // Any is the default template, so use a default annotation Any::class.qualifiedName, - // transitions to the new scripting API: substituting annotations for standard templates from script-runtime + // transitions to the new scripting API: substituting annotations for standard templates from script-runtime "$SCRIPT_RUNTIME_TEMPLATES_PACKAGE.SimpleScriptTemplate", "$SCRIPT_RUNTIME_TEMPLATES_PACKAGE.ScriptTemplateWithArgs", - "$SCRIPT_RUNTIME_TEMPLATES_PACKAGE.ScriptTemplateWithBindings" -> DummyScriptTemplate::class.findAnnotation() + "$SCRIPT_RUNTIME_TEMPLATES_PACKAGE.ScriptTemplateWithBindings", + -> DummyScriptTemplate::class.findAnnotation() else -> null } - ?: throw IllegalArgumentException("${ERROR_MSG_PREFIX}Expecting KotlinScript annotation on the ${this}") + ?: throw IllegalArgumentException("${ERROR_MSG_PREFIX}Expecting KotlinScript annotation on the $this") private fun KotlinType.getTemplateClass(hostConfiguration: ScriptingHostConfiguration, contextClass: KClass<*>): KClass<*> { val getScriptingClass = hostConfiguration[ScriptingHostConfiguration.getScriptingClass] @@ -98,7 +181,7 @@ private fun KotlinType.getTemplateClass(hostConfiguration: ScriptingHostConfigur return try { getScriptingClass(this, contextClass, hostConfiguration) } catch (e: Throwable) { - throw IllegalArgumentException("${ERROR_MSG_PREFIX}Unable to load base class ${this}", e) + throw IllegalArgumentException("${ERROR_MSG_PREFIX}Unable to load base class $this", e) } } @@ -106,18 +189,9 @@ private inline fun KClass<*>.findAnnotation(): T? = @Suppress("UNCHECKED_CAST") this.java.annotations.firstOrNull { it is T } as T? -private fun KClass.createInstance(): T { - // TODO: throw a meaningful exception - val noArgsConstructor = java.constructors.singleOrNull { it.parameters.isEmpty() } - ?: throw IllegalArgumentException("Class should have a single no-arg constructor: $this") - - @Suppress("UNCHECKED_CAST") - return noArgsConstructor.newInstance() as T -} - -private fun scriptConfigInstance(kclass: KClass): T = try { - kclass.objectInstance ?: kclass.createInstance() -} catch (e: Throwable) { - throw IllegalArgumentException("$ILLEGAL_CONFIG_ANN_ARG: ${e.message + if (e.cause != null) " (${e.cause})" else ""}", e) -} +private inline fun scriptConfigInstance(kclass: KClass): T? = + kclass.objectInstance ?: run { + val noArgsConstructor = kclass.java.constructors.singleOrNull { it.parameters.isEmpty() } + noArgsConstructor?.let { it.newInstance() as T } + } diff --git a/libraries/scripting/common/src/kotlin/script/experimental/host/hostConfiguration.kt b/libraries/scripting/common/src/kotlin/script/experimental/host/hostConfiguration.kt index c39998d80ba..bd2b304f221 100644 --- a/libraries/scripting/common/src/kotlin/script/experimental/host/hostConfiguration.kt +++ b/libraries/scripting/common/src/kotlin/script/experimental/host/hostConfiguration.kt @@ -17,7 +17,7 @@ interface ScriptingHostConfigurationKeys * The container for script evaluation configuration * For usages see actual code examples */ -class ScriptingHostConfiguration(baseScriptingConfigurations: Iterable, body: Builder.() -> Unit) : +open class ScriptingHostConfiguration(baseScriptingConfigurations: Iterable, body: Builder.() -> Unit) : PropertiesCollection(Builder(baseScriptingConfigurations).apply(body).data) { constructor(body: Builder.() -> Unit = {}) : this(emptyList(), body) diff --git a/libraries/scripting/jsr223/src/kotlin/script/experimental/jsr223/KotlinJsr223DefaultScriptEngineFactory.kt b/libraries/scripting/jsr223/src/kotlin/script/experimental/jsr223/KotlinJsr223DefaultScriptEngineFactory.kt index 3e4416f1a94..7efb286a0bd 100644 --- a/libraries/scripting/jsr223/src/kotlin/script/experimental/jsr223/KotlinJsr223DefaultScriptEngineFactory.kt +++ b/libraries/scripting/jsr223/src/kotlin/script/experimental/jsr223/KotlinJsr223DefaultScriptEngineFactory.kt @@ -11,15 +11,14 @@ import java.io.File import javax.script.Bindings import javax.script.ScriptContext import javax.script.ScriptEngine -import kotlin.script.experimental.api.ScriptCompilationConfiguration import kotlin.script.experimental.api.dependencies +import kotlin.script.experimental.api.with import kotlin.script.experimental.jvm.JvmDependencyFromClassLoader import kotlin.script.experimental.jvm.JvmScriptCompilationConfigurationBuilder import kotlin.script.experimental.jvm.jvm import kotlin.script.experimental.jvm.updateClasspath import kotlin.script.experimental.jvm.util.scriptCompilationClasspathFromContext -import kotlin.script.experimental.jvmhost.createJvmCompilationConfigurationFromTemplate -import kotlin.script.experimental.jvmhost.createJvmEvaluationConfigurationFromTemplate +import kotlin.script.experimental.jvmhost.createJvmScriptDefinitionFromTemplate import kotlin.script.experimental.jvmhost.jsr223.KotlinJsr223ScriptEngineImpl /** @@ -31,13 +30,12 @@ const val KOTLIN_JSR223_RESOLVE_FROM_CLASSLOADER_PROPERTY = "kotlin.jsr223.exper class KotlinJsr223DefaultScriptEngineFactory : KotlinJsr223JvmScriptEngineFactoryBase() { - private val compilationConfiguration = createJvmCompilationConfigurationFromTemplate() - private val evaluationConfiguration = createJvmEvaluationConfigurationFromTemplate() + private val scriptDefinition = createJvmScriptDefinitionFromTemplate() private var lastClassLoader: ClassLoader? = null private var lastClassPath: List? = null @Synchronized - protected fun JvmScriptCompilationConfigurationBuilder.dependenciesFromCurrentContext() { + private fun JvmScriptCompilationConfigurationBuilder.dependenciesFromCurrentContext() { val currentClassLoader = Thread.currentThread().contextClassLoader val classPath = if (lastClassLoader == null || lastClassLoader != currentClassLoader) { scriptCompilationClasspathFromContext( @@ -55,7 +53,7 @@ class KotlinJsr223DefaultScriptEngineFactory : KotlinJsr223JvmScriptEngineFactor override fun getScriptEngine(): ScriptEngine = KotlinJsr223ScriptEngineImpl( this, - ScriptCompilationConfiguration(compilationConfiguration) { + scriptDefinition.compilationConfiguration.with { jvm { if (System.getProperty(KOTLIN_JSR223_RESOLVE_FROM_CLASSLOADER_PROPERTY) == "true") { dependencies(JvmDependencyFromClassLoader { Thread.currentThread().contextClassLoader }) @@ -64,7 +62,7 @@ class KotlinJsr223DefaultScriptEngineFactory : KotlinJsr223JvmScriptEngineFactor } } }, - evaluationConfiguration + scriptDefinition.evaluationConfiguration ) { ScriptArgsWithTypes(arrayOf(it.getBindings(ScriptContext.ENGINE_SCOPE).orEmpty()), arrayOf(Bindings::class)) } } diff --git a/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ScriptingHostTest.kt b/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ScriptingHostTest.kt index 1f635f97052..a7958339991 100644 --- a/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ScriptingHostTest.kt +++ b/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ScriptingHostTest.kt @@ -54,12 +54,11 @@ class ScriptingHostTest : TestCase() { val greeting = "Hello from script!" val output = captureOut { val basicJvmScriptingHost = BasicJvmScriptingHost() - basicJvmScriptingHost.eval( + basicJvmScriptingHost.evalWithTemplate( "println(\"$greeting\")".toScriptSource("name"), - createJvmCompilationConfigurationFromTemplate(basicJvmScriptingHost.hostConfiguration) { + compilation = { updateClasspath(classpathFromClass()) - }, - createJvmEvaluationConfigurationFromTemplate(basicJvmScriptingHost.hostConfiguration) + } ).throwOnFailure() } Assert.assertEquals(greeting, output) @@ -206,15 +205,19 @@ class ScriptingHostTest : TestCase() { fun testSimpleImportWithImplicitReceiver() { val greeting = listOf("Hello from helloWithVal script!", "Hello from imported helloWithVal script!") val script = "println(\"Hello from imported \$helloScriptName script!\")" - val compilationConfiguration = createJvmCompilationConfigurationFromTemplate { - makeSimpleConfigurationWithTestImport() - implicitReceivers(String::class) - } - val evaluationConfiguration = createJvmEvaluationConfigurationFromTemplate { - implicitReceivers("abc") - } + val definition = createJvmScriptDefinitionFromTemplate( + compilation = { + makeSimpleConfigurationWithTestImport() + implicitReceivers(String::class) + }, + evaluation = { + implicitReceivers("abc") + } + ) val output = captureOut { - BasicJvmScriptingHost().eval(script.toScriptSource(), compilationConfiguration, evaluationConfiguration).throwOnFailure() + BasicJvmScriptingHost().eval( + script.toScriptSource(), definition.compilationConfiguration, definition.evaluationConfiguration + ).throwOnFailure() }.lines() Assert.assertEquals(greeting, output) } 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 8f94b9baa1e..5249aedc6c7 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 @@ -3,19 +3,18 @@ * 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.* -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.host.* import kotlin.script.experimental.jvm.BasicJvmScriptEvaluator import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration open class BasicJvmScriptingHost( - val hostConfiguration: ScriptingHostConfiguration = defaultJvmScriptingHostConfiguration, - compiler: JvmScriptCompiler = JvmScriptCompiler(hostConfiguration), + val baseHostConfiguration: ScriptingHostConfiguration? = null, + compiler: JvmScriptCompiler = JvmScriptCompiler(baseHostConfiguration.withDefaultsFrom(defaultJvmScriptingHostConfiguration)), evaluator: ScriptEvaluator = BasicJvmScriptEvaluator() ) : BasicScriptingHost(compiler, evaluator) { @@ -23,31 +22,43 @@ open class BasicJvmScriptingHost( script: SourceCode, noinline compilation: ScriptCompilationConfiguration.Builder.() -> Unit = {}, noinline evaluation: ScriptEvaluationConfiguration.Builder.() -> Unit = {} - ): ResultWithDiagnostics = - eval( - script, - createJvmCompilationConfigurationFromTemplate(hostConfiguration, compilation), - createJvmEvaluationConfigurationFromTemplate(hostConfiguration, evaluation) - ) + ): ResultWithDiagnostics { + val definition = + createJvmScriptDefinitionFromTemplate(baseHostConfiguration, compilation, evaluation) + return eval(script, definition.compilationConfiguration, definition.evaluationConfiguration) + } } inline fun createJvmCompilationConfigurationFromTemplate( - hostConfiguration: ScriptingHostConfiguration = defaultJvmScriptingHostConfiguration, + baseHostConfiguration: ScriptingHostConfiguration? = null, noinline body: ScriptCompilationConfiguration.Builder.() -> Unit = {} ): ScriptCompilationConfiguration = createCompilationConfigurationFromTemplate( KotlinType(T::class), - hostConfiguration, + baseHostConfiguration.withDefaultsFrom(defaultJvmScriptingHostConfiguration), ScriptCompilationConfiguration::class, body ) inline fun createJvmEvaluationConfigurationFromTemplate( - hostConfiguration: ScriptingHostConfiguration = defaultJvmScriptingHostConfiguration, + baseHostConfiguration: ScriptingHostConfiguration? = null, noinline body: ScriptEvaluationConfiguration.Builder.() -> Unit = {} ): ScriptEvaluationConfiguration = createEvaluationConfigurationFromTemplate( KotlinType(T::class), - hostConfiguration, + baseHostConfiguration.withDefaultsFrom(defaultJvmScriptingHostConfiguration), ScriptEvaluationConfiguration::class, body ) + +inline fun createJvmScriptDefinitionFromTemplate( + baseHostConfiguration: ScriptingHostConfiguration? = null, + noinline compilation: ScriptCompilationConfiguration.Builder.() -> Unit = {}, + noinline evaluation: ScriptEvaluationConfiguration.Builder.() -> Unit = {} +): ScriptDefinition = createScriptDefinitionFromTemplate( + KotlinType(T::class), + baseHostConfiguration.withDefaultsFrom(defaultJvmScriptingHostConfiguration), + ScriptCompilationConfiguration::class, + compilation, + evaluation +) + diff --git a/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/runner.kt b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/runner.kt index c8a04dfe372..679de663f5b 100644 --- a/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/runner.kt +++ b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/runner.kt @@ -11,25 +11,24 @@ import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.startCoroutine import kotlin.script.experimental.api.* import kotlin.script.experimental.host.createEvaluationConfigurationFromTemplate +import kotlin.script.experimental.host.withDefaultsFrom import kotlin.script.experimental.jvm.impl.createScriptFromClassLoader @Suppress("unused") // script codegen generates a call to it fun runCompiledScript(scriptClass: Class<*>, vararg args: String) { val script = createScriptFromClassLoader(scriptClass.name, scriptClass.classLoader) val evaluator = BasicJvmScriptEvaluator() - val hostConfiguration = script.compilationConfiguration[ScriptCompilationConfiguration.hostConfiguration] - ?: defaultJvmScriptingHostConfiguration - val baseEvaluationConfiguration = + val evaluationConfiguration = createEvaluationConfigurationFromTemplate( script.compilationConfiguration[ScriptCompilationConfiguration.baseClass]!!, - hostConfiguration, + script.compilationConfiguration[ScriptCompilationConfiguration.hostConfiguration] + .withDefaultsFrom(defaultJvmScriptingHostConfiguration), scriptClass.kotlin - ) - val evaluationConfiguration = ScriptEvaluationConfiguration(baseEvaluationConfiguration) { - jvm { - mainArguments(args) + ) { + jvm { + mainArguments(args) + } } - } runScriptSuspend { evaluator(script, evaluationConfiguration).onFailure { it.reports.forEach(System.err::println) diff --git a/libraries/tools/kotlin-main-kts-test/test/org/jetbrains/kotlin/mainKts/test/mainKtsTest.kt b/libraries/tools/kotlin-main-kts-test/test/org/jetbrains/kotlin/mainKts/test/mainKtsTest.kt index 5ee3b6acd4e..05142c10ae1 100644 --- a/libraries/tools/kotlin-main-kts-test/test/org/jetbrains/kotlin/mainKts/test/mainKtsTest.kt +++ b/libraries/tools/kotlin-main-kts-test/test/org/jetbrains/kotlin/mainKts/test/mainKtsTest.kt @@ -15,21 +15,23 @@ import kotlin.script.experimental.host.toScriptSource import kotlin.script.experimental.jvm.baseClassLoader import kotlin.script.experimental.jvm.jvm import kotlin.script.experimental.jvmhost.BasicJvmScriptingHost -import kotlin.script.experimental.jvmhost.createJvmCompilationConfigurationFromTemplate +import kotlin.script.experimental.jvmhost.createJvmScriptDefinitionFromTemplate fun evalFile(scriptFile: File, cacheDir: File? = null): ResultWithDiagnostics = withProperty(COMPILED_SCRIPTS_CACHE_DIR_PROPERTY, cacheDir?.absolutePath ?: "") { - val scriptDefinition = createJvmCompilationConfigurationFromTemplate() - - val evaluationEnv = ScriptEvaluationConfiguration { - jvm { - baseClassLoader(null) + val scriptDefinition = createJvmScriptDefinitionFromTemplate( + evaluation = { + jvm { + baseClassLoader(null) + } + constructorArgs(emptyArray()) + enableScriptsInstancesSharing() } - constructorArgs(emptyArray()) - enableScriptsInstancesSharing() - } + ) - BasicJvmScriptingHost().eval(scriptFile.toScriptSource(), scriptDefinition, evaluationEnv) + BasicJvmScriptingHost().eval( + scriptFile.toScriptSource(), scriptDefinition.compilationConfiguration, scriptDefinition.evaluationConfiguration + ) } diff --git a/libraries/tools/kotlin-main-kts/src/org/jetbrains/kotlin/mainKts/jsr223/KotlinJsr223MainKtsScriptEngineFactory.kt b/libraries/tools/kotlin-main-kts/src/org/jetbrains/kotlin/mainKts/jsr223/KotlinJsr223MainKtsScriptEngineFactory.kt index 219b5892f2e..5803ad3eede 100644 --- a/libraries/tools/kotlin-main-kts/src/org/jetbrains/kotlin/mainKts/jsr223/KotlinJsr223MainKtsScriptEngineFactory.kt +++ b/libraries/tools/kotlin-main-kts/src/org/jetbrains/kotlin/mainKts/jsr223/KotlinJsr223MainKtsScriptEngineFactory.kt @@ -12,26 +12,25 @@ import java.io.File import javax.script.ScriptEngine import kotlin.script.experimental.api.ScriptCompilationConfiguration import kotlin.script.experimental.api.fileExtension +import kotlin.script.experimental.api.with import kotlin.script.experimental.jvm.JvmScriptCompilationConfigurationBuilder -import kotlin.script.experimental.jvm.dependenciesFromCurrentContext import kotlin.script.experimental.jvm.jvm import kotlin.script.experimental.jvm.updateClasspath import kotlin.script.experimental.jvm.util.scriptCompilationClasspathFromContext -import kotlin.script.experimental.jvmhost.createJvmCompilationConfigurationFromTemplate -import kotlin.script.experimental.jvmhost.createJvmEvaluationConfigurationFromTemplate +import kotlin.script.experimental.jvmhost.createJvmScriptDefinitionFromTemplate import kotlin.script.experimental.jvmhost.jsr223.KotlinJsr223ScriptEngineImpl class KotlinJsr223MainKtsScriptEngineFactory : KotlinJsr223JvmScriptEngineFactoryBase() { - private val compilationConfiguration = createJvmCompilationConfigurationFromTemplate() - private val evaluationConfiguration = createJvmEvaluationConfigurationFromTemplate() + private val scriptDefinition = createJvmScriptDefinitionFromTemplate() private var lastClassLoader: ClassLoader? = null private var lastClassPath: List? = null - override fun getExtensions(): List = listOf(compilationConfiguration[ScriptCompilationConfiguration.fileExtension]!!) + override fun getExtensions(): List = + listOf(scriptDefinition.compilationConfiguration[ScriptCompilationConfiguration.fileExtension]!!) @Synchronized - protected fun JvmScriptCompilationConfigurationBuilder.dependenciesFromCurrentContext() { + private fun JvmScriptCompilationConfigurationBuilder.dependenciesFromCurrentContext() { val currentClassLoader = Thread.currentThread().contextClassLoader val classPath = if (lastClassLoader == null || lastClassLoader != currentClassLoader) { scriptCompilationClasspathFromContext( @@ -49,12 +48,12 @@ class KotlinJsr223MainKtsScriptEngineFactory : KotlinJsr223JvmScriptEngineFactor override fun getScriptEngine(): ScriptEngine = KotlinJsr223ScriptEngineImpl( this, - ScriptCompilationConfiguration(compilationConfiguration) { + scriptDefinition.compilationConfiguration.with { jvm { dependenciesFromCurrentContext() } }, - evaluationConfiguration + scriptDefinition.evaluationConfiguration ) { ScriptArgsWithTypes(arrayOf(emptyArray()), arrayOf(Array::class)) } } diff --git a/libraries/tools/kotlin-main-kts/src/org/jetbrains/kotlin/mainKts/scriptDef.kt b/libraries/tools/kotlin-main-kts/src/org/jetbrains/kotlin/mainKts/scriptDef.kt index 99fa5706b5e..622256fb122 100644 --- a/libraries/tools/kotlin-main-kts/src/org/jetbrains/kotlin/mainKts/scriptDef.kt +++ b/libraries/tools/kotlin-main-kts/src/org/jetbrains/kotlin/mainKts/scriptDef.kt @@ -35,7 +35,8 @@ import kotlin.script.experimental.jvmhost.jsr223.jsr223 @KotlinScript( fileExtension = "main.kts", compilationConfiguration = MainKtsScriptDefinition::class, - evaluationConfiguration = MainKtsEvaluationConfiguration::class + evaluationConfiguration = MainKtsEvaluationConfiguration::class, + hostConfiguration = MainKtsHostConfiguration::class ) abstract class MainKtsScript(val args: Array) @@ -58,26 +59,8 @@ class MainKtsScriptDefinition : ScriptCompilationConfiguration( jsr223 { importAllBindings(true) } - hostConfiguration(ScriptingHostConfiguration { - jvm { - val cacheExtSetting = System.getProperty(COMPILED_SCRIPTS_CACHE_DIR_PROPERTY) - ?: System.getenv(COMPILED_SCRIPTS_CACHE_DIR_ENV_VAR) - val cacheBaseDir = when { - cacheExtSetting == null -> System.getProperty("java.io.tmpdir") - ?.let(::File)?.takeIf { it.exists() && it.isDirectory } - ?.let { File(it, "main.kts.compiled.cache").apply { mkdir() } } - cacheExtSetting.isBlank() -> null - else -> File(cacheExtSetting) - }?.takeIf { it.exists() && it.isDirectory } - if (cacheBaseDir != null) - compilationCache( - CompiledScriptJarsCache { script, scriptCompilationConfiguration -> - File(cacheBaseDir, compiledScriptUniqueName(script, scriptCompilationConfiguration) + ".jar") - } - ) - } - }) - }) + } +) object MainKtsEvaluationConfiguration : ScriptEvaluationConfiguration( { @@ -87,6 +70,28 @@ object MainKtsEvaluationConfiguration : ScriptEvaluationConfiguration( } ) +class MainKtsHostConfiguration : ScriptingHostConfiguration( + { + jvm { + val cacheExtSetting = System.getProperty(COMPILED_SCRIPTS_CACHE_DIR_PROPERTY) + ?: System.getenv(COMPILED_SCRIPTS_CACHE_DIR_ENV_VAR) + val cacheBaseDir = when { + cacheExtSetting == null -> System.getProperty("java.io.tmpdir") + ?.let(::File)?.takeIf { it.exists() && it.isDirectory } + ?.let { File(it, "main.kts.compiled.cache").apply { mkdir() } } + cacheExtSetting.isBlank() -> null + else -> File(cacheExtSetting) + }?.takeIf { it.exists() && it.isDirectory } + if (cacheBaseDir != null) + compilationCache( + CompiledScriptJarsCache { script, scriptCompilationConfiguration -> + File(cacheBaseDir, compiledScriptUniqueName(script, scriptCompilationConfiguration) + ".jar") + } + ) + } + } +) + fun configureConstructorArgsFromMainArgs(context: ScriptEvaluationConfigurationRefinementContext): ResultWithDiagnostics { val mainArgs = context.evaluationConfiguration[ScriptEvaluationConfiguration.jvm.mainArguments] val res = if (context.evaluationConfiguration[ScriptEvaluationConfiguration.constructorArgs] == null && mainArgs != null) { diff --git a/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/definitions/LazyScriptDefinitionFromDiscoveredClass.kt b/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/definitions/LazyScriptDefinitionFromDiscoveredClass.kt index af467e811f8..e275e4549d3 100644 --- a/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/definitions/LazyScriptDefinitionFromDiscoveredClass.kt +++ b/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/definitions/LazyScriptDefinitionFromDiscoveredClass.kt @@ -8,14 +8,11 @@ package org.jetbrains.kotlin.scripting.definitions import java.io.File import kotlin.script.experimental.annotations.KotlinScript import kotlin.script.experimental.api.* -import kotlin.script.experimental.host.ScriptingHostConfiguration -import kotlin.script.experimental.host.configurationDependencies -import kotlin.script.experimental.host.createCompilationConfigurationFromTemplate -import kotlin.script.experimental.host.createEvaluationConfigurationFromTemplate +import kotlin.script.experimental.host.* import kotlin.script.experimental.jvm.JvmDependency class LazyScriptDefinitionFromDiscoveredClass internal constructor( - baseHostConfiguration: ScriptingHostConfiguration, + private val baseHostConfiguration: ScriptingHostConfiguration, private val annotationsFromAsm: ArrayList, private val className: String, private val classpath: List, @@ -30,31 +27,21 @@ class LazyScriptDefinitionFromDiscoveredClass internal constructor( messageReporter: MessageReporter ) : this(baseHostConfiguration, loadAnnotationsFromClass(classBytes), className, classpath, messageReporter) - override val hostConfiguration: ScriptingHostConfiguration by lazy(LazyThreadSafetyMode.PUBLICATION) { - ScriptingHostConfiguration(baseHostConfiguration) { - configurationDependencies.append(JvmDependency(classpath)) - } - } - - private val configurations by lazy(LazyThreadSafetyMode.PUBLICATION) { + private val definition: kotlin.script.experimental.host.ScriptDefinition by lazy(LazyThreadSafetyMode.PUBLICATION) { messageReporter( ScriptDiagnostic.Severity.DEBUG, "Configure scripting: loading script definition class $className using classpath $classpath\n. ${Thread.currentThread().stackTrace}" ) try { - val compileCfg = - createCompilationConfigurationFromTemplate( - KotlinType(className), - hostConfiguration, - LazyScriptDefinitionFromDiscoveredClass::class - ) - val evalCfg = - createEvaluationConfigurationFromTemplate( - KotlinType(className), - hostConfiguration, - LazyScriptDefinitionFromDiscoveredClass::class - ) - compileCfg to evalCfg + createScriptDefinitionFromTemplate( + KotlinType(className), + baseHostConfiguration.with { + if (classpath.isNotEmpty()) { + configurationDependencies.append(JvmDependency(classpath)) + } + }, + LazyScriptDefinitionFromDiscoveredClass::class + ) } catch (ex: ClassNotFoundException) { messageReporter(ScriptDiagnostic.Severity.ERROR, "Cannot find script definition class $className") InvalidScriptDefinition @@ -67,8 +54,11 @@ class LazyScriptDefinitionFromDiscoveredClass internal constructor( } } - override val compilationConfiguration: ScriptCompilationConfiguration get() = configurations.first - override val evaluationConfiguration: ScriptEvaluationConfiguration get() = configurations.second + override val hostConfiguration: ScriptingHostConfiguration + get() = definition.compilationConfiguration[ScriptCompilationConfiguration.hostConfiguration] ?: baseHostConfiguration + + override val compilationConfiguration: ScriptCompilationConfiguration get() = definition.compilationConfiguration + override val evaluationConfiguration: ScriptEvaluationConfiguration get() = definition.evaluationConfiguration override val fileExtension: String by lazy(LazyThreadSafetyMode.PUBLICATION) { annotationsFromAsm.find { it.name == KotlinScript::class.java.simpleName }?.args @@ -84,4 +74,5 @@ class LazyScriptDefinitionFromDiscoveredClass internal constructor( } } -val InvalidScriptDefinition = ScriptCompilationConfiguration() to ScriptEvaluationConfiguration() +val InvalidScriptDefinition = + ScriptDefinition(ScriptCompilationConfiguration(), ScriptEvaluationConfiguration()) diff --git a/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/definitions/ScriptDefinition.kt b/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/definitions/ScriptDefinition.kt index d858d56a02a..86942920986 100644 --- a/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/definitions/ScriptDefinition.kt +++ b/plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/definitions/ScriptDefinition.kt @@ -11,9 +11,7 @@ import org.jetbrains.kotlin.scripting.resolve.KotlinScriptDefinitionFromAnnotate import java.io.File import kotlin.reflect.KClass import kotlin.script.experimental.api.* -import kotlin.script.experimental.host.ScriptingHostConfiguration -import kotlin.script.experimental.host.createCompilationConfigurationFromTemplate -import kotlin.script.experimental.host.createEvaluationConfigurationFromTemplate +import kotlin.script.experimental.host.* import kotlin.script.experimental.jvm.baseClassLoader import kotlin.script.experimental.jvm.jvm @@ -181,14 +179,19 @@ abstract class ScriptDefinition : UserDataHolderBase() { ) : FromConfigurationsBase() open class FromTemplate( - hostConfiguration: ScriptingHostConfiguration, + private val baseHostConfiguration: ScriptingHostConfiguration, template: KClass<*>, contextClass: KClass<*> = ScriptCompilationConfiguration::class - ) : FromConfigurations( - hostConfiguration, - createCompilationConfigurationFromTemplate(KotlinType(template), hostConfiguration, contextClass), - createEvaluationConfigurationFromTemplate(KotlinType(template), hostConfiguration, contextClass) - ) + ) : FromConfigurationsBase() { + + private val definition = createScriptDefinitionFromTemplate(KotlinType(template), baseHostConfiguration, contextClass) + + override val hostConfiguration: ScriptingHostConfiguration + get() = definition.compilationConfiguration[ScriptCompilationConfiguration.hostConfiguration] ?: baseHostConfiguration + + override val compilationConfiguration: ScriptCompilationConfiguration get() = definition.compilationConfiguration + override val evaluationConfiguration: ScriptEvaluationConfiguration get() = definition.evaluationConfiguration + } companion object { fun getDefault(hostConfiguration: ScriptingHostConfiguration) =