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()
This commit is contained in:
Sergey Igushkin
2018-08-01 22:20:00 +03:00
parent 93943a565c
commit 3ff61f59a7
7 changed files with 86 additions and 25 deletions
@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.gradle.plugin.HasKotlinDependencies
interface KotlinSourceSet : Named, HasKotlinDependencies { interface KotlinSourceSet : Named, HasKotlinDependencies {
val kotlin: SourceDirectorySet val kotlin: SourceDirectorySet
val resources: SourceDirectorySet
fun kotlin(configureClosure: Closure<Any?>): SourceDirectorySet fun kotlin(configureClosure: Closure<Any?>): SourceDirectorySet
@@ -19,8 +20,8 @@ interface KotlinSourceSet : Named, HasKotlinDependencies {
const val COMMON_MAIN_SOURCE_SET_NAME = "commonMain" const val COMMON_MAIN_SOURCE_SET_NAME = "commonMain"
const val COMMON_TEST_SOURCE_SET_NAME = "commonTest" const val COMMON_TEST_SOURCE_SET_NAME = "commonTest"
} }
}
interface KotlinSourceSetWithResources : KotlinSourceSet { fun dependsOn(other: KotlinSourceSet)
val resources: SourceDirectorySet
val dependsOn: Set<KotlinSourceSet>
} }
@@ -417,7 +417,7 @@ internal abstract class AbstractKotlinPlugin(
kotlinCompilation.javaSourceSet = javaSourceSets.maybeCreate(sourceSetName) kotlinCompilation.javaSourceSet = javaSourceSets.maybeCreate(sourceSetName)
// Another Kotlin source set following the other convention, named according to the compilation, not the Java source set: // 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) kotlinCompilation.source(kotlinSourceSet)
} }
} }
@@ -29,7 +29,7 @@ import org.gradle.language.jvm.tasks.ProcessResources
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinOnlyTarget import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinOnlyTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.disambiguateName 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.tasks.AbstractKotlinCompile
import org.jetbrains.kotlin.gradle.utils.isGradleVersionAtLeast import org.jetbrains.kotlin.gradle.utils.isGradleVersionAtLeast
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
@@ -75,7 +75,7 @@ open class KotlinTargetConfigurator(
target.compilations.all { compilation -> target.compilations.all { compilation ->
defineConfigurationsForCompilation(compilation, target, project.configurations) 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 compilation.source(sourceSet) // also adds dependencies, requires the configurations for target and source set to exist at this point
} }
@@ -107,9 +107,14 @@ internal class KotlinMultiplatformPlugin(
val production = sourceSets.create(KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME) val production = sourceSets.create(KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME)
val test = sourceSets.create(KotlinSourceSet.COMMON_TEST_SOURCE_SET_NAME) val test = sourceSets.create(KotlinSourceSet.COMMON_TEST_SOURCE_SET_NAME)
targets.all { targets.all { target ->
it.compilations.findByName(KotlinCompilation.MAIN_COMPILATION_NAME)?.source(production) target.compilations.findByName(KotlinCompilation.MAIN_COMPILATION_NAME)?.let { mainCompilation ->
it.compilations.findByName(KotlinCompilation.TEST_COMPILATION_NAME)?.source(test) sourceSets.maybeCreate(mainCompilation.defaultSourceSetName).dependsOn(production)
}
target.compilations.findByName(KotlinCompilation.TEST_COMPILATION_NAME)?.let { testCompilation ->
sourceSets.maybeCreate(testCompilation.defaultSourceSetName).dependsOn(test)
}
} }
} }
@@ -24,6 +24,7 @@ import org.gradle.api.tasks.SourceSetOutput
import org.gradle.util.ConfigureUtil import org.gradle.util.ConfigureUtil
import org.jetbrains.kotlin.gradle.plugin.* import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.source.KotlinSourceSet 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.tasks.AbstractKotlinCompile
import org.jetbrains.kotlin.gradle.utils.addExtendsFromRelation import org.jetbrains.kotlin.gradle.utils.addExtendsFromRelation
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName 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) return lowerCamelCaseName(prefix, targetNamePart, compilationNamePart, suffix)
} }
internal val KotlinCompilation.fullName: String internal val KotlinCompilation.defaultSourceSetName: String
get() = fullCompilationName(target, compilationName) get() = lowerCamelCaseName(target.disambiguationClassifier, compilationName)
internal fun fullCompilationName(target: KotlinTarget, simpleName: String) = lowerCamelCaseName(target.disambiguationClassifier, simpleName)
internal class DefaultKotlinDependencyHandler( internal class DefaultKotlinDependencyHandler(
val parent: HasKotlinDependencies, val parent: HasKotlinDependencies,
@@ -71,17 +70,19 @@ abstract class AbstractKotlinCompilation(
if (kotlinSourceSets.add(sourceSet)) { if (kotlinSourceSets.add(sourceSet)) {
with(target.project) { with(target.project) {
whenEvaluated { 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 // Use `forced = false` since `api`, `implementation`, and `compileOnly` may be missing in some cases like
// old Java & Android projects: // old Java & Android projects:
addExtendsFromRelation(apiConfigurationName, sourceSet.apiConfigurationName, forced = false) addExtendsFromRelation(apiConfigurationName, sourceSet.apiConfigurationName, forced = false)
addExtendsFromRelation(implementationConfigurationName, sourceSet.implementationConfigurationName, forced = false) addExtendsFromRelation(implementationConfigurationName, sourceSet.implementationConfigurationName, forced = false)
addExtendsFromRelation(compileOnlyConfigurationName, sourceSet.compileOnlyConfigurationName, forced = false) addExtendsFromRelation(compileOnlyConfigurationName, sourceSet.compileOnlyConfigurationName, forced = false)
if (this is KotlinCompilationToRunnableFiles) { if (this is KotlinCompilationToRunnableFiles) {
addExtendsFromRelation(runtimeOnlyConfigurationName, sourceSet.runtimeOnlyConfigurationName) addExtendsFromRelation(runtimeOnlyConfigurationName, sourceSet.runtimeOnlyConfigurationName, forced = false)
}
}
} }
} }
} }
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.gradle.plugin.sources package org.jetbrains.kotlin.gradle.plugin.sources
import groovy.lang.Closure import groovy.lang.Closure
import org.gradle.api.InvalidUserCodeException
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.file.SourceDirectorySet import org.gradle.api.file.SourceDirectorySet
import org.gradle.api.internal.file.DefaultSourceDirectorySet 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.KotlinDependencyHandler
import org.jetbrains.kotlin.gradle.plugin.mpp.DefaultKotlinDependencyHandler import org.jetbrains.kotlin.gradle.plugin.mpp.DefaultKotlinDependencyHandler
import org.jetbrains.kotlin.gradle.plugin.source.KotlinSourceSet import org.jetbrains.kotlin.gradle.plugin.source.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.source.KotlinSourceSetWithResources
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import java.lang.reflect.Constructor import java.lang.reflect.Constructor
import java.util.*
class DefaultKotlinSourceSet( class DefaultKotlinSourceSet(
private val project: Project, private val project: Project,
val displayName: String, val displayName: String,
fileResolver: FileResolver fileResolver: FileResolver
) : KotlinSourceSetWithResources { ) : KotlinSourceSet {
override val apiConfigurationName: String override val apiConfigurationName: String
get() = disambiguateName("api") get() = disambiguateName("api")
@@ -52,9 +53,48 @@ class DefaultKotlinSourceSet(
override fun dependencies(configureClosure: Closure<Any?>) = override fun dependencies(configureClosure: Closure<Any?>) =
dependencies f@{ ConfigureUtil.configure(configureClosure, this@f) } 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<KotlinSourceSet>()
override val dependsOn: Set<KotlinSourceSet>
get() = dependsOnSourceSetsImpl
override fun toString(): String = "source set $name" 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<KotlinSourceSet>()
val stack = LinkedHashSet<KotlinSourceSet>() // 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 { internal fun KotlinSourceSet.disambiguateName(simpleName: String): String {
val nameParts = listOfNotNull(this.name.takeIf { it != "main" }, simpleName) val nameParts = listOfNotNull(this.name.takeIf { it != "main" }, simpleName)
return lowerCamelCaseName(*nameParts.toTypedArray()) return lowerCamelCaseName(*nameParts.toTypedArray())
@@ -77,6 +117,20 @@ private val createDefaultSourceDirectorySet: (name: String?, resolver: FileResol
} }
} }
internal fun KotlinSourceSet.getSourceSetHierarchy(): Set<KotlinSourceSet> {
val result = mutableSetOf<KotlinSourceSet>()
fun processSourceSet(sourceSet: KotlinSourceSet) {
if (result.add(sourceSet)) {
sourceSet.dependsOn.forEach { processSourceSet(it) }
}
}
processSourceSet(this)
return result
}
private fun <T> Class<T>.constructorOrNull(vararg parameterTypes: Class<*>): Constructor<T>? = private fun <T> Class<T>.constructorOrNull(vararg parameterTypes: Class<*>): Constructor<T>? =
try { try {
getConstructor(*parameterTypes) getConstructor(*parameterTypes)
@@ -7,7 +7,7 @@ package org.jetbrains.kotlin.gradle.utils
import org.gradle.api.Project 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 (extendingConfigurationName != extendsFromConfigurationName) {
if (forced || configurations.findByName(extendingConfigurationName) != null) { if (forced || configurations.findByName(extendingConfigurationName) != null) {
project.dependencies.add(extendingConfigurationName, project.configurations.getByName(extendsFromConfigurationName)) project.dependencies.add(extendingConfigurationName, project.configurations.getByName(extendsFromConfigurationName))