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:
+4
-3
@@ -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<Any?>): 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<KotlinSourceSet>
|
||||
}
|
||||
+1
-1
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -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
|
||||
}
|
||||
|
||||
|
||||
+8
-3
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+14
-13
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+56
-2
@@ -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<Any?>) =
|
||||
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"
|
||||
}
|
||||
|
||||
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 {
|
||||
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<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>? =
|
||||
try {
|
||||
getConstructor(*parameterTypes)
|
||||
|
||||
+1
-1
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user