From 3ff61f59a7e22bb1e03e2040766ac16d9ff4e88a Mon Sep 17 00:00:00 2001 From: Sergey Igushkin Date: Wed, 1 Aug 2018 22:20:00 +0300 Subject: [PATCH] Implement dependsOn relation for Kotlin source sets * Setup configurations' extendsFrom and inherit sources of source sets. This make it unnecessary to restore transitive dependencies of a source set at its use sites, since their sources and dependencies are already included. * Use dependsOn during default configuration instead of compilation.source() --- .../gradle/plugin/source/KotlinSourceSet.kt | 7 ++- .../kotlin/gradle/plugin/KotlinPlugin.kt | 2 +- .../gradle/plugin/KotlinTargetConfigurator.kt | 4 +- .../plugin/mpp/KotlinMultiplatformPlugin.kt | 11 +++- .../gradle/plugin/mpp/kotlinCompilations.kt | 27 ++++----- .../plugin/sources/DefaultKotlinSourceSet.kt | 58 ++++++++++++++++++- .../gradle/utils/gradleConfigurationUtils.kt | 2 +- 7 files changed, 86 insertions(+), 25 deletions(-) diff --git a/libraries/tools/kotlin-gradle-plugin-api/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/source/KotlinSourceSet.kt b/libraries/tools/kotlin-gradle-plugin-api/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/source/KotlinSourceSet.kt index 4c9701c260e..c496581913d 100644 --- a/libraries/tools/kotlin-gradle-plugin-api/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/source/KotlinSourceSet.kt +++ b/libraries/tools/kotlin-gradle-plugin-api/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/source/KotlinSourceSet.kt @@ -12,6 +12,7 @@ import org.jetbrains.kotlin.gradle.plugin.HasKotlinDependencies interface KotlinSourceSet : Named, HasKotlinDependencies { val kotlin: SourceDirectorySet + val resources: SourceDirectorySet fun kotlin(configureClosure: Closure): SourceDirectorySet @@ -19,8 +20,8 @@ interface KotlinSourceSet : Named, HasKotlinDependencies { const val COMMON_MAIN_SOURCE_SET_NAME = "commonMain" const val COMMON_TEST_SOURCE_SET_NAME = "commonTest" } -} -interface KotlinSourceSetWithResources : KotlinSourceSet { - val resources: SourceDirectorySet + fun dependsOn(other: KotlinSourceSet) + + val dependsOn: Set } \ No newline at end of file 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 7cdf52ea11d..a36ef3bee22 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 @@ -417,7 +417,7 @@ internal abstract class AbstractKotlinPlugin( kotlinCompilation.javaSourceSet = javaSourceSets.maybeCreate(sourceSetName) // Another Kotlin source set following the other convention, named according to the compilation, not the Java source set: - val kotlinSourceSet = project.kotlinExtension.sourceSets.maybeCreate(kotlinCompilation.fullName) + val kotlinSourceSet = project.kotlinExtension.sourceSets.maybeCreate(kotlinCompilation.defaultSourceSetName) kotlinCompilation.source(kotlinSourceSet) } } 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 edc8a7accb8..6393d16d5fe 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 @@ -29,7 +29,7 @@ import org.gradle.language.jvm.tasks.ProcessResources import org.jetbrains.kotlin.gradle.dsl.kotlinExtension import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinOnlyTarget import org.jetbrains.kotlin.gradle.plugin.mpp.disambiguateName -import org.jetbrains.kotlin.gradle.plugin.mpp.fullName +import org.jetbrains.kotlin.gradle.plugin.mpp.defaultSourceSetName import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile import org.jetbrains.kotlin.gradle.utils.isGradleVersionAtLeast import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName @@ -75,7 +75,7 @@ open class KotlinTargetConfigurator( target.compilations.all { compilation -> defineConfigurationsForCompilation(compilation, target, project.configurations) - project.kotlinExtension.sourceSets.maybeCreate(compilation.fullName).also { sourceSet -> + project.kotlinExtension.sourceSets.maybeCreate(compilation.defaultSourceSetName).also { sourceSet -> compilation.source(sourceSet) // also adds dependencies, requires the configurations for target and source set to exist at this point } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt index 6503aff880f..52fcc950180 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt @@ -107,9 +107,14 @@ internal class KotlinMultiplatformPlugin( val production = sourceSets.create(KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME) val test = sourceSets.create(KotlinSourceSet.COMMON_TEST_SOURCE_SET_NAME) - targets.all { - it.compilations.findByName(KotlinCompilation.MAIN_COMPILATION_NAME)?.source(production) - it.compilations.findByName(KotlinCompilation.TEST_COMPILATION_NAME)?.source(test) + targets.all { target -> + target.compilations.findByName(KotlinCompilation.MAIN_COMPILATION_NAME)?.let { mainCompilation -> + sourceSets.maybeCreate(mainCompilation.defaultSourceSetName).dependsOn(production) + } + + target.compilations.findByName(KotlinCompilation.TEST_COMPILATION_NAME)?.let { testCompilation -> + sourceSets.maybeCreate(testCompilation.defaultSourceSetName).dependsOn(test) + } } } 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 e97ee9e06dc..27ab6c66042 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 @@ -24,6 +24,7 @@ import org.gradle.api.tasks.SourceSetOutput import org.gradle.util.ConfigureUtil import org.jetbrains.kotlin.gradle.plugin.* import org.jetbrains.kotlin.gradle.plugin.source.KotlinSourceSet +import org.jetbrains.kotlin.gradle.plugin.sources.getSourceSetHierarchy import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile import org.jetbrains.kotlin.gradle.utils.addExtendsFromRelation import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName @@ -35,10 +36,8 @@ internal fun KotlinCompilation.composeName(prefix: String? = null, suffix: Strin return lowerCamelCaseName(prefix, targetNamePart, compilationNamePart, suffix) } -internal val KotlinCompilation.fullName: String - get() = fullCompilationName(target, compilationName) - -internal fun fullCompilationName(target: KotlinTarget, simpleName: String) = lowerCamelCaseName(target.disambiguationClassifier, simpleName) +internal val KotlinCompilation.defaultSourceSetName: String + get() = lowerCamelCaseName(target.disambiguationClassifier, compilationName) internal class DefaultKotlinDependencyHandler( val parent: HasKotlinDependencies, @@ -71,17 +70,19 @@ abstract class AbstractKotlinCompilation( if (kotlinSourceSets.add(sourceSet)) { with(target.project) { whenEvaluated { - (target.project.tasks.getByName(compileKotlinTaskName) as AbstractKotlinCompile<*>).source(sourceSet.kotlin) - } + sourceSet.getSourceSetHierarchy().forEach { sourceSet -> + (target.project.tasks.getByName(compileKotlinTaskName) as AbstractKotlinCompile<*>).source(sourceSet.kotlin) - // Use `forced = false` since `api`, `implementation`, and `compileOnly` may be missing in some cases like - // old Java & Android projects: - addExtendsFromRelation(apiConfigurationName, sourceSet.apiConfigurationName, forced = false) - addExtendsFromRelation(implementationConfigurationName, sourceSet.implementationConfigurationName, forced = false) - addExtendsFromRelation(compileOnlyConfigurationName, sourceSet.compileOnlyConfigurationName, forced = false) + // Use `forced = false` since `api`, `implementation`, and `compileOnly` may be missing in some cases like + // old Java & Android projects: + addExtendsFromRelation(apiConfigurationName, sourceSet.apiConfigurationName, forced = false) + addExtendsFromRelation(implementationConfigurationName, sourceSet.implementationConfigurationName, forced = false) + addExtendsFromRelation(compileOnlyConfigurationName, sourceSet.compileOnlyConfigurationName, forced = false) - if (this is KotlinCompilationToRunnableFiles) { - addExtendsFromRelation(runtimeOnlyConfigurationName, sourceSet.runtimeOnlyConfigurationName) + if (this is KotlinCompilationToRunnableFiles) { + addExtendsFromRelation(runtimeOnlyConfigurationName, sourceSet.runtimeOnlyConfigurationName, forced = false) + } + } } } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/DefaultKotlinSourceSet.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/DefaultKotlinSourceSet.kt index c1ee934083f..107c3957dac 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/DefaultKotlinSourceSet.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/DefaultKotlinSourceSet.kt @@ -6,6 +6,7 @@ package org.jetbrains.kotlin.gradle.plugin.sources import groovy.lang.Closure +import org.gradle.api.InvalidUserCodeException import org.gradle.api.Project import org.gradle.api.file.SourceDirectorySet import org.gradle.api.internal.file.DefaultSourceDirectorySet @@ -14,15 +15,15 @@ import org.gradle.util.ConfigureUtil import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler import org.jetbrains.kotlin.gradle.plugin.mpp.DefaultKotlinDependencyHandler import org.jetbrains.kotlin.gradle.plugin.source.KotlinSourceSet -import org.jetbrains.kotlin.gradle.plugin.source.KotlinSourceSetWithResources import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName import java.lang.reflect.Constructor +import java.util.* class DefaultKotlinSourceSet( private val project: Project, val displayName: String, fileResolver: FileResolver -) : KotlinSourceSetWithResources { +) : KotlinSourceSet { override val apiConfigurationName: String get() = disambiguateName("api") @@ -52,9 +53,48 @@ class DefaultKotlinSourceSet( override fun dependencies(configureClosure: Closure) = dependencies f@{ ConfigureUtil.configure(configureClosure, this@f) } + override fun dependsOn(other: KotlinSourceSet) { + dependsOnSourceSetsImpl.add(other) + + // Fail-fast approach: check on each new added edge and report a circular dependency at once when the edge is added. + checkForCircularDependencies() + } + + private val dependsOnSourceSetsImpl = mutableSetOf() + + override val dependsOn: Set + get() = dependsOnSourceSetsImpl + override fun toString(): String = "source set $name" } +private fun KotlinSourceSet.checkForCircularDependencies(): Unit { + // If adding an edge creates a cycle, than the source node of the edge belongs to the cycle, so run DFS from that node + // to check whether it became reachable from itself + val visited = hashSetOf() + val stack = LinkedHashSet() // Store the stack explicitly to pretty-print the cycle + + fun checkReachableRecursively(from: KotlinSourceSet) { + stack += from + visited += from + + for (to in from.dependsOn) { + if (to == this@checkForCircularDependencies) + throw InvalidUserCodeException( + "Circular dependsOn hierarchy found in the Kotlin source sets: " + + (stack.toList() + to).joinToString(" -> ") { it.name } + ) + + if (to !in visited) { + checkReachableRecursively(to) + } + } + stack -= from + } + + checkReachableRecursively(this@checkForCircularDependencies) +} + internal fun KotlinSourceSet.disambiguateName(simpleName: String): String { val nameParts = listOfNotNull(this.name.takeIf { it != "main" }, simpleName) return lowerCamelCaseName(*nameParts.toTypedArray()) @@ -77,6 +117,20 @@ private val createDefaultSourceDirectorySet: (name: String?, resolver: FileResol } } +internal fun KotlinSourceSet.getSourceSetHierarchy(): Set { + val result = mutableSetOf() + + fun processSourceSet(sourceSet: KotlinSourceSet) { + if (result.add(sourceSet)) { + sourceSet.dependsOn.forEach { processSourceSet(it) } + } + } + + processSourceSet(this) + return result +} + + private fun Class.constructorOrNull(vararg parameterTypes: Class<*>): Constructor? = try { getConstructor(*parameterTypes) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/utils/gradleConfigurationUtils.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/utils/gradleConfigurationUtils.kt index 9b8c1c11702..8c852392ab0 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/utils/gradleConfigurationUtils.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/utils/gradleConfigurationUtils.kt @@ -7,7 +7,7 @@ package org.jetbrains.kotlin.gradle.utils import org.gradle.api.Project -fun Project.addExtendsFromRelation(extendingConfigurationName: String, extendsFromConfigurationName: String, forced: Boolean = false) { +fun Project.addExtendsFromRelation(extendingConfigurationName: String, extendsFromConfigurationName: String, forced: Boolean = true) { if (extendingConfigurationName != extendsFromConfigurationName) { if (forced || configurations.findByName(extendingConfigurationName) != null) { project.dependencies.add(extendingConfigurationName, project.configurations.getByName(extendsFromConfigurationName))