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.
This commit is contained in:
Sergey Igushkin
2019-08-23 16:29:29 +03:00
parent 94fd5c2884
commit c4d9c4cee4
8 changed files with 180 additions and 12 deletions
@@ -75,6 +75,8 @@ interface KotlinCompilation<out T : KotlinCommonOptions> : Named, HasAttributes,
override val relatedConfigurationNames: List<String>
get() = super.relatedConfigurationNames + compileDependencyConfigurationName
val moduleName: String
}
interface KotlinCompilationToRunnableFiles<T : KotlinCommonOptions> : KotlinCompilation<T> {
@@ -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
)
@@ -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<*>, 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
}
}
}
@@ -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<T : KotlinCommonOptions>(
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<T : KotlinCommonOptions>(
_associateWith += other
addAssociateCompilationDependencies(other)
KotlinCompilationsModuleGroups.unionModules(this, other)
}
protected open fun addAssociateCompilationDependencies(other: KotlinCompilation<*>) {
@@ -233,6 +229,15 @@ abstract class AbstractKotlinCompilation<T : KotlinCommonOptions>(
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<KotlinCompilation<*>>
get() = mutableSetOf<KotlinCompilation<*>>().apply {
fun visit(other: KotlinCompilation<*>) {
@@ -50,7 +50,7 @@ open class KotlinCompileCommon : AbstractKotlinCompile<K2MetadataCompilerArgumen
args.apply { fillDefaultValues() }
super.setupCompilerArgs(args, defaultsOnly = defaultsOnly, ignoreClasspathResolutionErrors = ignoreClasspathResolutionErrors)
args.moduleName = friendTask?.moduleName ?: this@KotlinCompileCommon.moduleName
args.moduleName = this@KotlinCompileCommon.moduleName
if (defaultsOnly) return
@@ -31,6 +31,8 @@ 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.mpp.associateWithTransitiveClosure
import org.jetbrains.kotlin.gradle.plugin.mpp.ownModuleName
import org.jetbrains.kotlin.gradle.report.BuildReportMode
import org.jetbrains.kotlin.gradle.utils.isGradleVersionAtLeast
import org.jetbrains.kotlin.gradle.utils.isParentOf
@@ -399,7 +401,7 @@ open class KotlinCompile : AbstractKotlinCompile<K2JVMCompilerArguments>(), 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<K2JSCompilerArguments>(), Ko
get() = kotlinOptionsImpl
private val defaultOutputFile: File
get() = File(destinationDir, "$moduleName.js")
get() = File(destinationDir, "${taskData.compilation.ownModuleName}.js")
@Suppress("unused")
@get:OutputFile
@@ -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)
}
}
@@ -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}'"