[Gradle] Ide Dependency Resolution: Filter 'loop' or 'self' dependencies

Those are dependencies that point back to the original project.
This can happen with two projects like

```
// project a
kotlin {
    commonMain.get().dependencies {
        api(project(":b"))
    }
}

// project b
kotlin {
    commonTest.get().dependencies {
        implementation(project(":a"))
    }
}
```

Where b(test) -> a -> b

^KT-59020 Verification Pending
This commit is contained in:
Sebastian Sellmair
2023-06-02 19:27:39 +02:00
committed by Space Team
parent 19073b96a7
commit a80d6a7b74
6 changed files with 83 additions and 33 deletions
@@ -154,7 +154,8 @@ public final class org/jetbrains/kotlin/gradle/plugin/ide/dependencyResolvers/Id
public fun <init> (Lkotlin/jvm/functions/Function1;)V
public fun <init> (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)V
public fun <init> (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;)V
public synthetic fun <init> (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V
public synthetic fun <init> (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
}
public final class org/jetbrains/kotlin/gradle/plugin/ide/dependencyResolvers/IdeBinaryDependencyResolver$ArtifactResolutionStrategy$PlatformLikeSourceSet : org/jetbrains/kotlin/gradle/plugin/ide/dependencyResolvers/IdeBinaryDependencyResolver$ArtifactResolutionStrategy {
@@ -162,14 +163,16 @@ public final class org/jetbrains/kotlin/gradle/plugin/ide/dependencyResolvers/Id
public fun <init> (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)V
public fun <init> (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;)V
public fun <init> (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V
public synthetic fun <init> (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V
public synthetic fun <init> (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
}
public final class org/jetbrains/kotlin/gradle/plugin/ide/dependencyResolvers/IdeBinaryDependencyResolver$ArtifactResolutionStrategy$ResolvableConfiguration : org/jetbrains/kotlin/gradle/plugin/ide/dependencyResolvers/IdeBinaryDependencyResolver$ArtifactResolutionStrategy {
public fun <init> (Lkotlin/jvm/functions/Function1;)V
public fun <init> (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)V
public fun <init> (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;)V
public synthetic fun <init> (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V
public synthetic fun <init> (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
}
public final class org/jetbrains/kotlin/gradle/plugin/mpp/external/CreateExternalKotlinCompilationKt {
@@ -5,9 +5,7 @@
package org.jetbrains.kotlin.gradle.plugin.ide.dependencyResolvers
import org.gradle.api.artifacts.ArtifactView
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.DependencySubstitutions
import org.gradle.api.artifacts.*
import org.gradle.api.artifacts.component.*
import org.gradle.api.attributes.AttributeContainer
import org.gradle.api.logging.Logger
@@ -24,6 +22,7 @@ import org.jetbrains.kotlin.gradle.plugin.ide.IdeDependencyResolver
import org.jetbrains.kotlin.gradle.plugin.ide.IdeDependencyResolver.Companion.gradleArtifact
import org.jetbrains.kotlin.gradle.plugin.ide.IdeaKotlinBinaryCoordinates
import org.jetbrains.kotlin.gradle.plugin.ide.IdeaKotlinProjectCoordinates
import org.jetbrains.kotlin.gradle.plugin.ide.dependencyResolvers.IdeBinaryDependencyResolver.ArtifactResolutionStrategy
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMetadataCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.compilationImpl.KotlinCompilationConfigurationsContainer
import org.jetbrains.kotlin.gradle.plugin.mpp.internal
@@ -34,7 +33,6 @@ import org.jetbrains.kotlin.gradle.plugin.sources.DefaultKotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.sources.InternalKotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.sources.internal
import org.jetbrains.kotlin.gradle.utils.markResolvable
import org.gradle.api.artifacts.ResolutionStrategy
import org.jetbrains.kotlin.tooling.core.mutableExtrasOf
/**
@@ -57,11 +55,14 @@ import org.jetbrains.kotlin.tooling.core.mutableExtrasOf
@ExternalKotlinTargetApi
class IdeBinaryDependencyResolver @JvmOverloads constructor(
private val binaryType: String = IdeaKotlinBinaryDependency.KOTLIN_COMPILE_BINARY_TYPE,
private val artifactResolutionStrategy: ArtifactResolutionStrategy = ArtifactResolutionStrategy.Compilation()
private val artifactResolutionStrategy: ArtifactResolutionStrategy = ArtifactResolutionStrategy.Compilation(),
) : IdeDependencyResolver {
@ExternalKotlinTargetApi
sealed class ArtifactResolutionStrategy {
internal abstract val setupArtifactViewAttributes: AttributeContainer.(sourceSet: KotlinSourceSet) -> Unit
internal abstract val componentFilter: ((ComponentIdentifier) -> Boolean)?
internal abstract val dependencyFilter: ((Dependency) -> Boolean)?
/**
* Resolve the artifacts from a [KotlinSourceSet] using its [KotlinCompilation.compileDependencyConfigurationName],
@@ -74,9 +75,11 @@ class IdeBinaryDependencyResolver @JvmOverloads constructor(
*/
@ExternalKotlinTargetApi
class Compilation @JvmOverloads constructor(
internal val compilationSelector: (KotlinSourceSet) -> KotlinCompilation<*>? = { sourceSet -> sourceSet.internal.compilations.singleOrNull { it.platformType != KotlinPlatformType.common } },
internal val setupArtifactViewAttributes: AttributeContainer.(sourceSet: KotlinSourceSet) -> Unit = {},
internal val componentFilter: ((ComponentIdentifier) -> Boolean)? = null
internal val compilationSelector: (KotlinSourceSet) -> KotlinCompilation<*>? =
{ sourceSet -> sourceSet.internal.compilations.singleOrNull { it.platformType != KotlinPlatformType.common } },
override val setupArtifactViewAttributes: AttributeContainer.(sourceSet: KotlinSourceSet) -> Unit = {},
override val componentFilter: ((ComponentIdentifier) -> Boolean)? = null,
override val dependencyFilter: ((Dependency) -> Boolean)? = null,
) : ArtifactResolutionStrategy()
/**
@@ -84,12 +87,14 @@ class IdeBinaryDependencyResolver @JvmOverloads constructor(
* @param configurationSelector Returns the configuration that shall be resolved for the given [KotlinSourceSet]
* @param setupArtifactViewAttributes: Additional attributes that will be used to create an [ArtifactView] for resolving the dependencies.
* @param componentFilter Filter added to the artifactView: Only components passing the filter will be resolved
* @param dependencyFilter Filter added to the [ResolvableDependencies]: Only dependencies passing the filter will be resolved
*/
@ExternalKotlinTargetApi
class ResolvableConfiguration @JvmOverloads constructor(
internal val configurationSelector: (KotlinSourceSet) -> Configuration?,
internal val setupArtifactViewAttributes: AttributeContainer.(sourceSet: KotlinSourceSet) -> Unit = {},
internal val componentFilter: ((ComponentIdentifier) -> Boolean)? = null
override val setupArtifactViewAttributes: AttributeContainer.(sourceSet: KotlinSourceSet) -> Unit = {},
override val componentFilter: ((ComponentIdentifier) -> Boolean)? = null,
override val dependencyFilter: ((Dependency) -> Boolean)? = null,
) : ArtifactResolutionStrategy()
/**
@@ -101,13 +106,15 @@ class IdeBinaryDependencyResolver @JvmOverloads constructor(
* @param componentFilter Filter added to the artifactView: Only components passing the filter will be resolved
* @param dependencySubstitution Dependency substitution added to the adhoc configuration created for this resolution.
* see [ResolutionStrategy.dependencySubstitution]
* @param dependencyFilter Filter added to the [ResolvableDependencies]: Only dependencies passing the filter will be resolved
*/
@ExternalKotlinTargetApi
class PlatformLikeSourceSet @JvmOverloads constructor(
internal val setupPlatformResolutionAttributes: AttributeContainer.(sourceSet: KotlinSourceSet) -> Unit,
internal val setupArtifactViewAttributes: AttributeContainer.(sourceSet: KotlinSourceSet) -> Unit = {},
internal val componentFilter: ((ComponentIdentifier) -> Boolean)? = null,
override val setupArtifactViewAttributes: AttributeContainer.(sourceSet: KotlinSourceSet) -> Unit = {},
override val componentFilter: ((ComponentIdentifier) -> Boolean)? = null,
internal val dependencySubstitution: ((DependencySubstitutions) -> Unit)? = null,
override val dependencyFilter: ((Dependency) -> Boolean)? = null,
) : ArtifactResolutionStrategy()
}
@@ -194,24 +201,12 @@ class IdeBinaryDependencyResolver @JvmOverloads constructor(
return null
}
return compilation.internal.configurations.compileDependencyConfiguration.incoming.artifactView { view ->
view.isLenient = true
view.attributes.setupArtifactViewAttributes(sourceSet)
if (componentFilter != null) {
view.componentFilter(componentFilter)
}
}
return createArtifactViewFromConfiguration(sourceSet, compilation.internal.configurations.compileDependencyConfiguration)
}
private fun ArtifactResolutionStrategy.ResolvableConfiguration.createArtifactView(sourceSet: InternalKotlinSourceSet): ArtifactView? {
val configuration = configurationSelector(sourceSet) ?: return null
return configuration.incoming.artifactView { view ->
view.isLenient = true
view.attributes.setupArtifactViewAttributes(sourceSet)
if (componentFilter != null) {
view.componentFilter(componentFilter)
}
}
return createArtifactViewFromConfiguration(sourceSet, configuration)
}
private fun ArtifactResolutionStrategy.PlatformLikeSourceSet.createArtifactView(sourceSet: InternalKotlinSourceSet): ArtifactView? {
@@ -227,14 +222,23 @@ class IdeBinaryDependencyResolver @JvmOverloads constructor(
platformLikeCompileDependenciesConfiguration.resolutionStrategy.dependencySubstitution(dependencySubstitution)
}
return platformLikeCompileDependenciesConfiguration.incoming.artifactView { view ->
return createArtifactViewFromConfiguration(sourceSet, platformLikeCompileDependenciesConfiguration)
}
private fun ArtifactResolutionStrategy.createArtifactViewFromConfiguration(
sourceSet: KotlinSourceSet, configuration: Configuration,
): ArtifactView = configuration.incoming
.apply {
val dependencyFilter = dependencyFilter
if (dependencyFilter != null) dependencies.removeIf { dependency -> !dependencyFilter.invoke(dependency) }
}
.artifactView { view ->
view.isLenient = true
view.attributes.setupArtifactViewAttributes(sourceSet)
if (componentFilter != null) {
view.componentFilter(componentFilter)
}
}
}
private companion object {
val logger: Logger = Logging.getLogger(IdeBinaryDependencyResolver::class.java)
@@ -9,6 +9,7 @@ package org.jetbrains.kotlin.gradle.plugin.ide.dependencyResolvers
import org.gradle.api.Project
import org.gradle.api.artifacts.DependencySubstitutions
import org.gradle.api.artifacts.ProjectDependency
import org.gradle.api.artifacts.component.ModuleComponentSelector
import org.gradle.api.artifacts.component.ProjectComponentIdentifier
import org.gradle.api.attributes.Category
@@ -44,7 +45,8 @@ internal fun IdeJvmAndAndroidPlatformBinaryDependencyResolver(project: Project):
Otherwise we would match the -jvm.jar from the dependency project which will result in
matching the jvmMain source set as well (which is undesired)
*/
componentFilter = { identifier -> identifier !is ProjectComponentIdentifier },
componentFilter = { identifier -> identifier !is ProjectComponentIdentifier }, // paranoia: Only dependencyFilter should be OK
dependencyFilter = { dependency -> dependency !is ProjectDependency },
dependencySubstitution = ::substituteStdlibCommonWithAndroidJvm,
)
)
@@ -29,6 +29,10 @@ internal object IdeJvmAndAndroidSourceDependencyResolver : IdeDependencyResolver
if (!isJvmAndAndroid(sourceSet)) return emptySet()
if (sourceSet !is DefaultKotlinSourceSet) return emptySet()
return sourceSet.resolveMetadata<MetadataDependencyResolution.ChooseVisibleSourceSets>()
/**
* See [IdeVisibleMultiplatformSourceDependencyResolver] on why this could happen
*/
.filter { chooseVisibleSourceSets -> chooseVisibleSourceSets.projectDependency(sourceSet.project) != sourceSet.project }
.flatMap { chooseVisibleSourceSets ->
val projectDependency = chooseVisibleSourceSets.projectDependency(sourceSet.project)
?: return@flatMap emptyList<IdeaKotlinDependency>()
@@ -13,18 +13,28 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.ide.IdeDependencyResolver
import org.jetbrains.kotlin.gradle.plugin.ide.IdeaKotlinProjectCoordinates
import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution
import org.jetbrains.kotlin.gradle.plugin.mpp.projectDependency
import org.jetbrains.kotlin.gradle.plugin.sources.DefaultKotlinSourceSet
internal object IdeVisibleMultiplatformSourceDependencyResolver : IdeDependencyResolver {
override fun resolve(sourceSet: KotlinSourceSet): Set<IdeaKotlinDependency> {
if (sourceSet !is DefaultKotlinSourceSet) return emptySet()
return sourceSet.resolveMetadata<MetadataDependencyResolution.ChooseVisibleSourceSets>()
/*
Check if resolved dependency is pointing to the project that contains the Source Set
Such 'loop' or 'self' dependencies can happen when e.g.
1) 'project A' depends on 'project B'
2) 'project B / test' depends on project A
In this case project B tests will get a project dependency pointing to project B's main variant
*/
.filter { resolution -> resolution.projectDependency(sourceSet.project) != sourceSet.project }
.flatMap { resolution -> resolveSourceDependencies(resolution) }
.toSet()
}
private fun resolveSourceDependencies(
resolution: MetadataDependencyResolution.ChooseVisibleSourceSets
resolution: MetadataDependencyResolution.ChooseVisibleSourceSets,
): Iterable<IdeaKotlinDependency> {
val projectComponentIdentifier = resolution.dependency.id as? ProjectComponentIdentifier ?: return emptyList()
return resolution.allVisibleSourceSetNames.map { visibleSourceSetName ->
@@ -112,6 +112,33 @@ class IdeJvmAndAndroidDependencyResolutionTest {
)
}
@Test
fun `test - KT-59020 - transitive project dependency to self`() {
val root = buildProject { setMultiplatformAndroidSourceSetLayoutVersion(2) }
val a = buildProject({ withParent(root).withName("a") }) { configureAndroidAndMultiplatform() }
val b = buildProject({ withParent(root).withName("b") }) { configureAndroidAndMultiplatform() }
b.multiplatformExtension.commonMain.dependencies {
api(project(":a"))
}
a.multiplatformExtension.commonTest.dependencies {
api(project(":b"))
}
root.evaluate()
a.evaluate()
b.evaluate()
a.kotlinIdeMultiplatformImport.resolveDependencies("jvmAndAndroidTest").assertMatches(
friendSourceDependency(":a/commonMain"),
friendSourceDependency(":a/jvmAndAndroidMain"),
dependsOnDependency(":a/commonTest"),
regularSourceDependency(":b/commonMain"),
regularSourceDependency(":b/jvmAndAndroidMain"),
)
}
@Test
fun `test - default stdlib with no other dependencies`() {
val project = buildProject { configureAndroidAndMultiplatform(enableDefaultStdlib = true) }