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 {
|
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>
|
||||||
}
|
}
|
||||||
+1
-1
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+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.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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+8
-3
@@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+14
-13
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+56
-2
@@ -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)
|
||||||
|
|||||||
+1
-1
@@ -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))
|
||||||
|
|||||||
Reference in New Issue
Block a user