diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/plugins/PluginCliParser.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/plugins/PluginCliParser.kt index 196c241230f..296a0968da4 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/plugins/PluginCliParser.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/plugins/PluginCliParser.kt @@ -77,44 +77,52 @@ object PluginCliParser { configuration: CompilerConfiguration, classLoader: URLClassLoader ) { - val optionValuesByPlugin = pluginOptions?.map(::parsePluginOption)?.groupBy { - if (it == null) throw CliOptionProcessingException("Wrong plugin option format: $it, should be ${CommonCompilerArguments.PLUGIN_OPTION_FORMAT}") - it.pluginId - } ?: mapOf() - // TODO issue a warning on using deprecated command line processors when all official plugin migrate to the newer convention val commandLineProcessors = ServiceLoaderLite.loadImplementations(CommandLineProcessor::class.java, classLoader) - for (processor in commandLineProcessors) { - val declaredOptions = processor.pluginOptions.associateBy { it.optionName } - val optionsToValues = MultiMap() + processCompilerPluginsOptions(configuration, pluginOptions, commandLineProcessors) + } +} - for (optionValue in optionValuesByPlugin[processor.pluginId].orEmpty()) { - val option = declaredOptions[optionValue!!.optionName] - ?: throw CliOptionProcessingException("Unsupported plugin option: $optionValue") - optionsToValues.putValue(option, optionValue) +fun processCompilerPluginsOptions( + configuration: CompilerConfiguration, + pluginOptions: Iterable?, + commandLineProcessors: List +) { + val optionValuesByPlugin = pluginOptions?.map(::parsePluginOption)?.groupBy { + if (it == null) throw CliOptionProcessingException("Wrong plugin option format: $it, should be ${CommonCompilerArguments.PLUGIN_OPTION_FORMAT}") + it.pluginId + } ?: mapOf() + + for (processor in commandLineProcessors) { + val declaredOptions = processor.pluginOptions.associateBy { it.optionName } + val optionsToValues = MultiMap() + + for (optionValue in optionValuesByPlugin[processor.pluginId].orEmpty()) { + val option = declaredOptions[optionValue!!.optionName] + ?: throw CliOptionProcessingException("Unsupported plugin option: $optionValue") + optionsToValues.putValue(option, optionValue) + } + + for (option in processor.pluginOptions) { + val values = optionsToValues[option] + if (option.required && values.isEmpty()) { + throw PluginCliOptionProcessingException( + processor.pluginId, + processor.pluginOptions, + "Required plugin option not present: ${processor.pluginId}:${option.optionName}" + ) + } + if (!option.allowMultipleOccurrences && values.size > 1) { + throw PluginCliOptionProcessingException( + processor.pluginId, + processor.pluginOptions, + "Multiple values are not allowed for plugin option ${processor.pluginId}:${option.optionName}" + ) } - for (option in processor.pluginOptions) { - val values = optionsToValues[option] - if (option.required && values.isEmpty()) { - throw PluginCliOptionProcessingException( - processor.pluginId, - processor.pluginOptions, - "Required plugin option not present: ${processor.pluginId}:${option.optionName}" - ) - } - if (!option.allowMultipleOccurrences && values.size > 1) { - throw PluginCliOptionProcessingException( - processor.pluginId, - processor.pluginOptions, - "Multiple values are not allowed for plugin option ${processor.pluginId}:${option.optionName}" - ) - } - - for (value in values) { - processor.processOption(option, value.value, configuration) - } + for (value in values) { + processor.processOption(option, value.value, configuration) } } } diff --git a/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/util/jvmClassLoaderUtil.kt b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/util/jvmClassLoaderUtil.kt index f4fcd85c589..0a001f12924 100644 --- a/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/util/jvmClassLoaderUtil.kt +++ b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/util/jvmClassLoaderUtil.kt @@ -15,12 +15,12 @@ import java.util.jar.JarFile import java.util.jar.JarInputStream import kotlin.script.experimental.jvm.impl.toFileOrNull -fun ClassLoader.forAllMatchingFiles(namePattern: String, body: (String, InputStream) -> Unit) { +fun ClassLoader.forAllMatchingFiles(namePattern: String, vararg keyResourcePaths: String, body: (String, InputStream) -> Unit) { val processedDirs = HashSet() val processedJars = HashSet() val nameRegex = namePatternToRegex(namePattern) - fun iterateResources(vararg keyResourcePaths: String) { + fun iterateResources(keyResourcePaths: Array) { for (keyResourcePath in keyResourcePaths) { val resourceRootCalc = ClassLoaderResourceRootFIlePathCalculator(keyResourcePath) for (url in getResources(keyResourcePath)) { @@ -49,7 +49,7 @@ fun ClassLoader.forAllMatchingFiles(namePattern: String, body: (String, InputStr } } - iterateResources("", JAR_MANIFEST_RESOURCE_NAME) + iterateResources(if (keyResourcePaths.isEmpty()) arrayOf("", JAR_MANIFEST_RESOURCE_NAME) else keyResourcePaths) } internal val wildcardChars = "*?".toCharArray() 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 45f6ecf0894..f9f66b491b9 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 @@ -147,6 +147,8 @@ internal fun createInitialConfigurations( ScriptDefinition.FromConfigurations(hostConfiguration, scriptCompilationConfiguration, null) ) + kotlinCompilerConfiguration.loadPlugins() + initialScriptCompilationConfiguration[ScriptCompilationConfiguration.compilerOptions]?.let { compilerOptions -> kotlinCompilerConfiguration.updateWithCompilerOptions(compilerOptions, messageCollector, ignoredOptionsReportingState, false) } @@ -176,6 +178,8 @@ private fun CompilerConfiguration.updateWithCompilerOptions( ) } + processPluginsCommandLine(compilerArguments) + setupCommonArguments(compilerArguments) setupJvmSpecificArguments(compilerArguments) 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 c1e2225bbb6..0e437fbb511 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 @@ -115,7 +115,6 @@ internal fun reportArgumentsIgnoredGenerally( K2JVMCompilerArguments::disableStandardScript, K2JVMCompilerArguments::disableDefaultScriptingPlugin, K2JVMCompilerArguments::pluginClasspaths, - K2JVMCompilerArguments::pluginOptions, K2JVMCompilerArguments::useJavac, K2JVMCompilerArguments::compileJava, K2JVMCompilerArguments::reportPerf, 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 new file mode 100644 index 00000000000..9fa0c44c8b7 --- /dev/null +++ b/plugins/scripting/scripting-compiler/src/org/jetbrains/kotlin/scripting/compiler/plugin/impl/loadCompilerPlugins.kt @@ -0,0 +1,85 @@ +/* + * Copyright 2010-2020 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.impl + +import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments +import org.jetbrains.kotlin.cli.jvm.plugins.processCompilerPluginsOptions +import org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor +import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar +import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.scripting.compiler.plugin.ScriptingCommandLineProcessor +import org.jetbrains.kotlin.scripting.compiler.plugin.ScriptingCompilerConfigurationComponentRegistrar +import kotlin.script.experimental.jvm.util.forAllMatchingFiles + +private const val SCRIPT_COMPILATION_DISABLE_PLUGINS_PROPERTY = "script.compilation.disable.plugins" +private const val SCRIPT_COMPILATION_DISABLE_COMMANDLINE_PROCESSORS_PROPERTY = "script.compilation.disable.commandline.processors" + +private val scriptCompilationDisabledPlugins = + listOf( + ScriptingCompilerConfigurationComponentRegistrar::class.java.name + ) + +private val scriptCompilationDisabledCommandlineProcessors = + listOf( + ScriptingCommandLineProcessor::class.java.name + ) + +internal fun CompilerConfiguration.loadPlugins() { + val classLoader = CompilerConfiguration::class.java.classLoader + val registrars = + classLoader.loadServices(scriptCompilationDisabledPlugins, SCRIPT_COMPILATION_DISABLE_PLUGINS_PROPERTY) + addAll(ComponentRegistrar.PLUGIN_COMPONENT_REGISTRARS, registrars) +} + +internal fun CompilerConfiguration.processPluginsCommandLine(arguments: K2JVMCompilerArguments) { + val classLoader = CompilerConfiguration::class.java.classLoader + val pluginOptions = arguments.pluginOptions?.asIterable() ?: emptyList() + + val commandLineProcessors = + classLoader.loadServices( + scriptCompilationDisabledCommandlineProcessors, SCRIPT_COMPILATION_DISABLE_COMMANDLINE_PROCESSORS_PROPERTY + ) + processCompilerPluginsOptions(this, pluginOptions, commandLineProcessors) +} + +private inline fun ClassLoader.loadServices(disabled: List, disablingProperty: String): List { + val disabledServiceNames = disabled.toHashSet() + System.getProperty(disablingProperty)?.let { + it.split(',', ';', ' ').forEach { name -> + disabledServiceNames.add(name.trim()) + } + } + return loadServices { + !disabledServiceNames.contains(it) && !disabledServiceNames.contains(it.substringAfterLast('.')) + } +} + +private const val SERVICE_DIRECTORY_LOCATION = "META-INF/services/" + +private inline fun ClassLoader.loadServices(isEnabled: (String) -> Boolean): List { + val registrarsNames = HashSet() + val serviceFileName = SERVICE_DIRECTORY_LOCATION + Service::class.java.name + + forAllMatchingFiles(serviceFileName, serviceFileName) { name, stream -> + stream.reader().useLines { + it.mapNotNullTo(registrarsNames) { parseServiceFileLine(name, it) } + } + } + + return registrarsNames.mapNotNull { if (isEnabled(it)) (loadClass(it).newInstance() as Service) else null } +} + +private fun parseServiceFileLine(location: String, line: String): String? { + val actualLine = line.substringBefore('#').trim().takeIf { it.isNotEmpty() } ?: return null + actualLine.forEachIndexed { index: Int, c: Char -> + val isValid = if (index == 0) Character.isJavaIdentifierStart(c) else Character.isJavaIdentifierPart(c) || c == '.' + if (!isValid) { + val errorText = "Invalid Java identifier: $line" + throw RuntimeException("Error loading services from $location : $errorText") + } + } + return actualLine +}