[SCRIPTING] create extension for scripting in JS and extract common code with JVM part

This commit is contained in:
Vitaliy.Tikhonov
2019-08-30 16:17:18 +03:00
committed by romanart
parent c6c3d2de9e
commit 50c08b25d3
3 changed files with 282 additions and 111 deletions
@@ -0,0 +1,149 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.compiler.plugin
import com.intellij.core.JavaCoreProjectEnvironment
import kotlinx.coroutines.runBlocking
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.ExitCode
import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments
import org.jetbrains.kotlin.cli.common.extensions.ScriptEvaluationExtension
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.scripting.configuration.ScriptingConfigurationKeys
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinitionProvider
import java.io.File
import java.io.PrintStream
import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.toScriptSource
abstract class AbstractScriptEvaluationExtension : ScriptEvaluationExtension {
abstract fun getSourcePath(arguments: CommonCompilerArguments): String
abstract fun setupScriptConfiguration(configuration: CompilerConfiguration, sourcePath: String)
abstract fun createEnvironment(
projectEnvironment: JavaCoreProjectEnvironment,
configuration: CompilerConfiguration
): KotlinCoreEnvironment
abstract fun createScriptEvaluator(): ScriptEvaluator
abstract suspend fun compilerInvoke(
environment: KotlinCoreEnvironment,
script: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration
): ResultWithDiagnostics<CompiledScript<*>>
abstract suspend fun preprocessEvaluation(
scriptEvaluator: ScriptEvaluator,
scriptCompilationConfiguration: ScriptCompilationConfiguration,
evaluationConfiguration: ScriptEvaluationConfiguration
)
override fun eval(
arguments: CommonCompilerArguments,
configuration: CompilerConfiguration,
projectEnvironment: JavaCoreProjectEnvironment
): ExitCode {
val messageCollector = configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
val scriptDefinitionProvider = ScriptDefinitionProvider.getInstance(projectEnvironment.project)
if (scriptDefinitionProvider == null) {
messageCollector.report(CompilerMessageSeverity.ERROR, "Unable to process the script, scripting plugin is not configured")
return ExitCode.COMPILATION_ERROR
}
val sourcePath = getSourcePath(arguments)
setupScriptConfiguration(configuration, sourcePath)
val environment = createEnvironment(projectEnvironment, configuration)
val scriptFile = File(sourcePath)
if (scriptFile.isDirectory || !scriptDefinitionProvider.isScript(scriptFile)) {
val extensionHint =
if (configuration.get(ScriptingConfigurationKeys.SCRIPT_DEFINITIONS)?.let { it.size == 1 && it.first().isDefault } == true) " (.kts)"
else ""
messageCollector.report(CompilerMessageSeverity.ERROR, "Specify path to the script file$extensionHint as the first argument")
return ExitCode.COMPILATION_ERROR
}
val script = scriptFile.toScriptSource()
val definition = scriptDefinitionProvider.findDefinition(scriptFile) ?: scriptDefinitionProvider.getDefaultDefinition()
val scriptArgs =
if (arguments.freeArgs.isNotEmpty()) arguments.freeArgs.subList(1, arguments.freeArgs.size)
else emptyList<String>()
val evaluationConfiguration = definition.evaluationConfiguration.with {
constructorArgs(scriptArgs.toTypedArray())
}
val scriptCompilationConfiguration = definition.compilationConfiguration
val scriptEvaluator = createScriptEvaluator()
return runBlocking {
val compiledScript = compilerInvoke(
environment,
script,
scriptCompilationConfiguration
).valueOr {
for (report in it.reports) {
messageCollector.report(report.severity.toCompilerMessageSeverity(), report.render(withSeverity = false))
}
return@runBlocking ExitCode.COMPILATION_ERROR
}
preprocessEvaluation(scriptEvaluator, scriptCompilationConfiguration, evaluationConfiguration)
val evalResult = scriptEvaluator.invoke(compiledScript, evaluationConfiguration).valueOr {
for (report in it.reports) {
messageCollector.report(report.severity.toCompilerMessageSeverity(), report.render(withSeverity = false))
}
return@runBlocking ExitCode.INTERNAL_ERROR
}
when (evalResult.returnValue) {
is ResultValue.Value -> {
println((evalResult.returnValue as ResultValue.Value).value)
ExitCode.OK
}
is ResultValue.Error -> {
val errorValue = evalResult.returnValue as ResultValue.Error
errorValue.renderError(System.err)
ExitCode.SCRIPT_EXECUTION_ERROR
}
else -> ExitCode.OK
}
}
}
}
private fun ScriptDiagnostic.Severity.toCompilerMessageSeverity(): CompilerMessageSeverity =
when (this) {
ScriptDiagnostic.Severity.FATAL -> CompilerMessageSeverity.EXCEPTION
ScriptDiagnostic.Severity.ERROR -> CompilerMessageSeverity.ERROR
ScriptDiagnostic.Severity.WARNING -> CompilerMessageSeverity.WARNING
ScriptDiagnostic.Severity.INFO -> CompilerMessageSeverity.INFO
ScriptDiagnostic.Severity.DEBUG -> CompilerMessageSeverity.LOGGING
}
private fun ResultValue.Error.renderError(stream: PrintStream) {
val fullTrace = error.stackTrace
if (wrappingException == null || fullTrace.size < wrappingException!!.stackTrace.size) {
error.printStackTrace(stream)
} else {
// subtracting wrapping message stacktrace from error stacktrace to show only user-specific part of it
// TODO: consider more reliable logic, e.g. comparing traces, fallback to full error printing in case of mismatch
// TODO: write tests
stream.println(error)
val scriptTraceSize = fullTrace.size - wrappingException!!.stackTrace.size
for (i in 0 until scriptTraceSize) {
stream.println("\tat " + fullTrace[i])
}
}
}
@@ -0,0 +1,92 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.scripting.compiler.plugin
import com.intellij.core.JavaCoreProjectEnvironment
import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments
import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.scripting.configuration.ScriptingConfigurationKeys
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinition
import org.jetbrains.kotlin.scripting.definitions.platform
import org.jetbrains.kotlin.scripting.repl.js.CompiledToJsScript
import org.jetbrains.kotlin.scripting.repl.js.JsScriptCompiler
import org.jetbrains.kotlin.scripting.repl.js.JsScriptEvaluator
import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.ScriptingHostConfiguration
import kotlin.script.experimental.jvm.JsDependency
fun loadScriptConfiguration(configuration: CompilerConfiguration) {
val scriptConfiguration = ScriptCompilationConfiguration {
baseClass("kotlin.Any")
dependencies.append(JsDependency("compiler/ir/serialization.js/build/fullRuntime/klib"))
platform.put("JS")
}
configuration.add(
ScriptingConfigurationKeys.SCRIPT_DEFINITIONS,
ScriptDefinition.FromConfigurations(ScriptingHostConfiguration(), scriptConfiguration, null)
)
}
class JsScriptEvaluationExtension : AbstractScriptEvaluationExtension() {
override fun getSourcePath(arguments: CommonCompilerArguments): String {
return (arguments as K2JSCompilerArguments).scriptPath!!
}
override fun setupScriptConfiguration(configuration: CompilerConfiguration, sourcePath: String) {
loadScriptConfiguration(configuration)
}
override fun createEnvironment(
projectEnvironment: JavaCoreProjectEnvironment,
configuration: CompilerConfiguration
): KotlinCoreEnvironment {
return KotlinCoreEnvironment.createForProduction(
projectEnvironment,
configuration,
EnvironmentConfigFiles.JS_CONFIG_FILES
)
}
override fun createScriptEvaluator(): ScriptEvaluator {
return JsScriptEvaluator()
}
private var environment: KotlinCoreEnvironment? = null
private val scriptCompiler: JsScriptCompiler by lazy {
JsScriptCompiler(environment!!)
}
override suspend fun compilerInvoke(
environment: KotlinCoreEnvironment,
script: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration
): ResultWithDiagnostics<CompiledScript<*>> {
this.environment = environment
return scriptCompiler.invoke(script, scriptCompilationConfiguration)
}
override suspend fun preprocessEvaluation(
scriptEvaluator: ScriptEvaluator,
scriptCompilationConfiguration: ScriptCompilationConfiguration,
evaluationConfiguration: ScriptEvaluationConfiguration
) {
scriptEvaluator.invoke(
CompiledToJsScript(
scriptCompiler.scriptDependencyBinary,
scriptCompilationConfiguration
),
evaluationConfiguration
)
}
override fun isAccepted(arguments: CommonCompilerArguments): Boolean {
return arguments is K2JSCompilerArguments
}
}
@@ -6,132 +6,62 @@
package org.jetbrains.kotlin.scripting.compiler.plugin
import com.intellij.core.JavaCoreProjectEnvironment
import kotlinx.coroutines.runBlocking
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.ExitCode
import org.jetbrains.kotlin.cli.common.ExitCode.COMPILATION_ERROR
import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
import org.jetbrains.kotlin.cli.common.config.addKotlinSourceRoot
import org.jetbrains.kotlin.cli.common.extensions.ScriptEvaluationExtension
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.scripting.compiler.plugin.impl.ScriptJvmCompilerFromEnvironment
import org.jetbrains.kotlin.scripting.configuration.ScriptingConfigurationKeys
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinitionProvider
import java.io.File
import java.io.PrintStream
import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.toScriptSource
import kotlin.script.experimental.jvm.BasicJvmScriptEvaluator
import kotlin.script.experimental.jvm.baseClassLoader
import kotlin.script.experimental.jvm.jvm
class JvmCliScriptEvaluationExtension : ScriptEvaluationExtension {
override fun isAccepted(arguments: CommonCompilerArguments): Boolean =
arguments is K2JVMCompilerArguments && arguments.script
class JvmCliScriptEvaluationExtension : AbstractScriptEvaluationExtension() {
override fun eval(
arguments: CommonCompilerArguments,
configuration: CompilerConfiguration,
projectEnvironment: JavaCoreProjectEnvironment
): ExitCode {
val messageCollector = configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
val scriptDefinitionProvider = ScriptDefinitionProvider.getInstance(projectEnvironment.project)
if (scriptDefinitionProvider == null) {
messageCollector.report(CompilerMessageSeverity.ERROR, "Unable to process the script, scripting plugin is not configured")
return COMPILATION_ERROR
}
val sourcePath = arguments.freeArgs.first()
override fun getSourcePath(arguments: CommonCompilerArguments): String {
return arguments.freeArgs.first()
}
override fun setupScriptConfiguration(configuration: CompilerConfiguration, sourcePath: String) {
configuration.addKotlinSourceRoot(sourcePath)
configuration.put(JVMConfigurationKeys.RETAIN_OUTPUT_IN_MEMORY, true)
val environment =
KotlinCoreEnvironment.createForProduction(projectEnvironment, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
if (messageCollector.hasErrors()) return COMPILATION_ERROR
val scriptFile = File(sourcePath)
if (scriptFile.isDirectory || !scriptDefinitionProvider.isScript(scriptFile)) {
val extensionHint =
if (configuration.get(ScriptingConfigurationKeys.SCRIPT_DEFINITIONS)?.let { it.size == 1 && it.first().isDefault } == true) " (.kts)"
else ""
messageCollector.report(CompilerMessageSeverity.ERROR, "Specify path to the script file$extensionHint as the first argument")
return COMPILATION_ERROR
}
val script = scriptFile.toScriptSource()
val definition = scriptDefinitionProvider.findDefinition(scriptFile) ?: scriptDefinitionProvider.getDefaultDefinition()
val scriptArgs = arguments.freeArgs.subList(1, arguments.freeArgs.size)
val evaluationConfiguration = definition.evaluationConfiguration.with {
constructorArgs(scriptArgs.toTypedArray())
jvm {
baseClassLoader(null)
}
}
val scriptCompilationConfiguration = definition.compilationConfiguration
val scriptCompiler = ScriptJvmCompilerFromEnvironment(environment)
return runBlocking {
val compiledScript =
scriptCompiler.compile(script, scriptCompilationConfiguration)
.valueOr {
for (report in it.reports) {
messageCollector.report(report.severity.toCompilerMessageSeverity(), report.render(withSeverity = false))
}
return@runBlocking COMPILATION_ERROR
}
val evalResult = BasicJvmScriptEvaluator().invoke(compiledScript, evaluationConfiguration)
.valueOr {
for (report in it.reports) {
messageCollector.report(report.severity.toCompilerMessageSeverity(), report.render(withSeverity = false))
}
return@runBlocking ExitCode.INTERNAL_ERROR
}
when (evalResult.returnValue) {
is ResultValue.Value -> {
println((evalResult.returnValue as ResultValue.Value).value)
ExitCode.OK
}
is ResultValue.Error -> {
val errorValue = evalResult.returnValue as ResultValue.Error
errorValue.renderError(System.err)
ExitCode.SCRIPT_EXECUTION_ERROR
}
else -> ExitCode.OK
}
}
}
override fun createEnvironment(
projectEnvironment: JavaCoreProjectEnvironment,
configuration: CompilerConfiguration
): KotlinCoreEnvironment {
return KotlinCoreEnvironment.createForProduction(projectEnvironment, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
}
override fun createScriptEvaluator(): ScriptEvaluator {
return BasicJvmScriptEvaluator()
}
private var environment: KotlinCoreEnvironment? = null
private val scriptCompiler: ScriptJvmCompilerFromEnvironment by lazy {
ScriptJvmCompilerFromEnvironment(environment!!)
}
override suspend fun compilerInvoke(
environment: KotlinCoreEnvironment,
script: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration
): ResultWithDiagnostics<CompiledScript<*>> {
this.environment = environment
return scriptCompiler.compile(script, scriptCompilationConfiguration)
}
override suspend fun preprocessEvaluation(
scriptEvaluator: ScriptEvaluator,
scriptCompilationConfiguration: ScriptCompilationConfiguration,
evaluationConfiguration: ScriptEvaluationConfiguration
) {
//do nothing
}
override fun isAccepted(arguments: CommonCompilerArguments): Boolean =
arguments is K2JVMCompilerArguments && arguments.script
}
private fun ScriptDiagnostic.Severity.toCompilerMessageSeverity(): CompilerMessageSeverity =
when (this) {
ScriptDiagnostic.Severity.FATAL -> CompilerMessageSeverity.EXCEPTION
ScriptDiagnostic.Severity.ERROR -> CompilerMessageSeverity.ERROR
ScriptDiagnostic.Severity.WARNING -> CompilerMessageSeverity.WARNING
ScriptDiagnostic.Severity.INFO -> CompilerMessageSeverity.INFO
ScriptDiagnostic.Severity.DEBUG -> CompilerMessageSeverity.LOGGING
}
private fun ResultValue.Error.renderError(stream: PrintStream) {
val fullTrace = error.stackTrace
if (wrappingException == null || fullTrace.size < wrappingException!!.stackTrace.size) {
error.printStackTrace(stream)
} else {
// subtracting wrapping message stacktrace from error stacktrace to show only user-specific part of it
// TODO: consider more reliable logic, e.g. comparing traces, fallback to full error printing in case of mismatch
// TODO: write tests
stream.println(error)
val scriptTraceSize = fullTrace.size - wrappingException!!.stackTrace.size
for (i in 0 until scriptTraceSize) {
stream.println("\tat " + fullTrace[i])
}
}
}