From 50c08b25d396c788f473fd09de22a7205e1ec6b0 Mon Sep 17 00:00:00 2001 From: "Vitaliy.Tikhonov" Date: Fri, 30 Aug 2019 16:17:18 +0300 Subject: [PATCH] [SCRIPTING] create extension for scripting in JS and extract common code with JVM part --- .../AbstractScriptEvaluationExtension.kt | 149 +++++++++++++++++ .../plugin/JsScriptEvaluationExtension.kt | 92 +++++++++++ .../plugin/JvmCliScriptEvaluationExtension.kt | 152 +++++------------- 3 files changed, 282 insertions(+), 111 deletions(-) create mode 100644 plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/AbstractScriptEvaluationExtension.kt create mode 100644 plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/JsScriptEvaluationExtension.kt diff --git a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/AbstractScriptEvaluationExtension.kt b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/AbstractScriptEvaluationExtension.kt new file mode 100644 index 00000000000..4a158cad68b --- /dev/null +++ b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/AbstractScriptEvaluationExtension.kt @@ -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> + + 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() + + 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]) + } + } +} diff --git a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/JsScriptEvaluationExtension.kt b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/JsScriptEvaluationExtension.kt new file mode 100644 index 00000000000..d34969f52a7 --- /dev/null +++ b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/JsScriptEvaluationExtension.kt @@ -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> { + 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 + } +} diff --git a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/JvmCliScriptEvaluationExtension.kt b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/JvmCliScriptEvaluationExtension.kt index ef530bab2cd..d46372a4f28 100644 --- a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/JvmCliScriptEvaluationExtension.kt +++ b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/JvmCliScriptEvaluationExtension.kt @@ -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> { + 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]) - } - } -}