Improve script and REPL result handling:
- implement error result - refactor other result classes - implement handling in the script evaluation extension - also restores previous script error reporting functionality - add possibility to customize result fileds in script and REPL - refactor result calculation in the backend: cleanup, rename (since it is not only about REPL now)
This commit is contained in:
@@ -51,6 +51,8 @@ runtimeJar()
|
||||
sourcesJar()
|
||||
javadocJar()
|
||||
|
||||
testsJar()
|
||||
|
||||
projectTest {
|
||||
workingDir = rootDir
|
||||
}
|
||||
|
||||
+52
-13
@@ -14,7 +14,7 @@ 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.ERROR
|
||||
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
|
||||
@@ -23,9 +23,8 @@ import org.jetbrains.kotlin.scripting.compiler.plugin.impl.ScriptJvmCompilerFrom
|
||||
import org.jetbrains.kotlin.scripting.configuration.ScriptingConfigurationKeys
|
||||
import org.jetbrains.kotlin.scripting.definitions.ScriptDefinitionProvider
|
||||
import java.io.File
|
||||
import kotlin.script.experimental.api.constructorArgs
|
||||
import kotlin.script.experimental.api.valueOr
|
||||
import kotlin.script.experimental.api.with
|
||||
import java.io.PrintStream
|
||||
import kotlin.script.experimental.api.*
|
||||
import kotlin.script.experimental.host.toScriptSource
|
||||
import kotlin.script.experimental.jvm.BasicJvmScriptEvaluator
|
||||
|
||||
@@ -41,7 +40,7 @@ class JvmCliScriptEvaluationExtension : ScriptEvaluationExtension {
|
||||
val messageCollector = configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
|
||||
val scriptDefinitionProvider = ScriptDefinitionProvider.getInstance(projectEnvironment.project)
|
||||
if (scriptDefinitionProvider == null) {
|
||||
messageCollector.report(ERROR, "Unable to process the script, scripting plugin is not configured")
|
||||
messageCollector.report(CompilerMessageSeverity.ERROR, "Unable to process the script, scripting plugin is not configured")
|
||||
return COMPILATION_ERROR
|
||||
}
|
||||
val sourcePath = arguments.freeArgs.first()
|
||||
@@ -56,7 +55,7 @@ class JvmCliScriptEvaluationExtension : ScriptEvaluationExtension {
|
||||
val extensionHint =
|
||||
if (configuration.get(ScriptingConfigurationKeys.SCRIPT_DEFINITIONS)?.let { it.size == 1 && it.first().isDefault } == true) " (.kts)"
|
||||
else ""
|
||||
messageCollector.report(ERROR, "Specify path to the script file$extensionHint as the first argument")
|
||||
messageCollector.report(CompilerMessageSeverity.ERROR, "Specify path to the script file$extensionHint as the first argument")
|
||||
return COMPILATION_ERROR
|
||||
}
|
||||
|
||||
@@ -76,16 +75,56 @@ class JvmCliScriptEvaluationExtension : ScriptEvaluationExtension {
|
||||
return runBlocking {
|
||||
val compiledScript =
|
||||
scriptCompiler.compile(script, scriptCompilationConfiguration)
|
||||
.valueOr { 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 COMPILATION_ERROR
|
||||
}
|
||||
val evalResult = BasicJvmScriptEvaluator().invoke(compiledScript, evaluationConfiguration)
|
||||
.valueOr {
|
||||
for (report in it.reports) {
|
||||
messageCollector.report(ERROR, report.toString())
|
||||
messageCollector.report(report.severity.toCompilerMessageSeverity(), report.render(withSeverity = false))
|
||||
}
|
||||
return@runBlocking ExitCode.SCRIPT_EXECUTION_ERROR
|
||||
return@runBlocking ExitCode.INTERNAL_ERROR
|
||||
}
|
||||
ExitCode.OK
|
||||
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])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
-7
@@ -13,7 +13,6 @@ import org.jetbrains.kotlin.cli.common.messages.MessageCollectorBasedReporter
|
||||
import org.jetbrains.kotlin.cli.common.repl.IReplStageHistory
|
||||
import org.jetbrains.kotlin.cli.common.repl.LineId
|
||||
import org.jetbrains.kotlin.cli.common.repl.ReplCodeLine
|
||||
import org.jetbrains.kotlin.cli.common.repl.scriptResultFieldName
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
|
||||
import org.jetbrains.kotlin.codegen.ClassBuilderFactories
|
||||
import org.jetbrains.kotlin.codegen.CompilationErrorHandler
|
||||
@@ -112,7 +111,14 @@ class KJvmReplCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) :
|
||||
is ReplCodeAnalyzer.ReplLineAnalysisResult.WithErrors -> return failure(
|
||||
messageCollector
|
||||
)
|
||||
is ReplCodeAnalyzer.ReplLineAnalysisResult.Successful -> analysisResult.scriptDescriptor
|
||||
is ReplCodeAnalyzer.ReplLineAnalysisResult.Successful -> {
|
||||
(analysisResult.scriptDescriptor as? ScriptDescriptor)
|
||||
?: return failure(
|
||||
snippet,
|
||||
messageCollector,
|
||||
"Unexpected script descriptor type ${analysisResult.scriptDescriptor::class}"
|
||||
)
|
||||
}
|
||||
else -> return failure(
|
||||
snippet,
|
||||
messageCollector,
|
||||
@@ -120,8 +126,6 @@ class KJvmReplCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) :
|
||||
)
|
||||
}
|
||||
|
||||
val type = (scriptDescriptor as ScriptDescriptor).resultValue?.returnType
|
||||
|
||||
val generationState = GenerationState.Builder(
|
||||
snippetKtFile.project,
|
||||
ClassBuilderFactories.BINARIES,
|
||||
@@ -130,9 +134,7 @@ class KJvmReplCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) :
|
||||
sourceFiles,
|
||||
compilationState.environment.configuration
|
||||
).build().apply {
|
||||
replSpecific.resultType = type
|
||||
replSpecific.scriptResultFieldName = scriptResultFieldName(codeLine.no)
|
||||
replSpecific.earlierScriptsForReplInterpreter = history.map { it.item }
|
||||
scriptSpecific.earlierScriptsForReplInterpreter = history.map { it.item }
|
||||
beforeCompile()
|
||||
}
|
||||
KotlinCodegenFacade.generatePackage(
|
||||
|
||||
+3
-4
@@ -147,10 +147,9 @@ internal fun makeCompiledScript(
|
||||
|
||||
val module = makeCompiledModule(generationState)
|
||||
|
||||
val resultField = with(generationState.replSpecific) {
|
||||
// TODO: pass it in the configuration instead
|
||||
if (!hasResult || resultType == null || scriptResultFieldName == null) null
|
||||
else scriptResultFieldName!! to KotlinType(DescriptorRenderer.FQ_NAMES_IN_TYPES.renderType(resultType!!))
|
||||
val resultField = with(generationState.scriptSpecific) {
|
||||
if (resultType == null || resultFieldName == null) null
|
||||
else resultFieldName!! to KotlinType(DescriptorRenderer.FQ_NAMES_IN_TYPES.renderType(resultType!!))
|
||||
}
|
||||
|
||||
return KJvmCompiledScript(
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
|
||||
10
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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 java.io.File
|
||||
import junit.framework.Assert.*
|
||||
import org.jetbrains.kotlin.cli.common.CLITool
|
||||
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
|
||||
import org.junit.Test
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.PrintStream
|
||||
|
||||
class ScriptingWithCliCompilerTest {
|
||||
|
||||
companion object {
|
||||
const val TEST_DATA_DIR = "plugins/scripting/scripting-compiler/testData"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testResultValue() {
|
||||
runWithK2JVMCompiler("$TEST_DATA_DIR/integration/intResult.kts", listOf("10"))
|
||||
}
|
||||
}
|
||||
|
||||
fun runWithK2JVMCompiler(
|
||||
scriptPath: String,
|
||||
expectedOutPatterns: List<String> = emptyList(),
|
||||
expectedExitCode: Int = 0
|
||||
) {
|
||||
val mainKtsJar = File("dist/kotlinc/lib/kotlin-main-kts.jar")
|
||||
assertTrue("kotlin-main-kts.jar not found, run dist task: ${mainKtsJar.absolutePath}", mainKtsJar.exists())
|
||||
|
||||
val (out, err, ret) = captureOutErrRet {
|
||||
CLITool.doMainNoExit(
|
||||
K2JVMCompiler(),
|
||||
arrayOf("-kotlin-home", "dist/kotlinc", "-cp", mainKtsJar.absolutePath, "-script", scriptPath)
|
||||
)
|
||||
}
|
||||
try {
|
||||
val outLines = out.lines()
|
||||
assertEquals(expectedOutPatterns.size, outLines.size)
|
||||
for ((expectedPattern, actualLine) in expectedOutPatterns.zip(outLines)) {
|
||||
assertTrue(
|
||||
"line \"$actualLine\" do not match with expected pattern \"$expectedPattern\"",
|
||||
Regex(expectedPattern).matches(actualLine)
|
||||
)
|
||||
}
|
||||
assertEquals(expectedExitCode, ret.code)
|
||||
} catch (e: Throwable) {
|
||||
println("OUT:\n$out")
|
||||
println("ERR:\n$err")
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal fun <T> captureOutErrRet(body: () -> T): Triple<String, String, T> {
|
||||
val outStream = ByteArrayOutputStream()
|
||||
val errStream = ByteArrayOutputStream()
|
||||
val prevOut = System.out
|
||||
val prevErr = System.err
|
||||
System.setOut(PrintStream(outStream))
|
||||
System.setErr(PrintStream(errStream))
|
||||
val ret = try {
|
||||
body()
|
||||
} finally {
|
||||
System.out.flush()
|
||||
System.err.flush()
|
||||
System.setOut(prevOut)
|
||||
System.setErr(prevErr)
|
||||
}
|
||||
return Triple(outStream.toString().trim(), errStream.toString().trim(), ret)
|
||||
}
|
||||
Reference in New Issue
Block a user