diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinGradlePluginIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinGradlePluginIT.kt index 4172d830d54..b4c70238745 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinGradlePluginIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinGradlePluginIT.kt @@ -17,6 +17,8 @@ package org.jetbrains.kotlin.gradle import org.gradle.api.logging.LogLevel +import org.jetbrains.kotlin.gradle.plugin.MULTIPLE_KOTLIN_PLUGINS_LOADED_WARNING +import org.jetbrains.kotlin.gradle.plugin.MULTIPLE_KOTLIN_PLUGINS_SPECIFIC_PROJECTS_WARNING import org.jetbrains.kotlin.gradle.tasks.USING_JVM_INCREMENTAL_COMPILATION_MESSAGE import org.jetbrains.kotlin.gradle.util.* import org.junit.Test @@ -823,4 +825,70 @@ class KotlinGradleIT : BaseGradleIT() { assertTasksExecuted(":jvm-app:compileKotlin") } } + + @Test + fun testDetectingDifferentClassLoaders() = with(Project("kt-27059-pom-rewriting", GradleVersionRequired.AtLeast("4.10.2"))) { + setupWorkingDir() + + val originalRootBuildScript = gradleBuildScript().readText() + gradleBuildScript().modify(::transformBuildScriptWithPluginsDsl) + + build("publish", "-PmppProjectDependency=true") { + assertSuccessful() + assertNotContains(MULTIPLE_KOTLIN_PLUGINS_LOADED_WARNING) + assertNotContains(MULTIPLE_KOTLIN_PLUGINS_SPECIFIC_PROJECTS_WARNING) + } + + // Specify the plugin versions in the subprojects with different plugin sets – this will make Gradle use separate class loaders + gradleBuildScript().modify { + originalRootBuildScript.checkedReplace("id \"org.jetbrains.kotlin.multiplatform\"", "//") + } + gradleBuildScript("mpp-lib").modify { + it.checkedReplace( + "id \"org.jetbrains.kotlin.multiplatform\"", + "id \"org.jetbrains.kotlin.multiplatform\" version \"\"" + ).let(::transformBuildScriptWithPluginsDsl) + } + gradleBuildScript("jvm-app").modify { + it.checkedReplace( + "id \"org.jetbrains.kotlin.jvm\"", + "id \"org.jetbrains.kotlin.jvm\" version \"\"" + ).let(::transformBuildScriptWithPluginsDsl) + } + gradleBuildScript("js-app").modify { + it.checkedReplace( + "id \"kotlin2js\"", + "id \"kotlin2js\" version \"\"" + ).let(::transformBuildScriptWithPluginsDsl) + } + + // Also include another project via a composite build: + transformProjectWithPluginsDsl("allopenPluginsDsl", directoryPrefix = "pluginsDsl").let { other -> + val result = other.projectName + other.setupWorkingDir() + other.projectDir.copyRecursively(projectDir.resolve(result)) + gradleSettingsScript().appendText("\nincludeBuild(\"${result}\")") + gradleBuildScript().appendText( + "\ntasks.create(\"publish\").dependsOn(gradle.includedBuild(\"${result}\").task(\":assemble\"))" + ) + result + } + + build("publish", "-PmppProjectDependency=true") { + assertSuccessful() + assertContains(MULTIPLE_KOTLIN_PLUGINS_LOADED_WARNING) + + val specificProjectsReported = Regex("$MULTIPLE_KOTLIN_PLUGINS_SPECIFIC_PROJECTS_WARNING((?:'.*'(?:, )?)+)") + .find(output)!!.groupValues[1].split(", ").map { it.removeSurrounding("'") }.toSet() + + assertEquals(setOf(":mpp-lib", ":jvm-app", ":js-app"), specificProjectsReported) + } + + // Test the flag that turns off the warnings + build("publish", "-PmppProjectDependency=true", "-Pkotlin.pluginLoadedInMultipleProjects.ignore=true") { + assertSuccessful() + assertNotContains(MULTIPLE_KOTLIN_PLUGINS_LOADED_WARNING) + assertNotContains(MULTIPLE_KOTLIN_PLUGINS_SPECIFIC_PROJECTS_WARNING) + } + } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinGradleBuildServices.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinGradleBuildServices.kt index e6bd5e36957..32ec52ba00b 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinGradleBuildServices.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinGradleBuildServices.kt @@ -18,23 +18,19 @@ package org.jetbrains.kotlin.gradle.plugin import org.gradle.BuildAdapter import org.gradle.BuildResult +import org.gradle.api.Project import org.gradle.api.invocation.Gradle -import org.gradle.api.logging.Logger import org.gradle.api.logging.Logging +import org.gradle.api.plugins.ExtraPropertiesExtension import org.jetbrains.kotlin.compilerRunner.DELETED_SESSION_FILE_PREFIX import org.jetbrains.kotlin.compilerRunner.GradleCompilerRunner -import org.jetbrains.kotlin.gradle.plugin.internal.state.TaskLoggers import org.jetbrains.kotlin.gradle.logging.kotlinDebug import org.jetbrains.kotlin.gradle.plugin.internal.state.TaskExecutionResults -import org.jetbrains.kotlin.gradle.report.KotlinBuildReporter +import org.jetbrains.kotlin.gradle.plugin.internal.state.TaskLoggers import org.jetbrains.kotlin.gradle.report.configureBuildReporter -import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile import org.jetbrains.kotlin.gradle.utils.relativeToRoot import org.jetbrains.kotlin.utils.addToStdlib.sumByLong -import java.io.File import java.lang.management.ManagementFactory -import java.text.SimpleDateFormat -import java.util.* internal class KotlinGradleBuildServices private constructor( private val gradle: Gradle @@ -137,5 +133,58 @@ internal class KotlinGradleBuildServices private constructor( private fun getGcCount(): Long = ManagementFactory.getGarbageCollectorMXBeans().sumByLong { Math.max(0, it.collectionCount) } + + private var loadedInProjectPath: String? = null + + @Synchronized + internal fun detectKotlinPluginLoadedInMultipleProjects(project: Project, kotlinPluginVersion: String) { + val projectPath = project.path + + val loadedInProjectsPropertyName = "kotlin.plugin.loaded.in.projects.${kotlinPluginVersion}" + + if (loadedInProjectPath == null) { + loadedInProjectPath = projectPath + + val ext = project.rootProject.extensions.getByType(ExtraPropertiesExtension::class.java) + + if (!ext.has(loadedInProjectsPropertyName)) { + ext.set(loadedInProjectsPropertyName, projectPath) + + gradle.taskGraph.whenReady { + val loadedInProjects = (ext.get(loadedInProjectsPropertyName) as String).split(";") + if (loadedInProjects.size > 1) { + if (PropertiesProvider(project).ignorePluginLoadedInMultipleProjects != true) { + project.logger.warn(MULTIPLE_KOTLIN_PLUGINS_LOADED_WARNING) + project.logger.warn( + MULTIPLE_KOTLIN_PLUGINS_SPECIFIC_PROJECTS_WARNING + loadedInProjects.joinToString(limit = 4) { "'$it'" } + ) + } + project.logger.info( + "$MULTIPLE_KOTLIN_PLUGINS_SPECIFIC_PROJECTS_INFO: " + + loadedInProjects.joinToString { "'$it'" } + ) + } + } + } else { + ext.set(loadedInProjectsPropertyName, (ext.get(loadedInProjectsPropertyName) as String) + ";" + loadedInProjectPath) + } + } + } } +const val MULTIPLE_KOTLIN_PLUGINS_LOADED_WARNING: String = + "\nThe Kotlin Gradle plugin was loaded multiple times in different subprojects, which is not supported and may break the build. \n" + + + "This might happen in subprojects that apply the Kotlin plugins with the Gradle 'plugins { ... }' DSL if they specify " + + "explicit versions, even if the versions are equal.\n" + + + "Please add the Kotlin plugin to the common parent project or the root project, then remove the versions in the subprojects.\n" + + + "If the parent project does not need the plugin, add 'apply false' to the plugin line.\n" + + + "See: https://docs.gradle.org/current/userguide/plugins.html#sec:subprojects_plugins_dsl" + +const val MULTIPLE_KOTLIN_PLUGINS_SPECIFIC_PROJECTS_WARNING: String = + "The Kotlin plugin was loaded in the following projects: " + +const val MULTIPLE_KOTLIN_PLUGINS_SPECIFIC_PROJECTS_INFO: String = "The full list of projects that loaded the Kotlin plugin is: " 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 7eb5ec846fb..a6e07f95839 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 @@ -67,6 +67,8 @@ abstract class KotlinBasePluginWrapper( System.setProperty(org.jetbrains.kotlin.cli.common.KOTLIN_COMPILER_ENVIRONMENT_KEEPALIVE_PROPERTY, "true") val kotlinGradleBuildServices = KotlinGradleBuildServices.getInstance(project.gradle) + kotlinGradleBuildServices.detectKotlinPluginLoadedInMultipleProjects(project, kotlinPluginVersion) + project.createKotlinExtension(projectExtensionClass).apply { fun kotlinSourceSetContainer(factory: NamedDomainObjectFactory) = project.container(KotlinSourceSet::class.java, factory) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinProperties.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinProperties.kt index 890e60f0e1a..c8b3391ea3f 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinProperties.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinProperties.kt @@ -82,6 +82,9 @@ internal class PropertiesProvider(private val project: Project) { val keepMppDependenciesIntactInPoms: Boolean? get() = booleanProperty("kotlin.mpp.keepMppDependenciesIntactInPoms") + val ignorePluginLoadedInMultipleProjects: Boolean? + get() = booleanProperty("kotlin.pluginLoadedInMultipleProjects.ignore") + /** * Enables parallel tasks execution within a project with Workers API. * Does not enable using actual worker proccesses