From d961e2f5b45bbc30b52950e35ae8c58b2a3aa25e Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Tue, 23 Sep 2014 18:19:48 +0400 Subject: [PATCH] CommandLineProcessor infrastructure added --- .../arguments/CommonCompilerArguments.java | 10 ++ .../jetbrains/jet/cli/jvm/PluginCliParser.kt | 109 ++++++++++++++++++ .../kotlin/cli/jvm/K2JVMCompiler.java | 13 ++- compiler/plugin-api/plugin-api.iml | 1 + .../compiler/plugin/CommandLineProcessor.kt | 37 ++++++ 5 files changed, 165 insertions(+), 5 deletions(-) create mode 100644 compiler/cli/src/org/jetbrains/jet/cli/jvm/PluginCliParser.kt create mode 100644 compiler/plugin-api/src/org/jetbrains/kotlin/compiler/plugin/CommandLineProcessor.kt diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/CommonCompilerArguments.java b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/CommonCompilerArguments.java index 66318bf21f6..826afa751da 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/CommonCompilerArguments.java +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/CommonCompilerArguments.java @@ -23,6 +23,8 @@ import org.jetbrains.annotations.NotNull; import java.util.List; public abstract class CommonCompilerArguments { + public static final String PLUGIN_OPTION_FORMAT = ":="; + @Argument(value = "nowarn", description = "Generate no warnings") public boolean suppressWarnings; @@ -41,6 +43,14 @@ public abstract class CommonCompilerArguments { @Argument(value = "Xno-inline", description = "Disable method inlining") public boolean noInline; + @Argument(value = "Xplugin", description = "Load a plugin from the given classpath") + @ValueDescription("") + public String[] pluginClasspaths; + + @Argument(value = "P", description = "Pass an option to a plugin") + @ValueDescription(PLUGIN_OPTION_FORMAT) + public String[] pluginOptions; + public List freeArgs = new SmartList(); @NotNull diff --git a/compiler/cli/src/org/jetbrains/jet/cli/jvm/PluginCliParser.kt b/compiler/cli/src/org/jetbrains/jet/cli/jvm/PluginCliParser.kt new file mode 100644 index 00000000000..55e34640f59 --- /dev/null +++ b/compiler/cli/src/org/jetbrains/jet/cli/jvm/PluginCliParser.kt @@ -0,0 +1,109 @@ +/* + * Copyright 2010-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.jet.cli.jvm + +import org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor +import org.jetbrains.jet.cli.common.arguments.CommonCompilerArguments +import kotlin.platform.* +import java.util.jar.JarFile +import java.util.jar.Attributes +import org.jetbrains.kotlin.compiler.plugin.CliOptionProcessingException +import java.util.regex.Pattern +import org.jetbrains.jet.utils.valuesToMap +import org.jetbrains.jet.config.CompilerConfiguration +import com.intellij.util.containers.MultiMap +import org.jetbrains.kotlin.compiler.plugin.CliOption + +public object PluginCliParser { + + public val PLUGIN_ARGUMENT_PREFIX: String = "plugin:" + + private fun getCommandLineProcessors(arguments: CommonCompilerArguments): Collection { + return arguments.pluginClasspaths!!.map { + loadCommandLineProcessor(JarFile(it).getManifest()?.getAttributes("org.jetbrains.kotlin.compiler.plugin")) + }.filterNotNull() + } + + private fun loadCommandLineProcessor(attributes: Attributes?): CommandLineProcessor? { + if (attributes == null) return null + + val processorClassName = attributes.getValue("CommandLineProcessor") + if (processorClassName == null) return null + + try { + val processorClass = Class.forName(processorClassName) + return processorClass.newInstance() as CommandLineProcessor + } + catch (e: Throwable) { + throw CliOptionProcessingException("Loading plugin component failed: $processorClassName", e) + } + } + + [platformStatic] + fun processPluginOptions(arguments: CommonCompilerArguments, configuration: CompilerConfiguration) { + val optionValuesByPlugin = arguments.pluginOptions!!.map { parsePluginOption(it) }.groupBy { + if (it == null) throw CliOptionProcessingException("Wrong plugin option format: $it, should be ${CommonCompilerArguments.PLUGIN_OPTION_FORMAT}") + it.optionName + } + + val processors = getCommandLineProcessors(arguments) + for (processor in processors) { + val declaredOptions = processor.pluginOptions.valuesToMap { it.name } + val optionsToValues = MultiMap() + + for (optionValue in optionValuesByPlugin[processor.pluginId].orEmpty()) { + val option = declaredOptions[optionValue!!.optionName] + if (option == null) { + 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 CliOptionProcessingException("Required plugin option not present: ${processor.pluginId}:${option.name}") + } + if (!option.allowMultipleOccurrences && values.size() > 1) { + throw CliOptionProcessingException("Multiple values not allowed for plugin option ${processor.pluginId}:${option.name}") + } + + for (value in values) { + processor.processOption(option, value.value, configuration) + } + } + } + } + + private class PluginOptionValue( + val pluginId: String, + val optionName: String, + val value: String + ) { + override fun toString() = "$pluginId:$optionName=$value" + } + + private fun parsePluginOption(argumentValue: String): PluginOptionValue? { + val pattern = Pattern.compile("""^([^:]*):([^=]*)=(.*)$""") + val matcher = pattern.matcher(argumentValue) + if (matcher.matches()) { + return PluginOptionValue(matcher.group(1)!!, matcher.group(2)!!, matcher.group(3)!!) + } + + return null + } +} \ No newline at end of file diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/K2JVMCompiler.java b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/K2JVMCompiler.java index 6ca4fcd69ff..0a6514a9a77 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/K2JVMCompiler.java +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/K2JVMCompiler.java @@ -20,6 +20,7 @@ import com.google.common.base.Splitter; import com.google.common.collect.Lists; import com.intellij.openapi.Disposable; import org.jetbrains.annotations.NotNull; +import org.jetbrains.jet.cli.jvm.PluginCliParser; import org.jetbrains.kotlin.cli.common.CLICompiler; import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys; import org.jetbrains.kotlin.cli.common.ExitCode; @@ -29,6 +30,7 @@ import org.jetbrains.kotlin.cli.common.modules.ModuleScriptData; import org.jetbrains.kotlin.cli.jvm.compiler.*; import org.jetbrains.kotlin.cli.jvm.repl.ReplFromTerminal; import org.jetbrains.kotlin.codegen.CompilationException; +import org.jetbrains.kotlin.compiler.plugin.CliOptionProcessingException; import org.jetbrains.kotlin.config.CommonConfigurationKeys; import org.jetbrains.kotlin.config.CompilerConfiguration; import org.jetbrains.kotlin.config.Services; @@ -90,12 +92,13 @@ public class K2JVMCompiler extends CLICompiler { return INTERNAL_ERROR; } - if (arguments.androidRes != null) { - configuration.put(JVMConfigurationKeys.ANDROID_RES_PATH, arguments.androidRes); + try { + PluginCliParser.processPluginOptions(arguments, configuration); } - - if (arguments.androidManifest != null) { - configuration.put(JVMConfigurationKeys.ANDROID_MANIFEST, arguments.androidManifest); + catch (CliOptionProcessingException e) { + // TODO Print usage? + messageCollector.report(CompilerMessageSeverity.ERROR, e.getMessage(), CompilerMessageLocation.NO_LOCATION); + return INTERNAL_ERROR; } if (arguments.script) { diff --git a/compiler/plugin-api/plugin-api.iml b/compiler/plugin-api/plugin-api.iml index 5b956ef8cda..cde2d646b38 100644 --- a/compiler/plugin-api/plugin-api.iml +++ b/compiler/plugin-api/plugin-api.iml @@ -8,6 +8,7 @@ + diff --git a/compiler/plugin-api/src/org/jetbrains/kotlin/compiler/plugin/CommandLineProcessor.kt b/compiler/plugin-api/src/org/jetbrains/kotlin/compiler/plugin/CommandLineProcessor.kt new file mode 100644 index 00000000000..d68212b1fd5 --- /dev/null +++ b/compiler/plugin-api/src/org/jetbrains/kotlin/compiler/plugin/CommandLineProcessor.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2010-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.compiler.plugin + +import org.jetbrains.jet.config.CompilerConfiguration + +public class CliOption( + public val name: String, + public val valueDescription: String, + public val description: String, + public val required: Boolean = true, + public val allowMultipleOccurrences: Boolean = false +) + +public class CliOptionProcessingException(message: String, cause: Throwable? = null): RuntimeException(message, cause) + +public trait CommandLineProcessor { + public val pluginId: String + public val pluginOptions: Collection + + [throws(javaClass())] + public fun processOption(option: CliOption, value: String, configuration: CompilerConfiguration) +} \ No newline at end of file