Implement base jvm scripting infrastructure

This commit is contained in:
Ilya Chernikov
2018-02-14 17:05:37 +01:00
parent aac7f97121
commit c48a74b84a
11 changed files with 542 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
import org.jetbrains.kotlin.gradle.dsl.Coroutines
@@ -0,0 +1,96 @@
/*
* Copyright 2000-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.jvm.impl
import kotlinx.coroutines.experimental.runBlocking
import java.io.File
import kotlin.script.dependencies.Environment
import kotlin.script.dependencies.ScriptContents
import kotlin.script.experimental.api.*
import kotlin.script.experimental.dependencies.AsyncDependenciesResolver
import kotlin.script.experimental.dependencies.DependenciesResolver
import kotlin.script.experimental.dependencies.ScriptDependencies
import kotlin.script.experimental.dependencies.ScriptReport
import kotlin.script.experimental.host.toScriptSource
import kotlin.script.experimental.jvm.JvmDependency
import kotlin.script.experimental.jvm.defaultConfiguration
import kotlin.script.experimental.jvm.mapToLegacyScriptReportPosition
import kotlin.script.experimental.jvm.mapToLegacyScriptReportSeverity
class BridgeDependenciesResolver(
val scriptConfigurator: ScriptConfigurator?,
val baseScriptCompilerConfiguration: ScriptCompileConfiguration = scriptConfigurator.defaultConfiguration,
val onClasspathUpdated: (List<File>) -> Unit = {}
) : AsyncDependenciesResolver {
override fun resolve(scriptContents: ScriptContents, environment: Environment): DependenciesResolver.ResolveResult =
runBlocking {
resolveAsync(scriptContents, environment)
}
override suspend fun resolveAsync(scriptContents: ScriptContents, environment: Environment): DependenciesResolver.ResolveResult {
return try {
val diagnostics = arrayListOf<ScriptReport>()
val processedScriptData =
ProcessedScriptData(ProcessedScriptDataParams.annotations to scriptContents.annotations)
val scriptCompilerConfiguration = baseScriptCompilerConfiguration.cloneWith(
ScriptCompileConfigurationParams.scriptSourceFragments to scriptContents.toScriptSourceFragments()
)
val refinedConfiguration = scriptConfigurator?.let {
val res = scriptConfigurator.refineConfiguration(scriptCompilerConfiguration, processedScriptData)
when (res) {
is ResultWithDiagnostics.Failure ->
return@resolveAsync DependenciesResolver.ResolveResult.Failure(res.reports.mapScriptReportsToDiagnostics())
is ResultWithDiagnostics.Success -> {
diagnostics.addAll(res.reports.mapScriptReportsToDiagnostics())
res.value
}
}
} ?: scriptCompilerConfiguration
val newClasspath = refinedConfiguration.getOrNull(ScriptCompileConfigurationParams.dependencies)
?.flatMap { (it as JvmDependency).classpath } ?: emptyList()
if (refinedConfiguration != scriptCompilerConfiguration) {
val oldClasspath = scriptCompilerConfiguration.getOrNull(ScriptCompileConfigurationParams.dependencies)
?.flatMap { (it as JvmDependency).classpath } ?: emptyList()
if (newClasspath != oldClasspath) {
onClasspathUpdated(newClasspath)
}
}
DependenciesResolver.ResolveResult.Success(
ScriptDependencies(
classpath = newClasspath, // TODO: maybe it should return only increment from the initial config
imports = refinedConfiguration.getOrNull(ScriptCompileConfigurationParams.importedPackages)?.toList()
?: emptyList()
),
diagnostics
)
} catch (e: Throwable) {
DependenciesResolver.ResolveResult.Failure(
ScriptReport(
e.message ?: "unknown error $e"
)
)
}
}
}
internal fun List<ScriptDiagnostic>.mapScriptReportsToDiagnostics() =
map { ScriptReport(it.message, mapToLegacyScriptReportSeverity(it.severity), mapToLegacyScriptReportPosition(it.location)) }
internal fun ScriptContents.toScriptSourceFragments(): ScriptSourceFragments =
ScriptSourceFragments(
when {
text != null -> text!!.toString().toScriptSource()
file != null -> file!!.toScriptSource()
else -> throw IllegalArgumentException("Unable to convert script contents $this into script source")
},
null
)
@@ -0,0 +1,53 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
@file:Suppress("unused")
package kotlin.script.experimental.jvm
import kotlin.script.experimental.api.*
open class JvmScriptCompiler(
val compilerProxy: KJVMCompilerProxy,
val cache: CompiledJvmScriptsCache
) : ScriptCompiler {
override suspend fun compile(configuration: ScriptCompileConfiguration, configurator: ScriptConfigurator?): ResultWithDiagnostics<CompiledScript<*>> {
val refinedConfiguration = configurator?.refineConfiguration(configuration)?.let {
when (it) {
is ResultWithDiagnostics.Failure -> return it
is ResultWithDiagnostics.Success -> it.value
?: return ResultWithDiagnostics.Failure("Null script compile configuration received".asErrorDiagnostics())
}
} ?: configuration
val cached = cache[refinedConfiguration[ScriptCompileConfigurationParams.scriptSourceFragments]]
if (cached != null) return cached.asSuccess()
return compilerProxy.compile(refinedConfiguration, configurator).also {
if (it is ResultWithDiagnostics.Success) {
cache.store(it.value as CompiledScript<*>)
}
}
}
}
interface CompiledJvmScriptsCache {
operator fun get(script: ScriptSourceFragments): CompiledScript<*>?
fun store(compiledScript: CompiledScript<*>)
}
interface KJVMCompilerProxy {
fun compile(
scriptCompilerConfiguration: ScriptCompileConfiguration,
configurator: ScriptConfigurator?
): ResultWithDiagnostics<CompiledScript<*>>
}
class DummyCompiledJvmScriptCache : CompiledJvmScriptsCache {
override operator fun get(script: ScriptSourceFragments): CompiledScript<*>? = null
override fun store(compiledScript: CompiledScript<*>) {}
}
@@ -0,0 +1,19 @@
/*
* Copyright 2000-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.jvm
import kotlinx.coroutines.experimental.runBlocking
import kotlin.script.experimental.api.*
import java.io.File
fun jvmConfigWithJavaHome(vararg params: Pair<TypedKey<*>, Any?>): ScriptCompileConfiguration =
ScriptCompileConfiguration(
JvmScriptCompileConfigurationParams.javaHomeDir to File(System.getProperty("java.home")),
*params
)
val ScriptConfigurator?.defaultConfiguration: ScriptCompileConfiguration
get() = this?.let { runBlocking { baseConfiguration(null) } }?.resultOrNull() ?: ScriptCompileConfiguration()
@@ -0,0 +1,18 @@
/*
* Copyright 2000-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.jvm
import java.io.File
import kotlin.script.experimental.api.ScriptDependency
import kotlin.script.experimental.api.TypedKey
object JvmScriptCompileConfigurationParams {
val javaHomeDir = TypedKey<File>("javaHomeDir")
}
class JvmDependency(val classpath: Iterable<File>): ScriptDependency
@@ -0,0 +1,23 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
@file:Suppress("unused")
package kotlin.script.experimental.jvm
import kotlin.script.experimental.api.ScriptConfigurator
import kotlin.script.experimental.api.ScriptRunner
import kotlin.script.experimental.api.typedKey
import kotlin.script.experimental.host.BasicScriptingHost
open class JvmBasicScriptingHost<ScriptBase : Any>(
configurationExtractor: ScriptConfigurator,
compiler: JvmScriptCompiler,
runner: ScriptRunner<ScriptBase>
) : BasicScriptingHost<ScriptBase>(configurationExtractor, compiler, runner)
object JvmScriptEvaluationEnvironmentParams {
val baseClassLoader by typedKey<ClassLoader?>()
}
@@ -0,0 +1,34 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
@file:Suppress("unused")
package kotlin.script.experimental.jvm
import kotlin.script.experimental.api.ScriptDiagnostic
import kotlin.script.experimental.api.ScriptSource
import kotlin.script.dependencies.ScriptContents
import kotlin.script.dependencies.ScriptDependenciesResolver
import kotlin.script.experimental.dependencies.ScriptReport
fun mapLegacyDiagnosticSeverity(severity: ScriptDependenciesResolver.ReportSeverity): ScriptDiagnostic.Severity = when (severity) {
ScriptDependenciesResolver.ReportSeverity.ERROR -> ScriptDiagnostic.Severity.ERROR
ScriptDependenciesResolver.ReportSeverity.WARNING -> ScriptDiagnostic.Severity.WARNING
ScriptDependenciesResolver.ReportSeverity.INFO -> ScriptDiagnostic.Severity.INFO
ScriptDependenciesResolver.ReportSeverity.DEBUG -> ScriptDiagnostic.Severity.DEBUG
}
fun mapToLegacyScriptReportSeverity(severity: ScriptDiagnostic.Severity): ScriptReport.Severity = when (severity) {
ScriptDiagnostic.Severity.ERROR -> ScriptReport.Severity.ERROR
ScriptDiagnostic.Severity.WARNING -> ScriptReport.Severity.WARNING
ScriptDiagnostic.Severity.INFO -> ScriptReport.Severity.INFO
ScriptDiagnostic.Severity.DEBUG -> ScriptReport.Severity.DEBUG
}
fun mapLegacyScriptPosition(pos: ScriptContents.Position?): ScriptSource.Location? =
pos?.let { ScriptSource.Location(ScriptSource.Position(pos.line, pos.col)) }
fun mapToLegacyScriptReportPosition(pos: ScriptSource.Location?): ScriptReport.Position? =
pos?.let { ScriptReport.Position(pos.start.line, pos.start.col) }
@@ -0,0 +1,38 @@
/*
* Copyright 2000-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.jvm.runners
import kotlin.reflect.KClass
import kotlin.script.experimental.api.*
open class BasicJvmScriptRunner<ScriptBase : Any>(val baseClass: KClass<ScriptBase>? = null) : ScriptRunner<ScriptBase> {
override suspend fun run(
compiledScript: CompiledScript<ScriptBase>,
scriptEvaluationEnvironment: ScriptEvaluationEnvironment
): ResultWithDiagnostics<EvaluationResult> =
try {
val obj = compiledScript.instantiate(scriptEvaluationEnvironment)
when (obj) {
is ResultWithDiagnostics.Failure -> obj
is ResultWithDiagnostics.Success -> {
// in the future, when (if) we'll stop to compile everything into constructor
// run as SAM
// return res
val scriptObject = obj.value
if (scriptObject !is Class<*>)
ResultWithDiagnostics.Failure(ScriptDiagnostic("expecting class in this implementation, got ${scriptObject?.javaClass}"))
else {
scriptObject.getConstructor().newInstance()
ResultWithDiagnostics.Success(EvaluationResult(null, scriptEvaluationEnvironment))
}
}
}
} catch (e: Throwable) {
ResultWithDiagnostics.Failure(e.asDiagnostics())
}
}