[SCRIPTING] create extension for scripting in JS and extract common code with JVM part
This commit is contained in:
committed by
romanart
parent
c6c3d2de9e
commit
50c08b25d3
+149
@@ -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])
|
||||
}
|
||||
}
|
||||
}
|
||||
+92
@@ -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
|
||||
}
|
||||
}
|
||||
+41
-111
@@ -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])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user