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.
This commit is contained in:
Ilya Chernikov
2020-05-13 21:44:39 +02:00
parent 255ad47406
commit 4b032a14af
12 changed files with 246 additions and 159 deletions
@@ -10,6 +10,7 @@ package kotlin.script.experimental.annotations
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.script.experimental.api.ScriptCompilationConfiguration import kotlin.script.experimental.api.ScriptCompilationConfiguration
import kotlin.script.experimental.api.ScriptEvaluationConfiguration import kotlin.script.experimental.api.ScriptEvaluationConfiguration
import kotlin.script.experimental.host.ScriptingHostConfiguration
/** /**
* The annotation for declaring a script definition (template) * The annotation for declaring a script definition (template)
@@ -52,6 +53,7 @@ annotation class KotlinScript(
val fileExtension: String = "kts", val fileExtension: String = "kts",
val filePathPattern: String = "", val filePathPattern: String = "",
val compilationConfiguration: KClass<out ScriptCompilationConfiguration> = ScriptCompilationConfiguration.Default::class, val compilationConfiguration: KClass<out ScriptCompilationConfiguration> = ScriptCompilationConfiguration.Default::class,
val evaluationConfiguration: KClass<out ScriptEvaluationConfiguration> = ScriptEvaluationConfiguration.Default::class val evaluationConfiguration: KClass<out ScriptEvaluationConfiguration> = ScriptEvaluationConfiguration.Default::class,
val hostConfiguration: KClass<out ScriptingHostConfiguration> = ScriptingHostConfiguration::class
) )
@@ -10,18 +10,44 @@ import kotlin.script.experimental.annotations.KotlinScript
import kotlin.script.experimental.api.* import kotlin.script.experimental.api.*
import kotlin.script.experimental.util.PropertiesCollection 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 val compilationConfiguration =
private abstract class DummyScriptTemplate constructCompilationConfiguration(mainAnnotation, hostConfiguration, templateClass, baseClassType, compilation)
val evaluationConfiguration = constructEvaluationConfiguration(mainAnnotation, hostConfiguration, evaluation)
return ScriptDefinition(compilationConfiguration, evaluationConfiguration)
}
/** /**
* Creates compilation configuration from annotated script base class * 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 baseClassType the annotated script base class to construct the configuration from
* @param baseHostConfiguration scripting host configuration properties * @param baseHostConfiguration scripting host configuration properties
* @param contextClass optional context class to extract classloading strategy from * @param contextClass optional context class to extract classloading strategy from
@@ -33,20 +59,17 @@ fun createCompilationConfigurationFromTemplate(
contextClass: KClass<*> = ScriptCompilationConfiguration::class, contextClass: KClass<*> = ScriptCompilationConfiguration::class,
body: ScriptCompilationConfiguration.Builder.() -> Unit = {} body: ScriptCompilationConfiguration.Builder.() -> Unit = {}
): ScriptCompilationConfiguration { ): ScriptCompilationConfiguration {
val templateClass: KClass<*> = baseClassType.getTemplateClass(baseHostConfiguration, contextClass) 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)) { return constructCompilationConfiguration(mainAnnotation, hostConfiguration, templateClass, baseClassType, body)
hostConfiguration.update { it.withDefaultsFrom(baseHostConfiguration) }
propertiesFromTemplate(templateClass, baseClassType, mainAnnotation)
body()
}
} }
/** /**
* Creates evaluation configuration from annotated script base class * 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 baseClassType the annotated script base class to construct the configuration from
* @param baseHostConfiguration scripting host configuration properties * @param baseHostConfiguration scripting host configuration properties
* @param contextClass optional context class to extract classloading strategy from * @param contextClass optional context class to extract classloading strategy from
@@ -58,17 +81,76 @@ fun createEvaluationConfigurationFromTemplate(
contextClass: KClass<*> = ScriptEvaluationConfiguration::class, contextClass: KClass<*> = ScriptEvaluationConfiguration::class,
body: ScriptEvaluationConfiguration.Builder.() -> Unit = {} body: ScriptEvaluationConfiguration.Builder.() -> Unit = {}
): ScriptEvaluationConfiguration { ): ScriptEvaluationConfiguration {
val templateClass: KClass<*> = baseClassType.getTemplateClass(baseHostConfiguration, contextClass) val templateClass: KClass<*> = baseClassType.getTemplateClass(baseHostConfiguration, contextClass)
val mainAnnotation = templateClass.kotlinScriptAnnotation val mainAnnotation = templateClass.kotlinScriptAnnotation
return ScriptEvaluationConfiguration(scriptConfigInstance(mainAnnotation.evaluationConfiguration)) { val hostConfiguration = constructHostConfiguration(mainAnnotation.hostConfiguration, baseHostConfiguration) {}
hostConfiguration.update { it.withDefaultsFrom(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() 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<out ScriptingHostConfiguration>,
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( private fun ScriptCompilationConfiguration.Builder.propertiesFromTemplate(
templateClass: KClass<*>, baseClassType: KotlinType, mainAnnotation: KotlinScript templateClass: KClass<*>, baseClassType: KotlinType, mainAnnotation: KotlinScript
) { ) {
@@ -83,13 +165,14 @@ private val KClass<*>.kotlinScriptAnnotation: KotlinScript
?: when (this@kotlinScriptAnnotation.qualifiedName) { ?: when (this@kotlinScriptAnnotation.qualifiedName) {
// Any is the default template, so use a default annotation // Any is the default template, so use a default annotation
Any::class.qualifiedName, 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.SimpleScriptTemplate",
"$SCRIPT_RUNTIME_TEMPLATES_PACKAGE.ScriptTemplateWithArgs", "$SCRIPT_RUNTIME_TEMPLATES_PACKAGE.ScriptTemplateWithArgs",
"$SCRIPT_RUNTIME_TEMPLATES_PACKAGE.ScriptTemplateWithBindings" -> DummyScriptTemplate::class.findAnnotation<KotlinScript>() "$SCRIPT_RUNTIME_TEMPLATES_PACKAGE.ScriptTemplateWithBindings",
-> DummyScriptTemplate::class.findAnnotation()
else -> null 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<*> { private fun KotlinType.getTemplateClass(hostConfiguration: ScriptingHostConfiguration, contextClass: KClass<*>): KClass<*> {
val getScriptingClass = hostConfiguration[ScriptingHostConfiguration.getScriptingClass] val getScriptingClass = hostConfiguration[ScriptingHostConfiguration.getScriptingClass]
@@ -98,7 +181,7 @@ private fun KotlinType.getTemplateClass(hostConfiguration: ScriptingHostConfigur
return try { return try {
getScriptingClass(this, contextClass, hostConfiguration) getScriptingClass(this, contextClass, hostConfiguration)
} catch (e: Throwable) { } 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 <reified T : Annotation> KClass<*>.findAnnotation(): T? =
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
this.java.annotations.firstOrNull { it is T } as T? this.java.annotations.firstOrNull { it is T } as T?
private fun <T : Any> KClass<T>.createInstance(): T { private inline fun <reified T : PropertiesCollection> scriptConfigInstance(kclass: KClass<out T>): T? =
// TODO: throw a meaningful exception kclass.objectInstance ?: run {
val noArgsConstructor = java.constructors.singleOrNull { it.parameters.isEmpty() } val noArgsConstructor = kclass.java.constructors.singleOrNull { it.parameters.isEmpty() }
?: throw IllegalArgumentException("Class should have a single no-arg constructor: $this") noArgsConstructor?.let { it.newInstance() as T }
}
@Suppress("UNCHECKED_CAST")
return noArgsConstructor.newInstance() as T
}
private fun <T : PropertiesCollection> scriptConfigInstance(kclass: KClass<out T>): 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)
}
@@ -17,7 +17,7 @@ interface ScriptingHostConfigurationKeys
* The container for script evaluation configuration * The container for script evaluation configuration
* For usages see actual code examples * For usages see actual code examples
*/ */
class ScriptingHostConfiguration(baseScriptingConfigurations: Iterable<ScriptingHostConfiguration>, body: Builder.() -> Unit) : open class ScriptingHostConfiguration(baseScriptingConfigurations: Iterable<ScriptingHostConfiguration>, body: Builder.() -> Unit) :
PropertiesCollection(Builder(baseScriptingConfigurations).apply(body).data) { PropertiesCollection(Builder(baseScriptingConfigurations).apply(body).data) {
constructor(body: Builder.() -> Unit = {}) : this(emptyList(), body) constructor(body: Builder.() -> Unit = {}) : this(emptyList(), body)
@@ -11,15 +11,14 @@ import java.io.File
import javax.script.Bindings import javax.script.Bindings
import javax.script.ScriptContext import javax.script.ScriptContext
import javax.script.ScriptEngine import javax.script.ScriptEngine
import kotlin.script.experimental.api.ScriptCompilationConfiguration
import kotlin.script.experimental.api.dependencies import kotlin.script.experimental.api.dependencies
import kotlin.script.experimental.api.with
import kotlin.script.experimental.jvm.JvmDependencyFromClassLoader import kotlin.script.experimental.jvm.JvmDependencyFromClassLoader
import kotlin.script.experimental.jvm.JvmScriptCompilationConfigurationBuilder import kotlin.script.experimental.jvm.JvmScriptCompilationConfigurationBuilder
import kotlin.script.experimental.jvm.jvm import kotlin.script.experimental.jvm.jvm
import kotlin.script.experimental.jvm.updateClasspath import kotlin.script.experimental.jvm.updateClasspath
import kotlin.script.experimental.jvm.util.scriptCompilationClasspathFromContext import kotlin.script.experimental.jvm.util.scriptCompilationClasspathFromContext
import kotlin.script.experimental.jvmhost.createJvmCompilationConfigurationFromTemplate import kotlin.script.experimental.jvmhost.createJvmScriptDefinitionFromTemplate
import kotlin.script.experimental.jvmhost.createJvmEvaluationConfigurationFromTemplate
import kotlin.script.experimental.jvmhost.jsr223.KotlinJsr223ScriptEngineImpl 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() { class KotlinJsr223DefaultScriptEngineFactory : KotlinJsr223JvmScriptEngineFactoryBase() {
private val compilationConfiguration = createJvmCompilationConfigurationFromTemplate<KotlinJsr223DefaultScript>() private val scriptDefinition = createJvmScriptDefinitionFromTemplate<KotlinJsr223DefaultScript>()
private val evaluationConfiguration = createJvmEvaluationConfigurationFromTemplate<KotlinJsr223DefaultScript>()
private var lastClassLoader: ClassLoader? = null private var lastClassLoader: ClassLoader? = null
private var lastClassPath: List<File>? = null private var lastClassPath: List<File>? = null
@Synchronized @Synchronized
protected fun JvmScriptCompilationConfigurationBuilder.dependenciesFromCurrentContext() { private fun JvmScriptCompilationConfigurationBuilder.dependenciesFromCurrentContext() {
val currentClassLoader = Thread.currentThread().contextClassLoader val currentClassLoader = Thread.currentThread().contextClassLoader
val classPath = if (lastClassLoader == null || lastClassLoader != currentClassLoader) { val classPath = if (lastClassLoader == null || lastClassLoader != currentClassLoader) {
scriptCompilationClasspathFromContext( scriptCompilationClasspathFromContext(
@@ -55,7 +53,7 @@ class KotlinJsr223DefaultScriptEngineFactory : KotlinJsr223JvmScriptEngineFactor
override fun getScriptEngine(): ScriptEngine = override fun getScriptEngine(): ScriptEngine =
KotlinJsr223ScriptEngineImpl( KotlinJsr223ScriptEngineImpl(
this, this,
ScriptCompilationConfiguration(compilationConfiguration) { scriptDefinition.compilationConfiguration.with {
jvm { jvm {
if (System.getProperty(KOTLIN_JSR223_RESOLVE_FROM_CLASSLOADER_PROPERTY) == "true") { if (System.getProperty(KOTLIN_JSR223_RESOLVE_FROM_CLASSLOADER_PROPERTY) == "true") {
dependencies(JvmDependencyFromClassLoader { Thread.currentThread().contextClassLoader }) 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)) } ) { ScriptArgsWithTypes(arrayOf(it.getBindings(ScriptContext.ENGINE_SCOPE).orEmpty()), arrayOf(Bindings::class)) }
} }
@@ -54,12 +54,11 @@ class ScriptingHostTest : TestCase() {
val greeting = "Hello from script!" val greeting = "Hello from script!"
val output = captureOut { val output = captureOut {
val basicJvmScriptingHost = BasicJvmScriptingHost() val basicJvmScriptingHost = BasicJvmScriptingHost()
basicJvmScriptingHost.eval( basicJvmScriptingHost.evalWithTemplate<SimpleScript>(
"println(\"$greeting\")".toScriptSource("name"), "println(\"$greeting\")".toScriptSource("name"),
createJvmCompilationConfigurationFromTemplate<SimpleScript>(basicJvmScriptingHost.hostConfiguration) { compilation = {
updateClasspath(classpathFromClass<SimpleScript>()) updateClasspath(classpathFromClass<SimpleScript>())
}, }
createJvmEvaluationConfigurationFromTemplate<SimpleScript>(basicJvmScriptingHost.hostConfiguration)
).throwOnFailure() ).throwOnFailure()
} }
Assert.assertEquals(greeting, output) Assert.assertEquals(greeting, output)
@@ -206,15 +205,19 @@ class ScriptingHostTest : TestCase() {
fun testSimpleImportWithImplicitReceiver() { fun testSimpleImportWithImplicitReceiver() {
val greeting = listOf("Hello from helloWithVal script!", "Hello from imported helloWithVal script!") val greeting = listOf("Hello from helloWithVal script!", "Hello from imported helloWithVal script!")
val script = "println(\"Hello from imported \$helloScriptName script!\")" val script = "println(\"Hello from imported \$helloScriptName script!\")"
val compilationConfiguration = createJvmCompilationConfigurationFromTemplate<SimpleScriptTemplate> { val definition = createJvmScriptDefinitionFromTemplate<SimpleScriptTemplate>(
makeSimpleConfigurationWithTestImport() compilation = {
implicitReceivers(String::class) makeSimpleConfigurationWithTestImport()
} implicitReceivers(String::class)
val evaluationConfiguration = createJvmEvaluationConfigurationFromTemplate<SimpleScriptTemplate> { },
implicitReceivers("abc") evaluation = {
} implicitReceivers("abc")
}
)
val output = captureOut { val output = captureOut {
BasicJvmScriptingHost().eval(script.toScriptSource(), compilationConfiguration, evaluationConfiguration).throwOnFailure() BasicJvmScriptingHost().eval(
script.toScriptSource(), definition.compilationConfiguration, definition.evaluationConfiguration
).throwOnFailure()
}.lines() }.lines()
Assert.assertEquals(greeting, output) Assert.assertEquals(greeting, output)
} }
@@ -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. * 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 package kotlin.script.experimental.jvmhost
import kotlin.script.experimental.api.* import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.ScriptingHostConfiguration import kotlin.script.experimental.host.*
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.BasicJvmScriptEvaluator
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
open class BasicJvmScriptingHost( open class BasicJvmScriptingHost(
val hostConfiguration: ScriptingHostConfiguration = defaultJvmScriptingHostConfiguration, val baseHostConfiguration: ScriptingHostConfiguration? = null,
compiler: JvmScriptCompiler = JvmScriptCompiler(hostConfiguration), compiler: JvmScriptCompiler = JvmScriptCompiler(baseHostConfiguration.withDefaultsFrom(defaultJvmScriptingHostConfiguration)),
evaluator: ScriptEvaluator = BasicJvmScriptEvaluator() evaluator: ScriptEvaluator = BasicJvmScriptEvaluator()
) : BasicScriptingHost(compiler, evaluator) { ) : BasicScriptingHost(compiler, evaluator) {
@@ -23,31 +22,43 @@ open class BasicJvmScriptingHost(
script: SourceCode, script: SourceCode,
noinline compilation: ScriptCompilationConfiguration.Builder.() -> Unit = {}, noinline compilation: ScriptCompilationConfiguration.Builder.() -> Unit = {},
noinline evaluation: ScriptEvaluationConfiguration.Builder.() -> Unit = {} noinline evaluation: ScriptEvaluationConfiguration.Builder.() -> Unit = {}
): ResultWithDiagnostics<EvaluationResult> = ): ResultWithDiagnostics<EvaluationResult> {
eval( val definition =
script, createJvmScriptDefinitionFromTemplate<T>(baseHostConfiguration, compilation, evaluation)
createJvmCompilationConfigurationFromTemplate<T>(hostConfiguration, compilation), return eval(script, definition.compilationConfiguration, definition.evaluationConfiguration)
createJvmEvaluationConfigurationFromTemplate<T>(hostConfiguration, evaluation) }
)
} }
inline fun <reified T : Any> createJvmCompilationConfigurationFromTemplate( inline fun <reified T : Any> createJvmCompilationConfigurationFromTemplate(
hostConfiguration: ScriptingHostConfiguration = defaultJvmScriptingHostConfiguration, baseHostConfiguration: ScriptingHostConfiguration? = null,
noinline body: ScriptCompilationConfiguration.Builder.() -> Unit = {} noinline body: ScriptCompilationConfiguration.Builder.() -> Unit = {}
): ScriptCompilationConfiguration = createCompilationConfigurationFromTemplate( ): ScriptCompilationConfiguration = createCompilationConfigurationFromTemplate(
KotlinType(T::class), KotlinType(T::class),
hostConfiguration, baseHostConfiguration.withDefaultsFrom(defaultJvmScriptingHostConfiguration),
ScriptCompilationConfiguration::class, ScriptCompilationConfiguration::class,
body body
) )
inline fun <reified T : Any> createJvmEvaluationConfigurationFromTemplate( inline fun <reified T : Any> createJvmEvaluationConfigurationFromTemplate(
hostConfiguration: ScriptingHostConfiguration = defaultJvmScriptingHostConfiguration, baseHostConfiguration: ScriptingHostConfiguration? = null,
noinline body: ScriptEvaluationConfiguration.Builder.() -> Unit = {} noinline body: ScriptEvaluationConfiguration.Builder.() -> Unit = {}
): ScriptEvaluationConfiguration = createEvaluationConfigurationFromTemplate( ): ScriptEvaluationConfiguration = createEvaluationConfigurationFromTemplate(
KotlinType(T::class), KotlinType(T::class),
hostConfiguration, baseHostConfiguration.withDefaultsFrom(defaultJvmScriptingHostConfiguration),
ScriptEvaluationConfiguration::class, ScriptEvaluationConfiguration::class,
body body
) )
inline fun <reified T : Any> 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
)
@@ -11,25 +11,24 @@ import kotlin.coroutines.EmptyCoroutineContext
import kotlin.coroutines.startCoroutine import kotlin.coroutines.startCoroutine
import kotlin.script.experimental.api.* import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.createEvaluationConfigurationFromTemplate import kotlin.script.experimental.host.createEvaluationConfigurationFromTemplate
import kotlin.script.experimental.host.withDefaultsFrom
import kotlin.script.experimental.jvm.impl.createScriptFromClassLoader import kotlin.script.experimental.jvm.impl.createScriptFromClassLoader
@Suppress("unused") // script codegen generates a call to it @Suppress("unused") // script codegen generates a call to it
fun runCompiledScript(scriptClass: Class<*>, vararg args: String) { fun runCompiledScript(scriptClass: Class<*>, vararg args: String) {
val script = createScriptFromClassLoader(scriptClass.name, scriptClass.classLoader) val script = createScriptFromClassLoader(scriptClass.name, scriptClass.classLoader)
val evaluator = BasicJvmScriptEvaluator() val evaluator = BasicJvmScriptEvaluator()
val hostConfiguration = script.compilationConfiguration[ScriptCompilationConfiguration.hostConfiguration] val evaluationConfiguration =
?: defaultJvmScriptingHostConfiguration
val baseEvaluationConfiguration =
createEvaluationConfigurationFromTemplate( createEvaluationConfigurationFromTemplate(
script.compilationConfiguration[ScriptCompilationConfiguration.baseClass]!!, script.compilationConfiguration[ScriptCompilationConfiguration.baseClass]!!,
hostConfiguration, script.compilationConfiguration[ScriptCompilationConfiguration.hostConfiguration]
.withDefaultsFrom(defaultJvmScriptingHostConfiguration),
scriptClass.kotlin scriptClass.kotlin
) ) {
val evaluationConfiguration = ScriptEvaluationConfiguration(baseEvaluationConfiguration) { jvm {
jvm { mainArguments(args)
mainArguments(args) }
} }
}
runScriptSuspend { runScriptSuspend {
evaluator(script, evaluationConfiguration).onFailure { evaluator(script, evaluationConfiguration).onFailure {
it.reports.forEach(System.err::println) it.reports.forEach(System.err::println)
@@ -15,21 +15,23 @@ import kotlin.script.experimental.host.toScriptSource
import kotlin.script.experimental.jvm.baseClassLoader import kotlin.script.experimental.jvm.baseClassLoader
import kotlin.script.experimental.jvm.jvm import kotlin.script.experimental.jvm.jvm
import kotlin.script.experimental.jvmhost.BasicJvmScriptingHost 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<EvaluationResult> = fun evalFile(scriptFile: File, cacheDir: File? = null): ResultWithDiagnostics<EvaluationResult> =
withProperty(COMPILED_SCRIPTS_CACHE_DIR_PROPERTY, cacheDir?.absolutePath ?: "") { withProperty(COMPILED_SCRIPTS_CACHE_DIR_PROPERTY, cacheDir?.absolutePath ?: "") {
val scriptDefinition = createJvmCompilationConfigurationFromTemplate<MainKtsScript>() val scriptDefinition = createJvmScriptDefinitionFromTemplate<MainKtsScript>(
evaluation = {
val evaluationEnv = ScriptEvaluationConfiguration { jvm {
jvm { baseClassLoader(null)
baseClassLoader(null) }
constructorArgs(emptyArray<String>())
enableScriptsInstancesSharing()
} }
constructorArgs(emptyArray<String>()) )
enableScriptsInstancesSharing()
}
BasicJvmScriptingHost().eval(scriptFile.toScriptSource(), scriptDefinition, evaluationEnv) BasicJvmScriptingHost().eval(
scriptFile.toScriptSource(), scriptDefinition.compilationConfiguration, scriptDefinition.evaluationConfiguration
)
} }
@@ -12,26 +12,25 @@ import java.io.File
import javax.script.ScriptEngine import javax.script.ScriptEngine
import kotlin.script.experimental.api.ScriptCompilationConfiguration import kotlin.script.experimental.api.ScriptCompilationConfiguration
import kotlin.script.experimental.api.fileExtension import kotlin.script.experimental.api.fileExtension
import kotlin.script.experimental.api.with
import kotlin.script.experimental.jvm.JvmScriptCompilationConfigurationBuilder import kotlin.script.experimental.jvm.JvmScriptCompilationConfigurationBuilder
import kotlin.script.experimental.jvm.dependenciesFromCurrentContext
import kotlin.script.experimental.jvm.jvm import kotlin.script.experimental.jvm.jvm
import kotlin.script.experimental.jvm.updateClasspath import kotlin.script.experimental.jvm.updateClasspath
import kotlin.script.experimental.jvm.util.scriptCompilationClasspathFromContext import kotlin.script.experimental.jvm.util.scriptCompilationClasspathFromContext
import kotlin.script.experimental.jvmhost.createJvmCompilationConfigurationFromTemplate import kotlin.script.experimental.jvmhost.createJvmScriptDefinitionFromTemplate
import kotlin.script.experimental.jvmhost.createJvmEvaluationConfigurationFromTemplate
import kotlin.script.experimental.jvmhost.jsr223.KotlinJsr223ScriptEngineImpl import kotlin.script.experimental.jvmhost.jsr223.KotlinJsr223ScriptEngineImpl
class KotlinJsr223MainKtsScriptEngineFactory : KotlinJsr223JvmScriptEngineFactoryBase() { class KotlinJsr223MainKtsScriptEngineFactory : KotlinJsr223JvmScriptEngineFactoryBase() {
private val compilationConfiguration = createJvmCompilationConfigurationFromTemplate<MainKtsScript>() private val scriptDefinition = createJvmScriptDefinitionFromTemplate<MainKtsScript>()
private val evaluationConfiguration = createJvmEvaluationConfigurationFromTemplate<MainKtsScript>()
private var lastClassLoader: ClassLoader? = null private var lastClassLoader: ClassLoader? = null
private var lastClassPath: List<File>? = null private var lastClassPath: List<File>? = null
override fun getExtensions(): List<String> = listOf(compilationConfiguration[ScriptCompilationConfiguration.fileExtension]!!) override fun getExtensions(): List<String> =
listOf(scriptDefinition.compilationConfiguration[ScriptCompilationConfiguration.fileExtension]!!)
@Synchronized @Synchronized
protected fun JvmScriptCompilationConfigurationBuilder.dependenciesFromCurrentContext() { private fun JvmScriptCompilationConfigurationBuilder.dependenciesFromCurrentContext() {
val currentClassLoader = Thread.currentThread().contextClassLoader val currentClassLoader = Thread.currentThread().contextClassLoader
val classPath = if (lastClassLoader == null || lastClassLoader != currentClassLoader) { val classPath = if (lastClassLoader == null || lastClassLoader != currentClassLoader) {
scriptCompilationClasspathFromContext( scriptCompilationClasspathFromContext(
@@ -49,12 +48,12 @@ class KotlinJsr223MainKtsScriptEngineFactory : KotlinJsr223JvmScriptEngineFactor
override fun getScriptEngine(): ScriptEngine = override fun getScriptEngine(): ScriptEngine =
KotlinJsr223ScriptEngineImpl( KotlinJsr223ScriptEngineImpl(
this, this,
ScriptCompilationConfiguration(compilationConfiguration) { scriptDefinition.compilationConfiguration.with {
jvm { jvm {
dependenciesFromCurrentContext() dependenciesFromCurrentContext()
} }
}, },
evaluationConfiguration scriptDefinition.evaluationConfiguration
) { ScriptArgsWithTypes(arrayOf(emptyArray<String>()), arrayOf(Array<String>::class)) } ) { ScriptArgsWithTypes(arrayOf(emptyArray<String>()), arrayOf(Array<String>::class)) }
} }
@@ -35,7 +35,8 @@ import kotlin.script.experimental.jvmhost.jsr223.jsr223
@KotlinScript( @KotlinScript(
fileExtension = "main.kts", fileExtension = "main.kts",
compilationConfiguration = MainKtsScriptDefinition::class, compilationConfiguration = MainKtsScriptDefinition::class,
evaluationConfiguration = MainKtsEvaluationConfiguration::class evaluationConfiguration = MainKtsEvaluationConfiguration::class,
hostConfiguration = MainKtsHostConfiguration::class
) )
abstract class MainKtsScript(val args: Array<String>) abstract class MainKtsScript(val args: Array<String>)
@@ -58,26 +59,8 @@ class MainKtsScriptDefinition : ScriptCompilationConfiguration(
jsr223 { jsr223 {
importAllBindings(true) 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( 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<ScriptEvaluationConfiguration> { fun configureConstructorArgsFromMainArgs(context: ScriptEvaluationConfigurationRefinementContext): ResultWithDiagnostics<ScriptEvaluationConfiguration> {
val mainArgs = context.evaluationConfiguration[ScriptEvaluationConfiguration.jvm.mainArguments] val mainArgs = context.evaluationConfiguration[ScriptEvaluationConfiguration.jvm.mainArguments]
val res = if (context.evaluationConfiguration[ScriptEvaluationConfiguration.constructorArgs] == null && mainArgs != null) { val res = if (context.evaluationConfiguration[ScriptEvaluationConfiguration.constructorArgs] == null && mainArgs != null) {
@@ -8,14 +8,11 @@ package org.jetbrains.kotlin.scripting.definitions
import java.io.File import java.io.File
import kotlin.script.experimental.annotations.KotlinScript import kotlin.script.experimental.annotations.KotlinScript
import kotlin.script.experimental.api.* import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.ScriptingHostConfiguration import kotlin.script.experimental.host.*
import kotlin.script.experimental.host.configurationDependencies
import kotlin.script.experimental.host.createCompilationConfigurationFromTemplate
import kotlin.script.experimental.host.createEvaluationConfigurationFromTemplate
import kotlin.script.experimental.jvm.JvmDependency import kotlin.script.experimental.jvm.JvmDependency
class LazyScriptDefinitionFromDiscoveredClass internal constructor( class LazyScriptDefinitionFromDiscoveredClass internal constructor(
baseHostConfiguration: ScriptingHostConfiguration, private val baseHostConfiguration: ScriptingHostConfiguration,
private val annotationsFromAsm: ArrayList<BinAnnData>, private val annotationsFromAsm: ArrayList<BinAnnData>,
private val className: String, private val className: String,
private val classpath: List<File>, private val classpath: List<File>,
@@ -30,31 +27,21 @@ class LazyScriptDefinitionFromDiscoveredClass internal constructor(
messageReporter: MessageReporter messageReporter: MessageReporter
) : this(baseHostConfiguration, loadAnnotationsFromClass(classBytes), className, classpath, messageReporter) ) : this(baseHostConfiguration, loadAnnotationsFromClass(classBytes), className, classpath, messageReporter)
override val hostConfiguration: ScriptingHostConfiguration by lazy(LazyThreadSafetyMode.PUBLICATION) { private val definition: kotlin.script.experimental.host.ScriptDefinition by lazy(LazyThreadSafetyMode.PUBLICATION) {
ScriptingHostConfiguration(baseHostConfiguration) {
configurationDependencies.append(JvmDependency(classpath))
}
}
private val configurations by lazy(LazyThreadSafetyMode.PUBLICATION) {
messageReporter( messageReporter(
ScriptDiagnostic.Severity.DEBUG, ScriptDiagnostic.Severity.DEBUG,
"Configure scripting: loading script definition class $className using classpath $classpath\n. ${Thread.currentThread().stackTrace}" "Configure scripting: loading script definition class $className using classpath $classpath\n. ${Thread.currentThread().stackTrace}"
) )
try { try {
val compileCfg = createScriptDefinitionFromTemplate(
createCompilationConfigurationFromTemplate( KotlinType(className),
KotlinType(className), baseHostConfiguration.with {
hostConfiguration, if (classpath.isNotEmpty()) {
LazyScriptDefinitionFromDiscoveredClass::class configurationDependencies.append(JvmDependency(classpath))
) }
val evalCfg = },
createEvaluationConfigurationFromTemplate( LazyScriptDefinitionFromDiscoveredClass::class
KotlinType(className), )
hostConfiguration,
LazyScriptDefinitionFromDiscoveredClass::class
)
compileCfg to evalCfg
} catch (ex: ClassNotFoundException) { } catch (ex: ClassNotFoundException) {
messageReporter(ScriptDiagnostic.Severity.ERROR, "Cannot find script definition class $className") messageReporter(ScriptDiagnostic.Severity.ERROR, "Cannot find script definition class $className")
InvalidScriptDefinition InvalidScriptDefinition
@@ -67,8 +54,11 @@ class LazyScriptDefinitionFromDiscoveredClass internal constructor(
} }
} }
override val compilationConfiguration: ScriptCompilationConfiguration get() = configurations.first override val hostConfiguration: ScriptingHostConfiguration
override val evaluationConfiguration: ScriptEvaluationConfiguration get() = configurations.second 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) { override val fileExtension: String by lazy(LazyThreadSafetyMode.PUBLICATION) {
annotationsFromAsm.find { it.name == KotlinScript::class.java.simpleName }?.args 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())
@@ -11,9 +11,7 @@ import org.jetbrains.kotlin.scripting.resolve.KotlinScriptDefinitionFromAnnotate
import java.io.File import java.io.File
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.script.experimental.api.* import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.ScriptingHostConfiguration import kotlin.script.experimental.host.*
import kotlin.script.experimental.host.createCompilationConfigurationFromTemplate
import kotlin.script.experimental.host.createEvaluationConfigurationFromTemplate
import kotlin.script.experimental.jvm.baseClassLoader import kotlin.script.experimental.jvm.baseClassLoader
import kotlin.script.experimental.jvm.jvm import kotlin.script.experimental.jvm.jvm
@@ -181,14 +179,19 @@ abstract class ScriptDefinition : UserDataHolderBase() {
) : FromConfigurationsBase() ) : FromConfigurationsBase()
open class FromTemplate( open class FromTemplate(
hostConfiguration: ScriptingHostConfiguration, private val baseHostConfiguration: ScriptingHostConfiguration,
template: KClass<*>, template: KClass<*>,
contextClass: KClass<*> = ScriptCompilationConfiguration::class contextClass: KClass<*> = ScriptCompilationConfiguration::class
) : FromConfigurations( ) : FromConfigurationsBase() {
hostConfiguration,
createCompilationConfigurationFromTemplate(KotlinType(template), hostConfiguration, contextClass), private val definition = createScriptDefinitionFromTemplate(KotlinType(template), baseHostConfiguration, contextClass)
createEvaluationConfigurationFromTemplate(KotlinType(template), hostConfiguration, 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 { companion object {
fun getDefault(hostConfiguration: ScriptingHostConfiguration) = fun getDefault(hostConfiguration: ScriptingHostConfiguration) =