From c4d9c4cee424b0b22cca788d5b5e6ee665ffe21c Mon Sep 17 00:00:00 2001 From: Sergey Igushkin Date: Fri, 23 Aug 2019 16:29:29 +0300 Subject: [PATCH] Group associated compilations into modules with a single module name As associated compilations will see internals of each other, they now need to share the module name for the compiler to be able to correctly generate calls to the internals. Move `moduleName` to `KotlinCompilation`. Group associate compilations into modules in a DSU-like way, use a single module name for all of the compilations grouped into a module. --- .../kotlin/gradle/plugin/KotlinCompilation.kt | 2 + .../GradleKotlinCompilerRunner.kt | 3 +- .../KotlinCompilationsModuleGroups.kt | 71 +++++++++++++++ .../gradle/plugin/mpp/kotlinCompilations.kt | 21 +++-- .../gradle/tasks/KotlinCompileCommon.kt | 2 +- .../jetbrains/kotlin/gradle/tasks/Tasks.kt | 6 +- .../KotlinCompilationsModuleGroupsTest.kt | 86 +++++++++++++++++++ .../SourceSetVisibilityInferenceTest.kt | 1 + 8 files changed, 180 insertions(+), 12 deletions(-) create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/internal/KotlinCompilationsModuleGroups.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/test/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/internal/KotlinCompilationsModuleGroupsTest.kt 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}'"