Grand refactoring of the scripting DSL + many minor usability refactorings around it

This commit is contained in:
Ilya Chernikov
2018-07-30 17:55:13 +02:00
parent 45eaca1290
commit 74f8a541c0
41 changed files with 655 additions and 619 deletions
@@ -40,21 +40,28 @@ import kotlin.script.experimental.api.*
import kotlin.script.experimental.dependencies.DependenciesResolver
import kotlin.script.experimental.host.getMergedScriptText
import kotlin.script.experimental.jvm.JvmDependency
import kotlin.script.experimental.jvm.JvmScriptCompileConfigurationProperties
import kotlin.script.experimental.jvm.JvmScriptCompilationConfiguration
import kotlin.script.experimental.jvm.impl.BridgeDependenciesResolver
import kotlin.script.experimental.jvmhost.JvmScriptEvaluationEnvironmentProperties
import kotlin.script.experimental.jvm.javaHome
import kotlin.script.experimental.jvmhost.JvmScriptEvaluationEnvironment
import kotlin.script.experimental.jvmhost.KJvmCompilerProxy
import kotlin.script.experimental.util.chainPropertyBags
import kotlin.script.experimental.jvmhost.baseClassLoader
import kotlin.script.experimental.util.getFirstFromChainOrNull
import kotlin.script.experimental.util.getMergedFromChainOrNull
import kotlin.script.experimental.util.getOrError
import kotlin.script.experimental.util.getOrNull
class KJvmCompiledScript<out ScriptBase : Any>(
override val configuration: ScriptCompileConfiguration,
val generationState: GenerationState,
val scriptClassFQName: String
override val definition: ScriptDefinition,
override val additionalConfiguration: ScriptCompileConfiguration?,
private val generationState: GenerationState,
private val scriptClassFQName: String
) : CompiledScript<ScriptBase> {
override suspend fun instantiate(scriptEvaluationEnvironment: ScriptEvaluationEnvironment): ResultWithDiagnostics<ScriptBase> = try {
val baseClassLoader = scriptEvaluationEnvironment.getOrNull(JvmScriptEvaluationEnvironmentProperties.baseClassLoader)
val dependencies = configuration.getOrNull(ScriptDefinitionProperties.dependencies)
override suspend fun instantiate(scriptEvaluationEnvironment: ScriptEvaluationEnvironment?): ResultWithDiagnostics<ScriptBase> = try {
val baseClassLoader = scriptEvaluationEnvironment?.getOrNull(JvmScriptEvaluationEnvironment.baseClassLoader)
?: Thread.currentThread().contextClassLoader
val dependencies = additionalConfiguration?.getOrNull(ScriptDefinition.dependencies)
?.flatMap { (it as? JvmDependency)?.classpath?.map { it.toURI().toURL() } ?: emptyList() }
// TODO: previous dependencies and classloaders should be taken into account here
val classLoaderWithDeps =
@@ -75,7 +82,7 @@ class KJvmCompilerImpl(val hostEnvironment: ScriptingEnvironment) : KJvmCompiler
override fun compile(
script: ScriptSource,
scriptDefinition: ScriptDefinition,
additionalConfiguration: ScriptCompileConfiguration
additionalConfiguration: ScriptCompileConfiguration?
): ResultWithDiagnostics<CompiledScript<*>> {
val messageCollector = ScriptDiagnosticsMessageCollector()
@@ -85,37 +92,35 @@ class KJvmCompilerImpl(val hostEnvironment: ScriptingEnvironment) : KJvmCompiler
try {
setIdeaIoUseFallback()
val scriptCompileConfiguration = chainPropertyBags(additionalConfiguration, scriptDefinition)
var environment: KotlinCoreEnvironment? = null
var updatedScriptCompileConfiguration = scriptCompileConfiguration
var updatedScriptCompileConfiguration = additionalConfiguration
fun updateClasspath(classpath: List<File>) {
environment!!.updateClasspath(classpath.map(::JvmClasspathRoot))
val updatedDeps = updatedScriptCompileConfiguration.getOrNull(ScriptDefinitionProperties.dependencies)?.plus(
JvmDependency(classpath)
) ?: listOf(JvmDependency(classpath))
updatedScriptCompileConfiguration = ScriptCompileConfiguration(
updatedScriptCompileConfiguration,
ScriptDefinitionProperties.dependencies to updatedDeps
)
if (classpath.isNotEmpty()) {
updatedScriptCompileConfiguration = ScriptCompileConfiguration.create {
include(updatedScriptCompileConfiguration)
dependencies.append(JvmDependency(classpath))
}
}
}
val disposable = Disposer.newDisposable()
val kotlinCompilerConfiguration = org.jetbrains.kotlin.config.CompilerConfiguration().apply {
add(
JVMConfigurationKeys.SCRIPT_DEFINITIONS,
BridgeScriptDefinition(scriptDefinition, scriptCompileConfiguration, hostEnvironment, ::updateClasspath)
BridgeScriptDefinition(scriptDefinition, additionalConfiguration, hostEnvironment, ::updateClasspath)
)
put<MessageCollector>(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector)
put(JVMConfigurationKeys.RETAIN_OUTPUT_IN_MEMORY, true)
var isModularJava = false
scriptCompileConfiguration.getOrNull(JvmScriptCompileConfigurationProperties.javaHomeDir)?.let {
getFirstFromChainOrNull(JvmScriptCompilationConfiguration.javaHome, updatedScriptCompileConfiguration, hostEnvironment)?.let {
put(JVMConfigurationKeys.JDK_HOME, it)
isModularJava = CoreJrtFileSystem.isModularJdk(it)
}
scriptCompileConfiguration.getOrNull(ScriptDefinitionProperties.dependencies)?.let {
getMergedFromChainOrNull(ScriptDefinition.dependencies, scriptDefinition, updatedScriptCompileConfiguration)?.let {
addJvmClasspathRoots(
it.flatMap {
(it as JvmDependency).classpath
@@ -147,7 +152,7 @@ class KJvmCompilerImpl(val hostEnvironment: ScriptingEnvironment) : KJvmCompiler
val analyzerWithCompilerReport = AnalyzerWithCompilerReport(messageCollector, environment.configuration.languageVersionSettings)
val psiFileFactory: PsiFileFactoryImpl = PsiFileFactory.getInstance(environment.project) as PsiFileFactoryImpl
val scriptText = getMergedScriptText(script, scriptCompileConfiguration)
val scriptText = getMergedScriptText(script, updatedScriptCompileConfiguration)
val scriptFileName = "script" // TODO: extract from file/url if available
val virtualFile = LightVirtualFile(
"$scriptFileName${KotlinParserDefinition.STD_SCRIPT_EXT}",
@@ -192,7 +197,7 @@ class KJvmCompilerImpl(val hostEnvironment: ScriptingEnvironment) : KJvmCompiler
org.jetbrains.kotlin.codegen.CompilationErrorHandler.THROW_EXCEPTION
)
val res = KJvmCompiledScript<Any>(updatedScriptCompileConfiguration, generationState, scriptFileName.capitalize())
val res = KJvmCompiledScript<Any>(scriptDefinition, updatedScriptCompileConfiguration, generationState, scriptFileName.capitalize())
return ResultWithDiagnostics.Success(res, messageCollector.diagnostics)
} catch (ex: Throwable) {
@@ -238,23 +243,22 @@ class ScriptDiagnosticsMessageCollector : MessageCollector {
internal class BridgeScriptDefinition(
scriptDefinition: ScriptDefinition,
calculatedScriptCompilerConfiguration: ScriptCompileConfiguration,
additionalCompilationConfiguration: ScriptCompileConfiguration?,
hostEnvironment: ScriptingEnvironment,
updateClasspath: (List<File>) -> Unit
) : KotlinScriptDefinition(
hostEnvironment.getScriptingClass(
scriptDefinition[ScriptDefinitionProperties.baseClass],
scriptDefinition.getOrError(ScriptDefinition.baseClass),
BridgeScriptDefinition::class
)
) {
override val acceptedAnnotations = run {
val cl = this::class.java.classLoader
calculatedScriptCompilerConfiguration.getOrNull(ScriptDefinitionProperties.refineConfigurationOnAnnotations)
additionalCompilationConfiguration?.getOrNull(ScriptDefinition.refineConfigurationOnAnnotations)
?.map { (cl.loadClass(it.typeName) as Class<out Annotation>).kotlin }
?: emptyList()
}
override val dependencyResolver: DependenciesResolver =
BridgeDependenciesResolver(scriptDefinition, calculatedScriptCompilerConfiguration, updateClasspath)
BridgeDependenciesResolver(scriptDefinition, additionalCompilationConfiguration, updateClasspath)
}
@@ -0,0 +1,35 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package kotlin.script.experimental.jvmhost.impl
import kotlin.script.experimental.api.ScriptCompileConfiguration
import kotlin.script.experimental.api.ScriptingEnvironment
import kotlin.script.experimental.jvm.defaultJvmScriptingEnvironment
internal fun mergeConfigurations(vararg configurations: ScriptCompileConfiguration?): ScriptCompileConfiguration? {
val nonEmptyConfigurations = configurations.filter { it != null && it.properties.isNotEmpty() }
return when {
nonEmptyConfigurations.isEmpty() -> null
nonEmptyConfigurations.size == 1 -> nonEmptyConfigurations.first()!!
else -> ScriptCompileConfiguration.create {
for (configuration in nonEmptyConfigurations) {
include(configuration!!)
}
}
}
}
fun ScriptingEnvironment.withDefaults(): ScriptingEnvironment =
if (this == defaultJvmScriptingEnvironment || defaultJvmScriptingEnvironment.properties.all {
this.properties.containsKey(it.key)
}) {
this
} else {
ScriptingEnvironment.create {
include(defaultJvmScriptingEnvironment)
include(this)
}
}
@@ -10,21 +10,29 @@ package kotlin.script.experimental.jvmhost
import kotlin.script.experimental.api.*
import kotlin.script.experimental.jvm.defaultJvmScriptingEnvironment
import kotlin.script.experimental.jvmhost.impl.KJvmCompilerImpl
import kotlin.script.experimental.util.chainPropertyBags
import kotlin.script.experimental.jvmhost.impl.mergeConfigurations
import kotlin.script.experimental.jvmhost.impl.withDefaults
import kotlin.script.experimental.util.getOrNull
interface CompiledJvmScriptsCache {
fun get(script: ScriptSource, configuration: ScriptCompileConfiguration): CompiledScript<*>?
fun store(compiledScript: CompiledScript<*>, configuration: ScriptCompileConfiguration)
fun get(script: ScriptSource, scriptDefinition: ScriptDefinition, configuration: ScriptCompileConfiguration?): CompiledScript<*>?
fun store(compiledScript: CompiledScript<*>, scriptDefinition: ScriptDefinition, configuration: ScriptCompileConfiguration?)
object NoCache : CompiledJvmScriptsCache {
override fun get(script: ScriptSource, configuration: ScriptCompileConfiguration): CompiledScript<*>? = null
override fun store(compiledScript: CompiledScript<*>, configuration: ScriptCompileConfiguration) {}
override fun get(
script: ScriptSource, scriptDefinition: ScriptDefinition, configuration: ScriptCompileConfiguration?
): CompiledScript<*>? = null
override fun store(
compiledScript: CompiledScript<*>, scriptDefinition: ScriptDefinition, configuration: ScriptCompileConfiguration?
) {
}
}
}
open class JvmScriptCompiler(
hostEnvironment: ScriptingEnvironment = defaultJvmScriptingEnvironment,
val compilerProxy: KJvmCompilerProxy = KJvmCompilerImpl(hostEnvironment.cloneWithNewParent(defaultJvmScriptingEnvironment)),
val compilerProxy: KJvmCompilerProxy = KJvmCompilerImpl(hostEnvironment.withDefaults()),
val cache: CompiledJvmScriptsCache = CompiledJvmScriptsCache.NoCache
) : ScriptCompiler {
@@ -33,29 +41,29 @@ open class JvmScriptCompiler(
scriptDefinition: ScriptDefinition,
additionalConfiguration: ScriptCompileConfiguration?
): ResultWithDiagnostics<CompiledScript<*>> {
val baseConfiguration = chainPropertyBags(additionalConfiguration, scriptDefinition)
val refineConfigurationFn = baseConfiguration.getOrNull(ScriptDefinitionProperties.refineConfigurationHandler)
val refineConfigurationFn = scriptDefinition.getOrNull(ScriptDefinition.refineConfigurationHandler)
val refinedConfiguration =
if (baseConfiguration.getOrNull(ScriptDefinitionProperties.refineConfigurationBeforeParsing) == true) {
if (scriptDefinition.getOrNull(ScriptDefinition.refineConfigurationBeforeParsing) == true) {
if (refineConfigurationFn == null) {
return ResultWithDiagnostics.Failure("Non-null configurator expected".asErrorDiagnostics())
}
refineConfigurationFn(script, baseConfiguration).let {
refineConfigurationFn(script, scriptDefinition, additionalConfiguration).let {
when (it) {
is ResultWithDiagnostics.Failure -> return it
is ResultWithDiagnostics.Success -> it.value
}
}
} else {
baseConfiguration
null
}
val cached = cache.get(script, refinedConfiguration)
val actualConfiguration = mergeConfigurations(additionalConfiguration, refinedConfiguration)
val cached = cache.get(script, scriptDefinition, actualConfiguration)
if (cached != null) return cached.asSuccess()
return compilerProxy.compile(script, scriptDefinition, refinedConfiguration).also {
return compilerProxy.compile(script, scriptDefinition, actualConfiguration).also {
if (it is ResultWithDiagnostics.Success) {
cache.store(it.value, refinedConfiguration)
cache.store(it.value, scriptDefinition, actualConfiguration)
}
}
}
@@ -65,7 +73,7 @@ interface KJvmCompilerProxy {
fun compile(
script: ScriptSource,
scriptDefinition: ScriptDefinition,
additionalConfiguration: ScriptCompileConfiguration
additionalConfiguration: ScriptCompileConfiguration?
): ResultWithDiagnostics<CompiledScript<*>>
}
@@ -8,17 +8,23 @@
package kotlin.script.experimental.jvmhost
import kotlin.script.experimental.api.*
import kotlin.script.experimental.util.typedKey
import kotlin.script.experimental.util.PropertiesCollection
import kotlin.script.experimental.util.getOrNull
object JvmScriptEvaluationEnvironmentProperties {
val baseClassLoader by typedKey<ClassLoader?>(Thread.currentThread().contextClassLoader)
open class JvmScriptEvaluationEnvironment : PropertiesCollection.Builder() {
companion object : JvmScriptEvaluationEnvironment()
}
val JvmScriptEvaluationEnvironment.baseClassLoader by PropertiesCollection.key<ClassLoader?>(Thread.currentThread().contextClassLoader)
val ScriptEvaluationEnvironment.jvm get() = JvmScriptEvaluationEnvironment()
open class BasicJvmScriptEvaluator : ScriptEvaluator {
override suspend operator fun invoke(
compiledScript: CompiledScript<*>,
scriptEvaluationEnvironment: ScriptEvaluationEnvironment
scriptEvaluationEnvironment: ScriptEvaluationEnvironment?
): ResultWithDiagnostics<EvaluationResult> =
try {
val obj = compiledScript.instantiate(scriptEvaluationEnvironment)
@@ -32,7 +38,7 @@ open class BasicJvmScriptEvaluator : ScriptEvaluator {
if (scriptObject !is Class<*>)
ResultWithDiagnostics.Failure(ScriptDiagnostic("expecting class in this implementation, got ${scriptObject?.javaClass}"))
else {
val receivers = scriptEvaluationEnvironment.getOrNull(ScriptEvaluationEnvironmentParams.implicitReceivers)
val receivers = scriptEvaluationEnvironment?.getOrNull(ScriptEvaluationEnvironment.implicitReceivers)
val instance = if (receivers == null) {
scriptObject.getConstructor().newInstance()
} else {