From d8650d9b04e488584c0162e13c0f620a6b4d23ae Mon Sep 17 00:00:00 2001 From: Ilya Chernikov Date: Thu, 22 Dec 2022 16:59:16 +0100 Subject: [PATCH] Scripting: support compiler plugins in hosted script compiler Now the plugin-related compiler arguments are respected then used in the compiler configuration (only in the static part, not supported if set in a refinement callback) Note that the "embeddable" version of the plugin should be used if embeddable compiler is used. #KT-54095 fixed --- .../kotlin/cli/common/CLICompiler.kt | 85 +++++++++++-------- .../kotlin-main-kts-test/build.gradle.kts | 9 +- .../kotlin/mainKts/test/mainKtsTest.kt | 67 +++++++++++---- .../plugin/impl/compilationContext.kt | 25 ++++-- .../compiler/plugin/impl/errorReporting.kt | 6 +- .../plugin/impl/loadCompilerPlugins.kt | 3 +- 6 files changed, 130 insertions(+), 65 deletions(-) diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/common/CLICompiler.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/common/CLICompiler.kt index 8abbeef97bc..c42450326cb 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/common/CLICompiler.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/common/CLICompiler.kt @@ -173,43 +173,8 @@ abstract class CLICompiler : CLITool() { val pluginConfigurations = arguments.pluginConfigurations.orEmpty().toMutableList() val messageCollector = configuration.getNotNull(MESSAGE_COLLECTOR_KEY) - for (classpath in pluginClasspaths) { - if (!File(classpath).exists()) { - messageCollector.report(ERROR, "Plugin classpath entry points to a non-existent location: $classpath") - } - } - - if (pluginConfigurations.isNotEmpty()) { - var hasErrors = false - messageCollector.report(WARNING, "Argument -Xcompiler-plugin is experimental") - if (!arguments.useK2) { - hasErrors = true - messageCollector.report( - ERROR, - "-Xcompiler-plugin argument is allowed only for for K2 compiler. Please use -Xplugin argument or enable -Xuse-k2" - ) - } - if (pluginClasspaths.isNotEmpty() || pluginOptions.isNotEmpty()) { - hasErrors = true - val message = buildString { - appendLine("Mixing legacy and modern plugin arguments is prohibited. Please use only one syntax") - appendLine("Legacy arguments:") - if (pluginClasspaths.isNotEmpty()) { - appendLine(" -Xplugin=${pluginClasspaths.joinToString(",")}") - } - pluginOptions.forEach { - appendLine(" -P $it") - } - appendLine("Modern arguments:") - pluginConfigurations.forEach { - appendLine(" -Xcompiler-plugin=$it") - } - } - messageCollector.report(ERROR, message) - } - if (hasErrors) { - return INTERNAL_ERROR - } + if (!checkPluginsArguments(messageCollector, arguments.useK2, pluginClasspaths, pluginOptions, pluginConfigurations)) { + return INTERNAL_ERROR } if (!arguments.disableDefaultScriptingPlugin) { @@ -259,3 +224,49 @@ abstract class CLICompiler : CLITool() { } } +fun checkPluginsArguments( + messageCollector: MessageCollector, + useK2: Boolean, + pluginClasspaths: List, + pluginOptions: List, + pluginConfigurations: MutableList +): Boolean { + var hasErrors = false + + for (classpath in pluginClasspaths) { + if (!File(classpath).exists()) { + messageCollector.report(ERROR, "Plugin classpath entry points to a non-existent location: $classpath") + } + } + + if (pluginConfigurations.isNotEmpty()) { + messageCollector.report(WARNING, "Argument -Xcompiler-plugin is experimental") + if (!useK2) { + hasErrors = true + messageCollector.report( + ERROR, + "-Xcompiler-plugin argument is allowed only for for K2 compiler. Please use -Xplugin argument or enable -Xuse-k2" + ) + } + if (pluginClasspaths.isNotEmpty() || pluginOptions.isNotEmpty()) { + hasErrors = true + val message = buildString { + appendLine("Mixing legacy and modern plugin arguments is prohibited. Please use only one syntax") + appendLine("Legacy arguments:") + if (pluginClasspaths.isNotEmpty()) { + appendLine(" -Xplugin=${pluginClasspaths.joinToString(",")}") + } + pluginOptions.forEach { + appendLine(" -P $it") + } + appendLine("Modern arguments:") + pluginConfigurations.forEach { + appendLine(" -Xcompiler-plugin=$it") + } + } + messageCollector.report(ERROR, message) + } + } + return !hasErrors +} + diff --git a/libraries/tools/kotlin-main-kts-test/build.gradle.kts b/libraries/tools/kotlin-main-kts-test/build.gradle.kts index 62459f41fa3..23834f608d2 100644 --- a/libraries/tools/kotlin-main-kts-test/build.gradle.kts +++ b/libraries/tools/kotlin-main-kts-test/build.gradle.kts @@ -5,6 +5,8 @@ plugins { kotlin("jvm") } +val kotlinxSerializationGradlePluginClasspath by configurations.creating + dependencies { testApi(project(":kotlin-main-kts")) testCompileOnly(project(":compiler:cli")) @@ -13,6 +15,7 @@ dependencies { testApi(commonDependency("junit")) testApi(projectTests(":kotlin-scripting-compiler")) { isTransitive = false } testImplementation(project(":kotlin-compiler-embeddable")) + kotlinxSerializationGradlePluginClasspath(project(":kotlin-serialization")) { isTransitive = false } } sourceSets { @@ -21,7 +24,11 @@ sourceSets { } projectTest(parallel = true) { - dependsOn(":dist") + dependsOn(":dist", ":kotlin-serialization:jar") + val localKotlinxSerializationPluginClasspath: FileCollection = kotlinxSerializationGradlePluginClasspath + doFirst { + systemProperty("kotlin.script.test.kotlinx.serialization.plugin.classpath", localKotlinxSerializationPluginClasspath.asPath) + } workingDir = rootDir } diff --git a/libraries/tools/kotlin-main-kts-test/test/org/jetbrains/kotlin/mainKts/test/mainKtsTest.kt b/libraries/tools/kotlin-main-kts-test/test/org/jetbrains/kotlin/mainKts/test/mainKtsTest.kt index c39e9a72c05..62354391ec9 100644 --- a/libraries/tools/kotlin-main-kts-test/test/org/jetbrains/kotlin/mainKts/test/mainKtsTest.kt +++ b/libraries/tools/kotlin-main-kts-test/test/org/jetbrains/kotlin/mainKts/test/mainKtsTest.kt @@ -5,15 +5,17 @@ package org.jetbrains.kotlin.mainKts.test import org.jetbrains.kotlin.mainKts.COMPILED_SCRIPTS_CACHE_DIR_PROPERTY -import org.jetbrains.kotlin.mainKts.impl.Directories import org.jetbrains.kotlin.mainKts.MainKtsScript import org.jetbrains.kotlin.mainKts.SCRIPT_FILE_LOCATION_DEFAULT_VARIABLE_NAME +import org.jetbrains.kotlin.mainKts.impl.Directories import org.jetbrains.kotlin.scripting.compiler.plugin.assertTrue import org.junit.Assert import org.junit.Assert.assertEquals import org.junit.Ignore import org.junit.Test -import java.io.* +import java.io.ByteArrayOutputStream +import java.io.File +import java.io.PrintStream import java.util.* import kotlin.script.experimental.api.* import kotlin.script.experimental.host.toScriptSource @@ -24,21 +26,31 @@ import kotlin.script.experimental.jvmhost.createJvmScriptDefinitionFromTemplate fun evalFile(scriptFile: File, cacheDir: File? = null): ResultWithDiagnostics = withProperty(COMPILED_SCRIPTS_CACHE_DIR_PROPERTY, cacheDir?.absolutePath ?: "") { - val scriptDefinition = createJvmScriptDefinitionFromTemplate( - evaluation = { - jvm { - baseClassLoader(null) - } - constructorArgs(emptyArray()) - enableScriptsInstancesSharing() - } - ) - - BasicJvmScriptingHost().eval( - scriptFile.toScriptSource(), scriptDefinition.compilationConfiguration, scriptDefinition.evaluationConfiguration - ) + evalFileWithConfigurations(scriptFile) } +fun evalFileWithConfigurations( + scriptFile: File, + compilation: ScriptCompilationConfiguration.Builder.() -> Unit = {}, + evaluation: ScriptEvaluationConfiguration.Builder.() -> Unit = {} +): ResultWithDiagnostics { + val scriptDefinition = createJvmScriptDefinitionFromTemplate( + compilation = compilation, + evaluation = { + evaluation() + jvm { + baseClassLoader(null) + } + constructorArgs(emptyArray()) + enableScriptsInstancesSharing() + } + ) + + return BasicJvmScriptingHost().eval( + scriptFile.toScriptSource(), scriptDefinition.compilationConfiguration, scriptDefinition.evaluationConfiguration + ) +} + const val TEST_DATA_ROOT = "libraries/tools/kotlin-main-kts-test/testData" val OUT_FROM_IMPORT_TEST = listOf("Hi from common", "Hi from middle", "Hi from main", "sharedVar == 5") @@ -231,9 +243,32 @@ class MainKtsTest { assertSucceeded(res) } + @Test + fun testHelloSerialization() { + // the embeddable plugin is needed for this test, because embeddable compiler is used. + // furtunately appropriate gradle plugin can serve as an embeddable compiler plugin now + // so, the path to it is prepared in the build file + val serializationPluginClasspath = System.getProperty("kotlin.script.test.kotlinx.serialization.plugin.classpath")!! + val out = captureOut { + val res = evalFileWithConfigurations( + File("$TEST_DATA_ROOT/hello-kotlinx-serialization.main.kts"), + compilation = { + compilerOptions( + "-Xplugin", serializationPluginClasspath + ) + } + ) + assertSucceeded(res) + }.lines() + assertEquals( + listOf("""{"firstName":"James","lastName":"Bond"}""", "User(firstName=James, lastName=Bond)"), + out + ) + } + private fun assertSucceeded(res: ResultWithDiagnostics) { Assert.assertTrue( - "test failed:\n ${res.reports.joinToString("\n ") { it.message + if (it.exception == null) "" else ": ${it.exception}" }}", + "test failed:\n ${res.reports.joinToString("\n ") { it.severity.name + ": " + it.message + if (it.exception == null) "" else ": ${it.exception}" }}", res is ResultWithDiagnostics.Success ) } diff --git a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/compilationContext.kt b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/compilationContext.kt index eb18d9f1f71..062363256e1 100644 --- a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/compilationContext.kt +++ b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/compilationContext.kt @@ -16,6 +16,7 @@ import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity import org.jetbrains.kotlin.cli.common.messages.MessageCollector import org.jetbrains.kotlin.cli.common.reportArgumentParseProblems import org.jetbrains.kotlin.cli.common.setupCommonArguments +import org.jetbrains.kotlin.cli.common.checkPluginsArguments import org.jetbrains.kotlin.cli.jvm.* import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment @@ -23,6 +24,7 @@ import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots import org.jetbrains.kotlin.cli.jvm.config.configureJdkClasspathRoots import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots +import org.jetbrains.kotlin.cli.jvm.plugins.PluginCliParser import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar import org.jetbrains.kotlin.config.CommonConfigurationKeys import org.jetbrains.kotlin.config.CompilerConfiguration @@ -199,13 +201,6 @@ internal fun createInitialConfigurations( val initialScriptCompilationConfiguration = scriptCompilationConfiguration.withUpdatesFromCompilerConfiguration(kotlinCompilerConfiguration) - kotlinCompilerConfiguration.add( - ScriptingConfigurationKeys.SCRIPT_DEFINITIONS, - ScriptDefinition.FromConfigurations(hostConfiguration, scriptCompilationConfiguration, null) - ) - - kotlinCompilerConfiguration.loadPlugins() - initialScriptCompilationConfiguration[ScriptCompilationConfiguration.compilerOptions]?.let { compilerOptions -> kotlinCompilerConfiguration.updateWithCompilerOptions(compilerOptions, messageCollector, ignoredOptionsReportingState, false) } @@ -339,6 +334,22 @@ private fun createInitialCompilerConfiguration( configureJdkClasspathRoots() put(JVMConfigurationKeys.USE_FAST_JAR_FILE_SYSTEM, true) + + add( + ScriptingConfigurationKeys.SCRIPT_DEFINITIONS, + ScriptDefinition.FromConfigurations(hostConfiguration, scriptCompilationConfiguration, null) + ) + + val pluginClasspaths = baseArguments.pluginClasspaths?.asList().orEmpty() + val pluginOptions = baseArguments.pluginOptions?.asList().orEmpty() + val pluginConfigurations = baseArguments.pluginConfigurations.orEmpty().toMutableList() + + checkPluginsArguments(messageCollector, false, pluginClasspaths, pluginOptions, pluginConfigurations) + if (pluginClasspaths.isNotEmpty() || pluginConfigurations.isNotEmpty()) { + PluginCliParser.loadPluginsSafe(pluginClasspaths, pluginOptions, pluginConfigurations, this) + } else { + loadPlugins() + } } } diff --git a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/errorReporting.kt b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/errorReporting.kt index 414d642025c..9d24bc40f54 100644 --- a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/errorReporting.kt +++ b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/errorReporting.kt @@ -160,7 +160,6 @@ internal fun reportArgumentsIgnoredGenerally( K2JVMCompilerArguments::disableStandardScript, K2JVMCompilerArguments::defaultScriptExtension, K2JVMCompilerArguments::disableDefaultScriptingPlugin, - K2JVMCompilerArguments::pluginClasspaths, K2JVMCompilerArguments::useJavac, K2JVMCompilerArguments::compileJava, K2JVMCompilerArguments::reportPerf, @@ -181,7 +180,10 @@ internal fun reportArgumentsIgnoredFromRefinement( K2JVMCompilerArguments::javaModulePath, K2JVMCompilerArguments::classpath, K2JVMCompilerArguments::noStdlib, - K2JVMCompilerArguments::noReflect + K2JVMCompilerArguments::noReflect, + K2JVMCompilerArguments::pluginClasspaths, + K2JVMCompilerArguments::pluginOptions, + K2JVMCompilerArguments::pluginConfigurations, ) diff --git a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/loadCompilerPlugins.kt b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/loadCompilerPlugins.kt index 21b70cf4c89..12faf740474 100644 --- a/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/loadCompilerPlugins.kt +++ b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/loadCompilerPlugins.kt @@ -30,8 +30,7 @@ private val scriptCompilationDisabledCommandlineProcessors = ScriptingCommandLineProcessor::class.java.name ) -internal fun CompilerConfiguration.loadPlugins() { - val classLoader = CompilerConfiguration::class.java.classLoader +internal fun CompilerConfiguration.loadPlugins(classLoader: ClassLoader = CompilerConfiguration::class.java.classLoader) { val registrars = classLoader.loadServices(scriptCompilationDisabledPlugins, SCRIPT_COMPILATION_DISABLE_PLUGINS_PROPERTY) addAll(ComponentRegistrar.PLUGIN_COMPONENT_REGISTRARS, registrars)