diff --git a/libraries/tools/kotlin-gradle-plugin-api/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinCompilation.kt b/libraries/tools/kotlin-gradle-plugin-api/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinCompilation.kt index 453d5eb89ad..f6348400d63 100644 --- a/libraries/tools/kotlin-gradle-plugin-api/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinCompilation.kt +++ b/libraries/tools/kotlin-gradle-plugin-api/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinCompilation.kt @@ -75,6 +75,8 @@ interface KotlinCompilation : Named, HasAttributes, override val relatedConfigurationNames: List get() = super.relatedConfigurationNames + compileDependencyConfigurationName + + val moduleName: String } interface KotlinCompilationToRunnableFiles : KotlinCompilation { diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/GradleKotlinCompilerRunner.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/GradleKotlinCompilerRunner.kt index c907f25c7be..f320529b864 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/GradleKotlinCompilerRunner.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/compilerRunner/GradleKotlinCompilerRunner.kt @@ -37,6 +37,7 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType import org.jetbrains.kotlin.gradle.tasks.KotlinCompileTaskData import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinWithJavaTarget +import org.jetbrains.kotlin.gradle.plugin.mpp.ownModuleName import org.jetbrains.kotlin.gradle.utils.archivePathCompatible import org.jetbrains.kotlin.gradle.utils.newTmpFile import org.jetbrains.kotlin.gradle.utils.relativeToRoot @@ -192,7 +193,7 @@ internal open class GradleCompilerRunner(protected val task: Task) { val target = taskData.compilation.target val module = IncrementalModuleEntry( project.path, - compilation.moduleName, + compilation.ownModuleName, project.buildDir, taskData.buildHistoryFile ) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/internal/KotlinCompilationsModuleGroups.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/internal/KotlinCompilationsModuleGroups.kt new file mode 100644 index 00000000000..4d835b568fd --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/internal/KotlinCompilationsModuleGroups.kt @@ -0,0 +1,71 @@ +/* + * Copyright 2010-2019 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.gradle.plugin.mpp.internal + +import org.gradle.api.Project +import org.gradle.api.plugins.ExtraPropertiesExtension +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation + +/** This is a disjoint-set union-like approach to having a module name that is equal across associated compilations, as the compiler + * now requires that to properly compile internal calls to friend classes. Associating a compilation with another one leads to their + * disjoint sets being united, so their module names become equal, taken from the new leader compilation. + * + * TODO: once the compiler is able to correctly generate calls to internals in other modules, remove this logic. + */ +internal class KotlinCompilationsModuleGroups { + private val moduleLeaderCompilationMap: MutableMap, KotlinCompilation<*>> = + mutableMapOf() + + fun getModuleLeader(compilation: KotlinCompilation<*>): KotlinCompilation<*> { + if (compilation !in moduleLeaderCompilationMap) { + moduleLeaderCompilationMap[compilation] = compilation + } + val leader = moduleLeaderCompilationMap.getValue(compilation) + return when { + leader == compilation -> leader + else -> getModuleLeader(leader).also { moduleLeaderCompilationMap[compilation] = it } + } + } + + fun unionModules(compilationA: KotlinCompilation<*>, compilationB: KotlinCompilation<*>) { + val aLeader = getModuleLeader(compilationA) + val bLeader = getModuleLeader(compilationB) + + if (aLeader == bLeader) + return + + listOf(aLeader, bLeader).run { + /** heuristically choose the new leader: choose `main` when possible, don't choose `*test*` when there's an alternative, + * if that didn't work, choose the first name lexicographically */ + val newLeader = singleOrNull { it.name == KotlinCompilation.MAIN_COMPILATION_NAME } + ?: singleOrNull { it.name.contains("main", true) } + ?: singleOrNull { !it.name.contains("test", true) } + ?: minBy { it.name }!! + + forEach { moduleLeaderCompilationMap[it] = newLeader } + } + } + + companion object { + private const val EXT_NAME = "kotlin.compilations.moduleGroups" + + fun getModuleLeaderCompilation(compilation: KotlinCompilation<*>): KotlinCompilation<*> = + getInstance(compilation.target.project).getModuleLeader(compilation) + + fun unionModules(compilationA: KotlinCompilation<*>, compilationB: KotlinCompilation<*>) { + getInstance(compilationA.target.project).unionModules(compilationA, compilationB) + } + + private fun getInstance(project: Project): KotlinCompilationsModuleGroups { + val ext = project.extensions.getByType(ExtraPropertiesExtension::class.java) + if (!ext.has(EXT_NAME)) { + ext.set(EXT_NAME, KotlinCompilationsModuleGroups()) + } + @Suppress("UNCHECKED_CAST") + return ext.get(EXT_NAME) as KotlinCompilationsModuleGroups + } + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinCompilations.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinCompilations.kt index 9a2817c8133..0975d9331a9 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinCompilations.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/kotlinCompilations.kt @@ -19,6 +19,7 @@ import org.gradle.api.tasks.TaskState import org.gradle.util.ConfigureUtil import org.jetbrains.kotlin.gradle.dsl.* import org.jetbrains.kotlin.gradle.plugin.* +import org.jetbrains.kotlin.gradle.plugin.mpp.internal.KotlinCompilationsModuleGroups import org.jetbrains.kotlin.gradle.plugin.sources.defaultSourceSetLanguageSettingsChecker import org.jetbrains.kotlin.gradle.plugin.sources.getSourceSetHierarchy import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile @@ -191,14 +192,8 @@ abstract class AbstractKotlinCompilation( override fun toString(): String = "compilation '$compilationName' ($target)" - internal val moduleName: String - get() { - val project = target.project - val baseName = project.convention.findPlugin(BasePluginConvention::class.java)?.archivesBaseName - ?: project.name - val suffix = if (compilationName == KotlinCompilation.MAIN_COMPILATION_NAME) "" else "_$compilationName" - return filterModuleName("$baseName$suffix") - } + override val moduleName: String + get() = KotlinCompilationsModuleGroups.getModuleLeaderCompilation(this).takeIf { it != this }?.ownModuleName ?: ownModuleName override fun associateWith(other: KotlinCompilation<*>) { require(other.target == target) { "Only associations between compilations of a single target are supported" } @@ -207,6 +202,7 @@ abstract class AbstractKotlinCompilation( _associateWith += other addAssociateCompilationDependencies(other) + KotlinCompilationsModuleGroups.unionModules(this, other) } protected open fun addAssociateCompilationDependencies(other: KotlinCompilation<*>) { @@ -233,6 +229,15 @@ abstract class AbstractKotlinCompilation( get() = Collections.unmodifiableSet(_associateWith) } + internal val KotlinCompilation<*>.ownModuleName: String + get() { + val project = target.project + val baseName = project.convention.findPlugin(BasePluginConvention::class.java)?.archivesBaseName + ?: project.name + val suffix = if (compilationName == KotlinCompilation.MAIN_COMPILATION_NAME) "" else "_$compilationName" + return filterModuleName("$baseName$suffix") + } + internal val KotlinCompilation<*>.associateWithTransitiveClosure: Iterable> get() = mutableSetOf>().apply { fun visit(other: KotlinCompilation<*>) { diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/KotlinCompileCommon.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/KotlinCompileCommon.kt index cfa11eab239..cace59718bc 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/KotlinCompileCommon.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/KotlinCompileCommon.kt @@ -50,7 +50,7 @@ open class KotlinCompileCommon : AbstractKotlinCompile(), Kotl args.apply { fillDefaultValues() } super.setupCompilerArgs(args, defaultsOnly = defaultsOnly, ignoreClasspathResolutionErrors = ignoreClasspathResolutionErrors) - args.moduleName = friendTask?.moduleName ?: moduleName + args.moduleName = taskData.compilation.moduleName logger.kotlinDebug { "args.moduleName = ${args.moduleName}" } args.friendPaths = friendPaths.value @@ -540,7 +542,7 @@ open class Kotlin2JsCompile : AbstractKotlinCompile(), Ko get() = kotlinOptionsImpl private val defaultOutputFile: File - get() = File(destinationDir, "$moduleName.js") + get() = File(destinationDir, "${taskData.compilation.ownModuleName}.js") @Suppress("unused") @get:OutputFile diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/internal/KotlinCompilationsModuleGroupsTest.kt b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/internal/KotlinCompilationsModuleGroupsTest.kt new file mode 100644 index 00000000000..69b8bdaa016 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/internal/KotlinCompilationsModuleGroupsTest.kt @@ -0,0 +1,86 @@ +/* + * Copyright 2010-2019 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.gradle.plugin.mpp.internal + +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation +import org.jetbrains.kotlin.gradle.plugin.sources.MockKotlinCompilation +import org.jetbrains.kotlin.gradle.plugin.sources.MockKotlinSourceSet +import org.junit.Before +import org.junit.Test +import kotlin.test.assertEquals + +class KotlinCompilationsModuleGroupsTest { + private lateinit var instance: KotlinCompilationsModuleGroups + + @Before + fun before() { + instance = KotlinCompilationsModuleGroups() + } + + private fun compilation(name: String) = MockKotlinCompilation(name, MockKotlinSourceSet(name)) + + @Test + fun testIdentityAsModuleLeaderForNewCompilation() { + val a = compilation("a") + assertEquals(a, instance.getModuleLeader(a)) + } + + private fun assertLeader(leader: KotlinCompilation<*>, vararg ofCompilations: KotlinCompilation<*>) { + val leadersForModules = ofCompilations.map { instance.getModuleLeader(it) } + assertEquals(leadersForModules.map { leader }, leadersForModules) + } + + @Test + fun testModulesMerged() { + val (a, b, c, d) = listOf("a", "b", "c", "d").map(this::compilation) + + // Union all but one: + instance.unionModules(b, c) + instance.unionModules(a, b) + assertLeader(a, a, b, c) + assertLeader(d, d) + } + + @Test + fun testMainPreferredAsLeader() { + val (aMain, aTest) = listOf("aMain", "aTest").map(this::compilation) + val (zMain, yTest) = listOf("zMain", "yTest").map(this::compilation) + val (xMain, xBenchmark, xTest) = listOf("xMain", "xBenchmark", "xTest").map(this::compilation) + + instance.unionModules(aMain, aTest) + assertLeader(aMain, aMain, aTest) + + instance.unionModules(yTest, zMain) + assertLeader(zMain, zMain, yTest) + + instance.unionModules(xBenchmark, xTest) + instance.unionModules(xBenchmark, xMain) + assertLeader(xMain, xMain, xBenchmark, xTest) + } + + @Test + fun testNonTestPreferredAsLeader() { + val (aBenchmark, aPerformanceTest, aTest) = listOf("aBenchmark", "aPerformanceTest", "aTest").map(this::compilation) + instance.unionModules(aPerformanceTest, aTest) + instance.unionModules(aTest, aBenchmark) + assertLeader(aBenchmark, aBenchmark, aPerformanceTest, aTest) + } + + @Test + fun unionModuleWithSelf() { + val compilation = compilation("compilation") + instance.unionModules(compilation, compilation) + assertLeader(compilation, compilation) + } + + @Test + fun testUnionModulesTwice() { + val (a, b) = listOf("a", "b").map(this::compilation) + instance.unionModules(a, b) + instance.unionModules(a, b) + assertLeader(a, a, b) + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/SourceSetVisibilityInferenceTest.kt b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/SourceSetVisibilityInferenceTest.kt index afd28475149..35d167e5fd4 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/SourceSetVisibilityInferenceTest.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/SourceSetVisibilityInferenceTest.kt @@ -316,6 +316,7 @@ class MockKotlinCompilation( override fun attributes(configure: AttributeContainer.() -> Unit) = throw UnsupportedOperationException() override fun attributes(configure: Closure<*>) = throw UnsupportedOperationException() override val compileAllTaskName: String get() = throw UnsupportedOperationException() + override val moduleName: String get() = throw UnsupportedOperationException() //endregion override fun toString(): String = "compilation '${name}'"