[Gradle] jvm+android: Support source dependency resolution to jvm only project
^KT-62033 Verification Pending
This commit is contained in:
committed by
Space Team
parent
5473db9ac5
commit
cb5026b528
+79
-10
@@ -5,7 +5,12 @@
|
||||
|
||||
package org.jetbrains.kotlin.gradle.plugin.ide.dependencyResolvers
|
||||
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.artifacts.component.ProjectComponentIdentifier
|
||||
import org.gradle.api.attributes.Attribute
|
||||
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
|
||||
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull
|
||||
import org.jetbrains.kotlin.gradle.idea.tcs.IdeaKotlinBinaryDependency
|
||||
import org.jetbrains.kotlin.gradle.idea.tcs.IdeaKotlinDependency
|
||||
import org.jetbrains.kotlin.gradle.idea.tcs.IdeaKotlinSourceDependency
|
||||
import org.jetbrains.kotlin.gradle.idea.tcs.IdeaKotlinSourceDependency.Type.Regular
|
||||
@@ -15,7 +20,7 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
|
||||
import org.jetbrains.kotlin.gradle.plugin.ide.IdeDependencyResolver
|
||||
import org.jetbrains.kotlin.gradle.plugin.ide.IdeMultiplatformImport.SourceSetConstraint.Companion.isJvmAndAndroid
|
||||
import org.jetbrains.kotlin.gradle.plugin.ide.IdeaKotlinSourceCoordinates
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmAndroidCompilation
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.*
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.isMain
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.projectDependency
|
||||
@@ -23,27 +28,91 @@ import org.jetbrains.kotlin.gradle.plugin.sources.DefaultKotlinSourceSet
|
||||
import org.jetbrains.kotlin.gradle.plugin.sources.android.AndroidVariantType
|
||||
import org.jetbrains.kotlin.gradle.plugin.sources.android.type
|
||||
import org.jetbrains.kotlin.gradle.plugin.sources.internal
|
||||
import org.jetbrains.kotlin.gradle.utils.copyAttributes
|
||||
import org.jetbrains.kotlin.gradle.utils.toMap
|
||||
|
||||
/**
|
||||
* ### Special Resolver for the JVM + Android use case:
|
||||
* This resolver takes care of resolving the 'source dependencies' (aka dependencies to other projects' SourceSets)
|
||||
* for SourceSets sharing code across a JVM and Android target.
|
||||
*
|
||||
* There are two special cases this resolver will archive its purpose:
|
||||
*
|
||||
* #### 1: 'This' multiplatform project depends on another multiplatform project:
|
||||
* In this case, the metadata dependency resolution will return a 'ChooseVisibleSourceSets'.
|
||||
* However, since we do not compile jvm+android into metadata, the resolution will not contain the desired source sets.
|
||||
* Instead, the algorithm here will resolve the dependency project, look into it and will pick the corresponding 'jvm + android'
|
||||
* SourceSets that it can find
|
||||
*
|
||||
* #### 2: 'This' multiplatform project depends on a **non** multiplatform dependency project:
|
||||
* In this case the algorithm will use another approach to determine desirable SourceSets to resolve in the IDE:
|
||||
* It will use the 'PlatformLike' dependency resolution mechanics to resolve the project dependencies in an 'as if this was jvm' mode.
|
||||
* In order to set up attributes, the algorithm will copy the jvm platform attributes from the corresponding jvm compilations
|
||||
* of the current SourceSet.
|
||||
*/
|
||||
internal object IdeJvmAndAndroidSourceDependencyResolver : IdeDependencyResolver {
|
||||
override fun resolve(sourceSet: KotlinSourceSet): Set<IdeaKotlinDependency> {
|
||||
if (!isJvmAndAndroid(sourceSet)) return emptySet()
|
||||
if (sourceSet !is DefaultKotlinSourceSet) return emptySet()
|
||||
return sourceSet.resolveMetadata<MetadataDependencyResolution.ChooseVisibleSourceSets>()
|
||||
|
||||
return sourceSet.resolveMetadata<MetadataDependencyResolution>()
|
||||
/**
|
||||
* 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>()
|
||||
val kotlin = projectDependency.multiplatformExtensionOrNull ?: return@flatMap emptyList<IdeaKotlinDependency>()
|
||||
kotlin.sourceSets
|
||||
.filter { sourceSet -> isJvmAndAndroidMain(sourceSet) }
|
||||
.map { sourceSet -> IdeaKotlinSourceDependency(type = Regular, coordinates = IdeaKotlinSourceCoordinates(sourceSet)) }
|
||||
.filter { metadataDependencyResolution -> metadataDependencyResolution.projectDependency(sourceSet.project) != sourceSet.project }
|
||||
.flatMap { metadataDependencyResolution ->
|
||||
|
||||
when (metadataDependencyResolution) {
|
||||
/* Dependency project is multiplatform */
|
||||
is MetadataDependencyResolution.ChooseVisibleSourceSets -> resolveMultiplatformSourceSets(
|
||||
metadataDependencyResolution.projectDependency(sourceSet.project) ?: return@flatMap emptyList()
|
||||
)
|
||||
|
||||
/* Dependency project is not multiplatform (jvm/android only) */
|
||||
is MetadataDependencyResolution.KeepOriginalDependency -> resolveJvmSourceSets(sourceSet)
|
||||
else -> emptyList()
|
||||
}
|
||||
}
|
||||
.toSet()
|
||||
}
|
||||
|
||||
private fun resolveMultiplatformSourceSets(dependencyProject: Project): Iterable<IdeaKotlinDependency> {
|
||||
val kotlin = dependencyProject.multiplatformExtensionOrNull ?: return emptyList()
|
||||
return kotlin.sourceSets
|
||||
.filter { sourceSet -> isJvmAndAndroidMain(sourceSet) }
|
||||
.map { sourceSet -> IdeaKotlinSourceDependency(type = Regular, coordinates = IdeaKotlinSourceCoordinates(sourceSet)) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Pretend that this [sourceSet] is 'jvm' and resolve binaries.
|
||||
* #### Setting up attributes:
|
||||
* In order to set up the 'platform like' / 'jvm like' dependency resolution, this algorithm
|
||||
* will look at all 'jvm' based compilations, uses their 'compileDependencyConfiguration' as reference and
|
||||
* then uses the intersection of all available attributes
|
||||
*
|
||||
* #### componentFilter:
|
||||
* This resolver will just care about resolving project dependencies.
|
||||
* Therefore, a componentFilter is added to only resolve project dependencies.
|
||||
* We expect to resolve project artifact dependencies which can then be matched to the corresponding
|
||||
* SourceSets on IDE side.
|
||||
*/
|
||||
private fun resolveJvmSourceSets(sourceSet: KotlinSourceSet): Iterable<IdeaKotlinDependency> {
|
||||
return IdeBinaryDependencyResolver(
|
||||
binaryType = IdeaKotlinBinaryDependency.KOTLIN_COMPILE_BINARY_TYPE,
|
||||
artifactResolutionStrategy = IdeBinaryDependencyResolver.ArtifactResolutionStrategy.PlatformLikeSourceSet(
|
||||
setupPlatformResolutionAttributes = {
|
||||
sourceSet.internal.compilations.filter { it.platformType == KotlinPlatformType.jvm }
|
||||
.map { compilation -> compilation.internal.configurations.compileDependencyConfiguration.attributes }
|
||||
.map { attributes -> attributes.toMap().toList().toSet() }
|
||||
.reduceOrNull { acc, next -> acc intersect next }
|
||||
.orEmpty()
|
||||
.forEach { (key, value) -> @Suppress("UNCHECKED_CAST") attribute(key as Attribute<Any>, value as Any) }
|
||||
},
|
||||
componentFilter = { id -> id is ProjectComponentIdentifier }
|
||||
)
|
||||
).resolve(sourceSet)
|
||||
}
|
||||
|
||||
private fun isJvmAndAndroidMain(sourceSet: KotlinSourceSet): Boolean {
|
||||
if (!isJvmAndAndroid(sourceSet)) return false
|
||||
return sourceSet.internal.compilations.filter { it.platformType != KotlinPlatformType.common }.all { compilation ->
|
||||
|
||||
+36
-1
@@ -13,7 +13,9 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformSourceSetConventionsIm
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformSourceSetConventionsImpl.commonTest
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformSourceSetConventionsImpl.dependencies
|
||||
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
|
||||
import org.jetbrains.kotlin.gradle.idea.tcs.IdeaKotlinBinaryDependency
|
||||
import org.jetbrains.kotlin.gradle.idea.tcs.IdeaKotlinResolvedBinaryDependency
|
||||
import org.jetbrains.kotlin.gradle.idea.tcs.IdeaKotlinSourceDependency
|
||||
import org.jetbrains.kotlin.gradle.idea.testFixtures.tcs.*
|
||||
import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion
|
||||
import org.jetbrains.kotlin.gradle.plugin.ide.dependencyResolvers.IdeJvmAndAndroidPlatformBinaryDependencyResolver
|
||||
@@ -87,7 +89,7 @@ class IdeJvmAndAndroidDependencyResolutionTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - project to project dependency`() {
|
||||
fun `test - project to multiplatform project dependency`() {
|
||||
val root = buildProject { setMultiplatformAndroidSourceSetLayoutVersion(2) }
|
||||
val producer = buildProject({ withParent(root).withName("producer") }) { configureAndroidAndMultiplatform() }
|
||||
val consumer = buildProject({ withParent(root).withName("consumer") }) { configureAndroidAndMultiplatform() }
|
||||
@@ -96,6 +98,11 @@ class IdeJvmAndAndroidDependencyResolutionTest {
|
||||
producer.evaluate()
|
||||
consumer.evaluate()
|
||||
|
||||
root.allprojects { project ->
|
||||
project.repositories.mavenLocal()
|
||||
project.repositories.mavenCentral()
|
||||
}
|
||||
|
||||
consumer.multiplatformExtension.sourceSets.getByName("commonMain").dependencies {
|
||||
implementation(project(":producer"))
|
||||
}
|
||||
@@ -115,6 +122,34 @@ class IdeJvmAndAndroidDependencyResolutionTest {
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test - project to jvm project dependency`() {
|
||||
val root = buildProject()
|
||||
|
||||
val producer = buildProject({ withParent(root).withName("producer") }) { applyKotlinJvmPlugin() }
|
||||
val consumer = buildProject({ withParent(root).withName("consumer") }) { configureAndroidAndMultiplatform() }
|
||||
|
||||
consumer.multiplatformExtension.sourceSets.commonMain.dependencies {
|
||||
implementation(producer)
|
||||
}
|
||||
|
||||
root.evaluate()
|
||||
producer.evaluate()
|
||||
consumer.evaluate()
|
||||
|
||||
consumer.kotlinIdeMultiplatformImport.resolveDependencies("commonMain")
|
||||
.filter { it !is IdeaKotlinBinaryDependency }
|
||||
.assertMatches(projectArtifactDependency(IdeaKotlinSourceDependency.Type.Regular, ":producer", FilePathRegex(".*producer.jar")))
|
||||
|
||||
consumer.kotlinIdeMultiplatformImport.resolveDependencies("jvmAndAndroidMain")
|
||||
.filter { it !is IdeaKotlinBinaryDependency }
|
||||
.assertMatches(
|
||||
dependsOnDependency(":consumer/commonMain"),
|
||||
projectArtifactDependency(IdeaKotlinSourceDependency.Type.Regular, ":producer", FilePathRegex(".*producer.jar"))
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun `test - KT-59020 - transitive project dependency to self`() {
|
||||
val root = buildProject { setMultiplatformAndroidSourceSetLayoutVersion(2) }
|
||||
|
||||
Reference in New Issue
Block a user