Implement support for expression evaluation in jvm cli compiler

This commit is contained in:
Ilya Chernikov
2019-11-19 20:00:43 +01:00
parent 7bb9f97b11
commit 531ff92791
8 changed files with 80 additions and 26 deletions
@@ -49,6 +49,9 @@ class K2JVMCompilerArguments : CommonCompilerArguments() {
@Argument(value = "-no-reflect", description = "Don't automatically include Kotlin reflection into the classpath")
var noReflect: Boolean by FreezableVar(false)
@Argument(value = "-expression", description = "Evaluate the given string as a Kotlin script")
var expressions: Array<String>? by FreezableVar(null)
@Argument(
value = "-script-templates",
valueDescription = "<fully qualified class name[,]>",
@@ -35,9 +35,7 @@ import org.jetbrains.kotlin.cli.jvm.compiler.CompileEnvironmentUtil
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler
import org.jetbrains.kotlin.cli.jvm.plugins.PluginCliParser
import org.jetbrains.kotlin.codegen.CompilationException
import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
import org.jetbrains.kotlin.config.*
import org.jetbrains.kotlin.incremental.components.ExpectActualTracker
import org.jetbrains.kotlin.incremental.components.LookupTracker
@@ -48,9 +46,7 @@ import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMetadataVersion
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil
import org.jetbrains.kotlin.modules.JavaRootPath
import org.jetbrains.kotlin.utils.KotlinPaths
import org.jetbrains.kotlin.utils.PathUtil
import java.io.File
import java.util.*
class K2JVMCompiler : CLICompiler<K2JVMCompilerArguments>() {
@@ -80,7 +76,9 @@ class K2JVMCompiler : CLICompiler<K2JVMCompilerArguments>() {
configuration.configureStandardLibs(paths, arguments)
configuration.configureAdvancedJvmOptions(arguments)
if (arguments.buildFile == null && !arguments.version && !arguments.allowNoSourceFiles && (arguments.script || arguments.freeArgs.isEmpty())) {
if (arguments.buildFile == null && !arguments.version && !arguments.allowNoSourceFiles &&
(arguments.script || arguments.expressions != null || arguments.freeArgs.isEmpty())) {
// script or repl
if (arguments.script && arguments.freeArgs.isEmpty()) {
messageCollector.report(ERROR, "Specify script source path to evaluate")
@@ -94,7 +92,7 @@ class K2JVMCompiler : CLICompiler<K2JVMCompilerArguments>() {
)
projectEnvironment.registerExtensionsFromPlugins(configuration)
if (arguments.script) {
if (arguments.script || arguments.expressions != null) {
val scriptingEvaluator = ScriptEvaluationExtension.getInstances(projectEnvironment.project).find { it.isAccepted(arguments) }
if (scriptingEvaluator == null) {
messageCollector.report(ERROR, "Unable to evaluate script, no scripting plugin loaded")
+1
View File
@@ -2,6 +2,7 @@ Usage: kotlinc-jvm <options> <source files>
where possible options include:
-classpath (-cp) <path> List of directories and JAR/ZIP archives to search for user class files
-d <directory|jar> Destination for generated class files
-expression Evaluate the given string as a Kotlin script
-include-runtime Include Kotlin runtime into the resulting JAR
-java-parameters Generate metadata for Java 1.8 reflection on method parameters
-jdk-home <path> Include a custom JDK from the specified location into the classpath instead of the default JAVA_HOME
@@ -10,8 +10,10 @@ 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.arguments.K2JVMCompilerArguments
import org.jetbrains.kotlin.cli.common.extensions.ScriptEvaluationExtension
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.scripting.configuration.ScriptingConfigurationKeys
@@ -19,6 +21,7 @@ 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.StringScriptSource
import kotlin.script.experimental.host.toScriptSource
abstract class AbstractScriptEvaluationExtension : ScriptEvaluationExtension {
@@ -46,37 +49,59 @@ abstract class AbstractScriptEvaluationExtension : ScriptEvaluationExtension {
messageCollector.report(CompilerMessageSeverity.ERROR, "Unable to process the script, scripting plugin is not configured")
return ExitCode.COMPILATION_ERROR
}
val sourcePath = arguments.freeArgs.first()
setupScriptConfiguration(configuration)
val script = when {
arguments is K2JVMCompilerArguments && arguments.expressions != null -> {
StringScriptSource(arguments.expressions!!.joinToString("\n"), "script.kts")
}
arguments.script -> {
val scriptFile = File(arguments.freeArgs.first())
val script = scriptFile.toScriptSource()
if (scriptFile.isDirectory || !scriptDefinitionProvider.isScript(script)) {
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
}
script
}
else -> {
messageCollector.report(CompilerMessageSeverity.ERROR, "Illegal set of arguments: either -script or -expression arguments expected at this point")
return ExitCode.COMPILATION_ERROR
}
}
val environment = createEnvironment(projectEnvironment, configuration)
if (messageCollector.hasErrors()) return ExitCode.COMPILATION_ERROR
val scriptFile = File(sourcePath)
val script = scriptFile.toScriptSource()
if (scriptFile.isDirectory || !scriptDefinitionProvider.isScript(script)) {
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 definition = scriptDefinitionProvider.findDefinition(script) ?: scriptDefinitionProvider.getDefaultDefinition()
val scriptCompilationConfiguration = definition.compilationConfiguration
val scriptArgs =
if (arguments.freeArgs.isNotEmpty()) arguments.freeArgs.subList(1, arguments.freeArgs.size)
else emptyList<String>()
if (arguments.script) arguments.freeArgs.subList(1, arguments.freeArgs.size)
else arguments.freeArgs
val evaluationConfiguration = definition.evaluationConfiguration.with {
constructorArgs(scriptArgs.toTypedArray())
platformEvaluationConfiguration()
}
val scriptCompilationConfiguration = definition.compilationConfiguration
return doEval(script, scriptCompilationConfiguration, evaluationConfiguration, environment, messageCollector)
}
private fun doEval(
script: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration,
evaluationConfiguration: ScriptEvaluationConfiguration,
environment: KotlinCoreEnvironment,
messageCollector: MessageCollector
): ExitCode {
val scriptCompiler = createScriptCompiler(environment)
return runBlocking {
@@ -13,7 +13,8 @@ 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 kotlin.script.experimental.api.*
import kotlin.script.experimental.api.ScriptEvaluationConfiguration
import kotlin.script.experimental.api.ScriptEvaluator
import kotlin.script.experimental.jvm.BasicJvmScriptEvaluator
import kotlin.script.experimental.jvm.baseClassLoader
import kotlin.script.experimental.jvm.jvm
@@ -46,6 +47,6 @@ class JvmCliScriptEvaluationExtension : AbstractScriptEvaluationExtension() {
}
override fun isAccepted(arguments: CommonCompilerArguments): Boolean =
arguments is K2JVMCompilerArguments && arguments.script
arguments is K2JVMCompilerArguments && (arguments.script || arguments.expressions != null)
}
@@ -6,7 +6,6 @@
package org.jetbrains.kotlin.scripting.compiler.plugin.impl
import org.jetbrains.kotlin.cli.common.arguments.Argument
import org.jetbrains.kotlin.cli.common.arguments.ArgumentParseErrors
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
@@ -100,6 +99,7 @@ internal fun reportArgumentsIgnoredGenerally(
K2JVMCompilerArguments::commonSources,
K2JVMCompilerArguments::allWarningsAsErrors,
K2JVMCompilerArguments::script,
K2JVMCompilerArguments::expressions,
K2JVMCompilerArguments::scriptTemplates,
K2JVMCompilerArguments::scriptResolverEnvironment,
K2JVMCompilerArguments::disableStandardScript,
@@ -46,6 +46,21 @@ class ScriptingWithCliCompilerTest {
)
}
@Test
fun testExpression() {
runWithK2JVMCompiler(
arrayOf(
"-expression",
"val x = 7; println(x * 6); for (arg in args) println(arg)",
"--",
"hi",
"there"
),
listOf("42", "hi", "there")
)
}
private fun getMainKtsClassPath(): List<File> {
return listOf(
File("dist/kotlinc/lib/kotlin-main-kts.jar").also {
@@ -112,15 +112,26 @@ fun runWithK2JVMCompiler(
add("-script")
add(scriptPath)
}
runWithK2JVMCompiler(args.toTypedArray(), expectedOutPatterns, expectedExitCode)
}
fun runWithK2JVMCompiler(
args: Array<String>,
expectedOutPatterns: List<String> = emptyList(),
expectedExitCode: Int = 0
) {
val (out, err, ret) = captureOutErrRet {
CLITool.doMainNoExit(
K2JVMCompiler(),
args.toTypedArray()
args
)
}
try {
val outLines = out.lines()
Assert.assertEquals(expectedOutPatterns.size, outLines.size)
Assert.assertEquals(
"Expecting pattern:\n ${expectedOutPatterns.joinToString("\n ")}\nGot:\n ${outLines.joinToString("\n ")}",
expectedOutPatterns.size, outLines.size
)
for ((expectedPattern, actualLine) in expectedOutPatterns.zip(outLines)) {
Assert.assertTrue(
"line \"$actualLine\" do not match with expected pattern \"$expectedPattern\"",