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) =