From 1f65f8bc9772f34033c091536f03dfa872576708 Mon Sep 17 00:00:00 2001 From: Yan Zhulanow Date: Tue, 31 Oct 2017 11:35:42 +0900 Subject: [PATCH] Encode all compiler plugin arguments for Android Extensions in kapt Commas in option values breaks the option parsing in daemon (KT-20235). --- .../compiler/plugin/CommandLineProcessor.kt | 10 ++++ .../testData/cli/jvm/pluginSimpleUsage.args | 13 ----- .../testData/cli/jvm/pluginSimpleUsage.kt | 8 ---- .../testData/cli/jvm/pluginSimpleUsage.out | 10 ---- .../kotlin/cli/CliTestGenerated.java | 6 --- .../org/jetbrains/kotlin/utils/pluginUtils.kt | 46 ++++++++++++++++++ .../gradle/internal/AndroidSubplugin.kt | 4 +- .../internal/Kapt3KotlinGradleSubplugin.kt | 18 +++---- .../kotlin/gradle/internal/subpluginUtils.kt | 47 +++++++++++++++++++ .../synthetic/AndroidComponentRegistrar.kt | 14 ++++-- .../org/jetbrains/kotlin/kapt3/Kapt3Plugin.kt | 13 +++-- 11 files changed, 132 insertions(+), 57 deletions(-) delete mode 100644 compiler/testData/cli/jvm/pluginSimpleUsage.args delete mode 100644 compiler/testData/cli/jvm/pluginSimpleUsage.kt delete mode 100644 compiler/testData/cli/jvm/pluginSimpleUsage.out create mode 100644 compiler/util/src/org/jetbrains/kotlin/utils/pluginUtils.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/subpluginUtils.kt 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 index cb855c0fc17..c463c3ccea2 100644 --- a/compiler/plugin-api/src/org/jetbrains/kotlin/compiler/plugin/CommandLineProcessor.kt +++ b/compiler/plugin-api/src/org/jetbrains/kotlin/compiler/plugin/CommandLineProcessor.kt @@ -30,4 +30,14 @@ interface CommandLineProcessor { paths.add(value) put(option, paths) } + + fun CompilerConfiguration.applyOptionsFrom(map: Map>, pluginOptions: Collection) { + for ((key, values) in map) { + val option = pluginOptions.firstOrNull { it.name == key } ?: continue + + for (value in values) { + processOption(option, value, this) + } + } + } } \ No newline at end of file diff --git a/compiler/testData/cli/jvm/pluginSimpleUsage.args b/compiler/testData/cli/jvm/pluginSimpleUsage.args deleted file mode 100644 index 2d2d5b9a492..00000000000 --- a/compiler/testData/cli/jvm/pluginSimpleUsage.args +++ /dev/null @@ -1,13 +0,0 @@ --d -$TEMP_DIR$ --Xplugin=dist/kotlinc/lib/android-extensions-compiler.jar --P -plugin\:org.jetbrains.kotlin.android\:package=com.myapp -$TESTDATA_DIR$/pluginSimple.kt -$TESTDATA_DIR$/androidPlugin/androidApp.kt -$TESTDATA_DIR$/androidPlugin/androidView.kt -$TESTDATA_DIR$/androidPlugin/androidWidget.kt -$TESTDATA_DIR$/androidPlugin/fakeAppwidgetPackage.kt -$TESTDATA_DIR$/androidPlugin/fakeInputMethodServicePackage.kt -$TESTDATA_DIR$/androidPlugin/fakeOpenglPackage.kt -$TESTDATA_DIR$/androidPlugin/fakeWebkitPackage.kt diff --git a/compiler/testData/cli/jvm/pluginSimpleUsage.kt b/compiler/testData/cli/jvm/pluginSimpleUsage.kt deleted file mode 100644 index ee72fbe10f6..00000000000 --- a/compiler/testData/cli/jvm/pluginSimpleUsage.kt +++ /dev/null @@ -1,8 +0,0 @@ -import android.view.* -import android.app.* -import android.widget.* -import kotlinx.android.synthetic.main.layout.* - -class MyActivity : Activity() { - { textView.setText("Some text") } -} \ No newline at end of file diff --git a/compiler/testData/cli/jvm/pluginSimpleUsage.out b/compiler/testData/cli/jvm/pluginSimpleUsage.out deleted file mode 100644 index f90ed926dfb..00000000000 --- a/compiler/testData/cli/jvm/pluginSimpleUsage.out +++ /dev/null @@ -1,10 +0,0 @@ -error: required plugin option not present: org.jetbrains.kotlin.android:variant - -Plugin "org.jetbrains.kotlin.android" usage: - variant Android build variant (required, multiple) - package Application package (required) - experimental true/false Enable experimental features - defaultCacheImplementation hashMap/sparseArray/none - Default cache implementation for module - -COMPILATION_ERROR \ No newline at end of file diff --git a/compiler/tests/org/jetbrains/kotlin/cli/CliTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/cli/CliTestGenerated.java index 5c9c07becdd..296721c26b1 100644 --- a/compiler/tests/org/jetbrains/kotlin/cli/CliTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/cli/CliTestGenerated.java @@ -398,12 +398,6 @@ public class CliTestGenerated extends AbstractCliTest { doJvmTest(fileName); } - @TestMetadata("pluginSimpleUsage.args") - public void testPluginSimpleUsage() throws Exception { - String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cli/jvm/pluginSimpleUsage.args"); - doJvmTest(fileName); - } - @TestMetadata("returnAsWhenKey.args") public void testReturnAsWhenKey() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cli/jvm/returnAsWhenKey.args"); diff --git a/compiler/util/src/org/jetbrains/kotlin/utils/pluginUtils.kt b/compiler/util/src/org/jetbrains/kotlin/utils/pluginUtils.kt new file mode 100644 index 00000000000..f20432cf4f9 --- /dev/null +++ b/compiler/util/src/org/jetbrains/kotlin/utils/pluginUtils.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2010-2017 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.utils + +import java.io.ByteArrayInputStream +import java.io.ObjectInputStream +import java.util.* + +fun decodePluginOptions(options: String): Map> { + val map = LinkedHashMap>() + + val decodedBytes = Base64.getDecoder().decode(options) + val bis = ByteArrayInputStream(decodedBytes) + val ois = ObjectInputStream(bis) + + val n = ois.readInt() + + repeat(n) { + val key = ois.readUTF() + + val valueCount = ois.readInt() + val values = mutableListOf() + + repeat(valueCount) { + values += ois.readUTF() + } + + map[key] = values + } + + return map +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/AndroidSubplugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/AndroidSubplugin.kt index aec5155ab51..c447e664832 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/AndroidSubplugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/AndroidSubplugin.kt @@ -128,7 +128,7 @@ class AndroidSubplugin : KotlinGradleSubplugin { addVariant(sourceSet) } - return pluginOptions + return wrapPluginOptions(pluginOptions, "configuration") } private fun getLayoutDirectories(resDirectories: Collection): List { @@ -198,7 +198,7 @@ class AndroidSubplugin : KotlinGradleSubplugin { addSourceSetAsVariant(variantName) } - return pluginOptions + return wrapPluginOptions(pluginOptions, "configuration") } // Android25ProjectHandler.KaptVariant actually contains BaseVariant, not BaseVariantData diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/Kapt3KotlinGradleSubplugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/Kapt3KotlinGradleSubplugin.kt index 36e1abc762c..eca2410f02c 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/Kapt3KotlinGradleSubplugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/Kapt3KotlinGradleSubplugin.kt @@ -30,12 +30,12 @@ import org.gradle.api.tasks.compile.JavaCompile import org.jetbrains.kotlin.gradle.plugin.* import org.jetbrains.kotlin.gradle.tasks.CompilerPluginOptions import org.jetbrains.kotlin.gradle.tasks.KotlinCompile -import java.io.ByteArrayOutputStream import java.io.File -import java.io.ObjectOutputStream import org.jetbrains.kotlin.gradle.internal.Kapt3GradleSubplugin.Companion.getKaptGeneratedSourcesDir import org.jetbrains.kotlin.gradle.internal.Kapt3GradleSubplugin.Companion.getKaptGeneratedClassesDir import org.jetbrains.kotlin.gradle.internal.Kapt3GradleSubplugin.Companion.getKaptGeneratedKotlinSourcesDir +import java.io.ByteArrayOutputStream +import java.io.ObjectOutputStream import java.util.* // apply plugin: 'kotlin-kapt' @@ -234,9 +234,9 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin { androidPlugin ) + androidOptions + mapOf("kapt.kotlin.generated" to kotlinSourcesOutputDir.absolutePath) - pluginOptions += SubpluginOption("apoptions", encodeOptions(apOptions)) + pluginOptions += SubpluginOption("apoptions", encodeList(apOptions)) - pluginOptions += SubpluginOption("javacArguments", encodeOptions(kaptExtension.getJavacOptions())) + pluginOptions += SubpluginOption("javacArguments", encodeList(kaptExtension.getJavacOptions())) addMiscOptions(pluginOptions) @@ -245,19 +245,19 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin { private fun Kapt3SubpluginContext.buildAndAddOptionsTo(container: CompilerPluginOptions, aptMode: String) { val compilerPluginId = getCompilerPluginId() - for (option in buildOptions(aptMode)) { + for (option in wrapPluginOptions(buildOptions(aptMode), "configuration")) { container.addPluginArgument(compilerPluginId, option.key, option.value) } } - fun encodeOptions(options: Map): String { + private fun encodeList(options: Map): String { val os = ByteArrayOutputStream() val oos = ObjectOutputStream(os) oos.writeInt(options.size) - for ((k, v) in options.entries) { - oos.writeUTF(k) - oos.writeUTF(v) + for ((key, value) in options.entries) { + oos.writeUTF(key) + oos.writeUTF(value) } oos.flush() diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/subpluginUtils.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/subpluginUtils.kt new file mode 100644 index 00000000000..80c80698d37 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/subpluginUtils.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2010-2017 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.gradle.internal + +import org.jetbrains.kotlin.gradle.plugin.SubpluginOption +import java.io.ByteArrayOutputStream +import java.io.ObjectOutputStream +import java.util.* + +fun encodePluginOptions(options: Map>): String { + val os = ByteArrayOutputStream() + val oos = ObjectOutputStream(os) + + oos.writeInt(options.size) + for ((key, values) in options.entries) { + oos.writeUTF(key) + + oos.writeInt(values.size) + for (value in values) { + oos.writeUTF(value) + } + } + + oos.flush() + return Base64.getEncoder().encodeToString(os.toByteArray()) +} + +fun wrapPluginOptions(options: List, newOptionName: String): List { + val groupedOptions = options.groupBy { it.key }.mapValues { opt -> opt.value.map { it.value } } + val encodedOptions = encodePluginOptions(groupedOptions) + val singleOption = SubpluginOption(newOptionName, encodedOptions) + return listOf(singleOption) +} \ No newline at end of file diff --git a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/AndroidComponentRegistrar.kt b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/AndroidComponentRegistrar.kt index 0421c76b21e..64db07f61f7 100644 --- a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/AndroidComponentRegistrar.kt +++ b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/AndroidComponentRegistrar.kt @@ -47,6 +47,7 @@ import org.jetbrains.kotlin.resolve.TargetPlatform import org.jetbrains.kotlin.resolve.extensions.SyntheticResolveExtension import org.jetbrains.kotlin.resolve.jvm.extensions.PackageFragmentProviderExtension import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform +import org.jetbrains.kotlin.utils.decodePluginOptions object AndroidConfigurationKeys { val VARIANT = CompilerConfigurationKey.create>("Android build variant") @@ -59,8 +60,10 @@ class AndroidCommandLineProcessor : CommandLineProcessor { companion object { val ANDROID_COMPILER_PLUGIN_ID: String = "org.jetbrains.kotlin.android" - val VARIANT_OPTION = CliOption("variant", "", "Android build variant", allowMultipleOccurrences = true) - val PACKAGE_OPTION = CliOption("package", "", "Application package") + val CONFIGURATION = CliOption("configuration", "", "Encoded configuration", required = false) + + val VARIANT_OPTION = CliOption("variant", "", "Android build variant", allowMultipleOccurrences = true, required = false) + val PACKAGE_OPTION = CliOption("package", "", "Application package", required = false) val EXPERIMENTAL_OPTION = CliOption("experimental", "true/false", "Enable experimental features", required = false) val DEFAULT_CACHE_IMPL_OPTION = CliOption( "defaultCacheImplementation", "hashMap/sparseArray/none", "Default cache implementation for module", required = false) @@ -72,7 +75,7 @@ class AndroidCommandLineProcessor : CommandLineProcessor { override val pluginId: String = ANDROID_COMPILER_PLUGIN_ID override val pluginOptions: Collection - = listOf(VARIANT_OPTION, PACKAGE_OPTION, EXPERIMENTAL_OPTION, DEFAULT_CACHE_IMPL_OPTION) + = listOf(VARIANT_OPTION, PACKAGE_OPTION, EXPERIMENTAL_OPTION, DEFAULT_CACHE_IMPL_OPTION, CONFIGURATION) override fun processOption(option: CliOption, value: String, configuration: CompilerConfiguration) { when (option) { @@ -80,6 +83,7 @@ class AndroidCommandLineProcessor : CommandLineProcessor { PACKAGE_OPTION -> configuration.put(AndroidConfigurationKeys.PACKAGE, value) EXPERIMENTAL_OPTION -> configuration.put(AndroidConfigurationKeys.EXPERIMENTAL, value) DEFAULT_CACHE_IMPL_OPTION -> configuration.put(AndroidConfigurationKeys.DEFAULT_CACHE_IMPL, value) + CONFIGURATION -> configuration.applyOptionsFrom(decodePluginOptions(value), pluginOptions) else -> throw CliOptionProcessingException("Unknown option: ${option.name}") } } @@ -101,8 +105,8 @@ class AndroidComponentRegistrar : ComponentRegistrar { } override fun registerProjectComponents(project: MockProject, configuration: CompilerConfiguration) { - val applicationPackage = configuration.get(AndroidConfigurationKeys.PACKAGE) - val variants = configuration.get(AndroidConfigurationKeys.VARIANT)?.mapNotNull { parseVariant(it) } ?: emptyList() + val applicationPackage = configuration.get(AndroidConfigurationKeys.PACKAGE) ?: return + val variants = configuration.get(AndroidConfigurationKeys.VARIANT)?.mapNotNull { parseVariant(it) } ?: return val isExperimental = configuration.get(AndroidConfigurationKeys.EXPERIMENTAL) == "true" val globalCacheImpl = parseCacheImplementationType(configuration.get(AndroidConfigurationKeys.DEFAULT_CACHE_IMPL)) diff --git a/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/Kapt3Plugin.kt b/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/Kapt3Plugin.kt index cfd9e375343..56b79dd8a4d 100644 --- a/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/Kapt3Plugin.kt +++ b/plugins/kapt3/src/org/jetbrains/kotlin/kapt3/Kapt3Plugin.kt @@ -42,6 +42,7 @@ import org.jetbrains.kotlin.kapt3.util.KaptLogger import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.resolve.BindingTrace import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension +import org.jetbrains.kotlin.utils.decodePluginOptions import java.io.ByteArrayInputStream import java.io.File import java.io.ObjectInputStream @@ -93,6 +94,8 @@ class Kapt3CommandLineProcessor : CommandLineProcessor { companion object { val ANNOTATION_PROCESSING_COMPILER_PLUGIN_ID: String = "org.jetbrains.kotlin.kapt3" + val CONFIGURATION = CliOption("configuration", "", "Encoded configuration", required = false) + val SOURCE_OUTPUT_DIR_OPTION: CliOption = CliOption("sources", "", "Output path for the generated files", required = false) @@ -145,7 +148,8 @@ class Kapt3CommandLineProcessor : CommandLineProcessor { override val pluginOptions: Collection = listOf(SOURCE_OUTPUT_DIR_OPTION, ANNOTATION_PROCESSOR_CLASSPATH_OPTION, APT_OPTIONS_OPTION, JAVAC_CLI_OPTIONS_OPTION, CLASS_OUTPUT_DIR_OPTION, VERBOSE_MODE_OPTION, STUBS_OUTPUT_DIR_OPTION, APT_ONLY_OPTION, APT_MODE_OPTION, - USE_LIGHT_ANALYSIS_OPTION, CORRECT_ERROR_TYPES_OPTION, ANNOTATION_PROCESSORS_OPTION, INCREMENTAL_DATA_OUTPUT_DIR_OPTION) + USE_LIGHT_ANALYSIS_OPTION, CORRECT_ERROR_TYPES_OPTION, ANNOTATION_PROCESSORS_OPTION, INCREMENTAL_DATA_OUTPUT_DIR_OPTION, + CONFIGURATION) override fun processOption(option: CliOption, value: String, configuration: CompilerConfiguration) { when (option) { @@ -162,6 +166,7 @@ class Kapt3CommandLineProcessor : CommandLineProcessor { APT_MODE_OPTION -> configuration.put(Kapt3ConfigurationKeys.APT_MODE, value) USE_LIGHT_ANALYSIS_OPTION -> configuration.put(Kapt3ConfigurationKeys.USE_LIGHT_ANALYSIS, value) CORRECT_ERROR_TYPES_OPTION -> configuration.put(Kapt3ConfigurationKeys.CORRECT_ERROR_TYPES, value) + CONFIGURATION -> configuration.applyOptionsFrom(decodePluginOptions(value), pluginOptions) else -> throw CliOptionProcessingException("Unknown option: ${option.name}") } } @@ -172,7 +177,7 @@ class Kapt3ComponentRegistrar : ComponentRegistrar { private const val JAVAC_CONTEXT_CLASS = "com.sun.tools.javac.util.Context" } - fun decodeOptions(options: String): Map { + private fun decodeList(options: String): Map { val map = LinkedHashMap() val decodedBytes = Base64.getDecoder().decode(options) @@ -232,8 +237,8 @@ class Kapt3ComponentRegistrar : ComponentRegistrar { return } - val apOptions = configuration.get(APT_OPTIONS)?.let { decodeOptions(it) } ?: emptyMap() - val javacCliOptions = configuration.get(JAVAC_CLI_OPTIONS)?.let { decodeOptions(it) } ?: emptyMap() + val apOptions = configuration.get(APT_OPTIONS)?.let { decodeList(it) } ?: emptyMap() + val javacCliOptions = configuration.get(JAVAC_CLI_OPTIONS)?.let { decodeList(it) } ?: emptyMap() sourcesOutputDir.mkdirs()