diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/mpp/CompilationSpecificPluginPath.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/mpp/CompilationSpecificPluginPath.kt new file mode 100644 index 00000000000..c4b074cb66e --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/mpp/CompilationSpecificPluginPath.kt @@ -0,0 +1,185 @@ +/* + * Copyright 2010-2021 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. + */ +@file:Suppress("invisible_reference", "invisible_member", "FunctionName") + +package org.jetbrains.kotlin.gradle.mpp + +import org.gradle.api.Project +import org.gradle.api.internal.project.ProjectInternal +import org.gradle.api.provider.Provider +import org.gradle.testfixtures.ProjectBuilder +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension +import org.jetbrains.kotlin.gradle.dsl.kotlinExtension +import org.jetbrains.kotlin.gradle.plugin.* +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +internal class CompilationSpecificPluginPath { + @Test + fun `each compilation should have its own plugin classpath`() { + val project = buildProjectWithMPP { + kotlin { + jvm("jvm1") + jvm("jvm2") + js("js") { browser() } + } + } + + // Expect to see plugin classpath for each target compilation + listOf("jvm1", "jvm2", "js") + .map(String::capitalize) + .flatMap { listOf(pluginClassPathConfiguration(it, "main"), pluginClassPathConfiguration(it, "test")) } + .plus(pluginClassPathConfiguration("metadata", "main")) // and also one for metadata + .forEach { assertNotNull(project.configurations.findByName(it), "Configuration $it should exist") } + } + + @Test + fun `kotlin plugin author can control plugin artifacts per compilation`() { + // Given platform-specific Kotlin plugins + class JvmOnly : FakeSubPlugin("jvmOnly", null, { target.platformType == KotlinPlatformType.jvm }) + class JsOnly : FakeSubPlugin("jsOnly", null, { target.platformType == KotlinPlatformType.js }) + + // When apply them to a Kotlin MPP Project + val project = buildProjectWithMPP { + plugins.apply(JvmOnly::class.java) + plugins.apply(JsOnly::class.java) + + kotlin { + jvm("desktop") + js("web", IR) { + browser() + } + } + } + project.evaluate() + + // Then each plugin classpath should have its own dependency + assertEquals(listOf("kotlin-scripting-compiler-embeddable", "jvmOnly"), project.subplugins("desktop")) + assertEquals(listOf("kotlin-scripting-compiler-embeddable", "jsOnly"), project.subplugins("web")) + + // And each compilation task should have its own plugin classpath + val compileDesktop = project.tasks.getByName("compileKotlinDesktop") as KotlinCompile + val expectedConfig = project.configurations.getByName(pluginClassPathConfiguration("desktop", "main")) + assertEquals(expectedConfig, compileDesktop.pluginClasspath) + } + + @Test + fun `native artifact should take precedence over regular artifact for native platform`() { + // Given common plugin but with native-specific artifact + class NativeSpecificPlugin : FakeSubPlugin("common", "native", { true }) + + val project = buildProjectWithMPP { + plugins.apply(NativeSpecificPlugin::class.java) + + kotlin { + jvm() + linuxX64() + } + } + project.evaluate() + + assertTrue("common" in project.subplugins("jvm")) + assertTrue("native" in project.subplugins("linuxX64")) + assertTrue("common" !in project.subplugins("linuxX64")) + } + + @Test + fun `it should be possible to add common plugin classpath to all compilations except native`() { + // Given MPP project + val project = buildProjectWithMPP { + kotlin { + jvm() + js { browser() } + linuxX64("native") + } + } + + // When adding a shared plugin to common Plugin Classpath + project.dependencies.add(PLUGIN_CLASSPATH_CONFIGURATION_NAME, "test:shared") + project.evaluate() + + // Then ALL compilations should have shared plugin EXCEPT native + assertTrue("shared" in project.subplugins("jvm")) + assertTrue("shared" in project.subplugins("js")) + assertTrue("shared" in project.subplugins("js", "test")) + assertTrue("shared" in project.subplugins("metadata")) + assertTrue("shared" !in project.subplugins("native")) + } + + @Test + fun `native compilations should have its own shared plugin classpath`() { + // Given MPP project + val project = buildProjectWithMPP { + kotlin { + jvm() + linuxX64("linux") + linuxX64("mac") + } + } + + // When adding a shared plugin to native Plugin Classpath + project.dependencies.add(NATIVE_COMPILER_PLUGIN_CLASSPATH_CONFIGURATION_NAME, "test:shared") + project.evaluate() + + // Then ALL native compilations should have shared plugin + assertTrue("shared" in project.subplugins("linux")) + assertTrue("shared" in project.subplugins("mac")) + assertTrue("shared" in project.subplugins("mac", "test")) + + // And all non-native should not have it + assertTrue("shared" !in project.subplugins("metadata")) + assertTrue("shared" !in project.subplugins("jvm")) + } + + private fun pluginClassPathConfiguration(target: String, compilation: String) = + "kotlinCompilerPluginClasspath${target.capitalize()}${compilation.capitalize()}" + + private fun Project.subplugins(target: String, compilation: String = "main") = this + .configurations + .getByName(pluginClassPathConfiguration(target, compilation)) + .allDependencies + .map { it.name } + + + private fun buildProjectWithMPP(code: Project.() -> Unit): ProjectInternal { + val project = ProjectBuilder.builder().build() + project.plugins.apply("kotlin-multiplatform") + project.code() + return project as ProjectInternal + } + + private fun Project.kotlin(code: KotlinMultiplatformExtension.() -> Unit) { + val kotlin = project.kotlinExtension as KotlinMultiplatformExtension + kotlin.code() + } + + private abstract class FakeSubPlugin( + val id: String, + val idNative: String? = null, + val isApplicablePredicate: KotlinCompilation<*>.() -> Boolean + ) : KotlinCompilerPluginSupportPlugin { + override fun isApplicable(kotlinCompilation: KotlinCompilation<*>): Boolean = kotlinCompilation.isApplicablePredicate() + + override fun applyToCompilation(kotlinCompilation: KotlinCompilation<*>): Provider> = + kotlinCompilation.target.project.provider { emptyList() } + + override fun getCompilerPluginId(): String = id + + override fun getPluginArtifact(): SubpluginArtifact = SubpluginArtifact( + "test", + id + ) + + override fun getPluginArtifactForNative(): SubpluginArtifact? = idNative?.let { + SubpluginArtifact( + "test", + it + ) + } + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/KaptWithKotlincTask.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/KaptWithKotlincTask.kt index 00ec520482f..e5724d1f295 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/KaptWithKotlincTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/KaptWithKotlincTask.kt @@ -21,7 +21,6 @@ import org.jetbrains.kotlin.gradle.internal.kapt.incremental.KaptIncrementalChan import org.jetbrains.kotlin.gradle.internal.tasks.allOutputFiles import org.jetbrains.kotlin.gradle.logging.GradleKotlinLogger import org.jetbrains.kotlin.gradle.logging.GradlePrintingMessageCollector -import org.jetbrains.kotlin.gradle.plugin.PLUGIN_CLASSPATH_CONFIGURATION_NAME import org.jetbrains.kotlin.gradle.tasks.CompilerPluginOptions import org.jetbrains.kotlin.gradle.tasks.GradleCompileTaskProvider import org.jetbrains.kotlin.gradle.utils.getValue @@ -35,14 +34,7 @@ abstract class KaptWithKotlincTask : KaptTask(), CompilerArgumentAwareWithInput< @get:Classpath @get:InputFiles - @Suppress("unused") - internal val kotlinTaskPluginClasspaths - get() = kotlinCompileTask.pluginClasspath - - @get:Classpath - @get:InputFiles - val pluginClasspath: FileCollection - get() = project.configurations.getByName(PLUGIN_CLASSPATH_CONFIGURATION_NAME) + val pluginClasspath: FileCollection get() = kotlinCompileTask.pluginClasspath @get:Internal val taskProvider = GradleCompileTaskProvider(this) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt index 1e76b2507fc..d158b1c6f85 100755 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt @@ -564,9 +564,6 @@ internal abstract class AbstractKotlinPlugin( // Setup the consuming configurations: project.dependencies.attributesSchema.attribute(KotlinPlatformType.attribute) - kotlinTarget.compilations.all { compilation -> - AbstractKotlinTargetConfigurator.defineConfigurationsForCompilation(compilation) - } project.configurations.getByName("default").apply { setupAsLocalTargetSpecificConfigurationIfSupported(kotlinTarget) @@ -595,6 +592,7 @@ internal abstract class AbstractKotlinPlugin( buildSourceSetProcessor: (AbstractKotlinCompilation<*>) -> KotlinSourceSetProcessor<*> ) { kotlinTarget.compilations.all { compilation -> + AbstractKotlinTargetConfigurator.defineConfigurationsForCompilation(compilation) buildSourceSetProcessor(compilation).run() } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt index b6b57061638..065af6fe07f 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt @@ -24,7 +24,6 @@ import org.gradle.api.internal.FeaturePreviews import org.gradle.api.logging.Logger import org.gradle.api.logging.Logging import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry -import org.jetbrains.kotlin.cli.common.CompilerSystemProperties import org.jetbrains.kotlin.gradle.dsl.* import org.jetbrains.kotlin.gradle.logging.kotlinDebug import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMetadataTarget diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinTargetConfigurator.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinTargetConfigurator.kt index 5a77f3dded5..2f27c453ef9 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinTargetConfigurator.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinTargetConfigurator.kt @@ -272,6 +272,17 @@ abstract class AbstractKotlinTargetConfigurator val target = compilation.target val configurations = target.project.configurations + val pluginConfiguration = configurations.maybeCreate(compilation.pluginConfigurationName).apply { + if (target.platformType == KotlinPlatformType.native) { + extendsFrom(configurations.getByName(NATIVE_COMPILER_PLUGIN_CLASSPATH_CONFIGURATION_NAME)) + } else { + extendsFrom(target.project.commonKotlinPluginClasspath) + } + isVisible = false + isCanBeConsumed = false + description = "Kotlin compiler plugins for $compilation" + } + val compileConfiguration = configurations.findByName(compilation.deprecatedCompileConfigurationName)?.apply { isCanBeConsumed = false setupAsLocalTargetSpecificConfigurationIfSupported(target) @@ -493,3 +504,7 @@ fun Configuration.usesPlatformOf(target: KotlinTarget): Configuration { } return this } + +internal val Project.commonKotlinPluginClasspath get() = configurations.getByName(PLUGIN_CLASSPATH_CONFIGURATION_NAME) +internal val KotlinCompilation<*>.pluginConfigurationName + get() = lowerCamelCaseName(PLUGIN_CLASSPATH_CONFIGURATION_NAME, target.disambiguationClassifier, compilationName) \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/SubpluginEnvironment.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/SubpluginEnvironment.kt index 7aa9279ca9e..0754d5e6ebf 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/SubpluginEnvironment.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/SubpluginEnvironment.kt @@ -66,12 +66,11 @@ class SubpluginEnvironment( val pluginId = subplugin.getCompilerPluginId() project.logger.kotlinDebug { "Loading subplugin $pluginId" } - subplugin.getPluginArtifact().let { artifact -> - project.addMavenDependency(PLUGIN_CLASSPATH_CONFIGURATION_NAME, artifact) - } - - subplugin.getPluginArtifactForNative()?.let { artifact -> - project.addMavenDependency(NATIVE_COMPILER_PLUGIN_CLASSPATH_CONFIGURATION_NAME, artifact) + val nativeArtifact = subplugin.getPluginArtifactForNative() + if (nativeArtifact != null && kotlinCompilation.platformType == KotlinPlatformType.native) { + project.addMavenDependency(kotlinCompilation.pluginConfigurationName, nativeArtifact) + } else { + project.addMavenDependency(kotlinCompilation.pluginConfigurationName, subplugin.getPluginArtifact()) } val subpluginOptionsProvider = subplugin.applyToCompilation(kotlinCompilation) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTargetConfigurator.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTargetConfigurator.kt index be99731866f..baed502ddef 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTargetConfigurator.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTargetConfigurator.kt @@ -65,7 +65,9 @@ open class KotlinNativeTargetConfigurator( .addSubpluginOptions(project, compilation) compilation.compileKotlinTaskProvider.configure { - it.compilerPluginClasspath = project.configurations.getByName(NATIVE_COMPILER_PLUGIN_CLASSPATH_CONFIGURATION_NAME) + it.compilerPluginClasspath = project + .configurations + .getByName(compilation.pluginConfigurationName) } } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt index b0ac0b30a7c..2257c1fb735 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt @@ -38,10 +38,8 @@ import org.jetbrains.kotlin.gradle.internal.* import org.jetbrains.kotlin.gradle.internal.tasks.TaskWithLocalState import org.jetbrains.kotlin.gradle.internal.tasks.allOutputFiles import org.jetbrains.kotlin.gradle.logging.* +import org.jetbrains.kotlin.gradle.plugin.* import org.jetbrains.kotlin.gradle.plugin.COMPILER_CLASSPATH_CONFIGURATION_NAME -import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper -import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformPluginBase -import org.jetbrains.kotlin.gradle.plugin.PLUGIN_CLASSPATH_CONFIGURATION_NAME import org.jetbrains.kotlin.gradle.plugin.mpp.AbstractKotlinCompilation import org.jetbrains.kotlin.gradle.plugin.mpp.associateWithTransitiveClosure import org.jetbrains.kotlin.gradle.plugin.mpp.ownModuleName @@ -219,7 +217,9 @@ abstract class AbstractKotlinCompile() : AbstractKo @get:InputFiles @get:Classpath - open val pluginClasspath: FileCollection = project.configurations.getByName(PLUGIN_CLASSPATH_CONFIGURATION_NAME) + open val pluginClasspath: FileCollection by project.provider { + project.configurations.getByName(taskData.compilation.pluginConfigurationName) + } @get:Internal internal val pluginOptions = CompilerPluginOptions()