[Gradle] Implement IdePlatformDependencyResolver

(ported from IdeaKpmPlatformDependencyResolver)

^KT-54948 Verification Pending
This commit is contained in:
Sebastian Sellmair
2022-11-15 09:42:35 +01:00
committed by Space Team
parent e15812f196
commit bc9296d646
7 changed files with 247 additions and 1 deletions
@@ -13,7 +13,7 @@ data class IdeaKotlinBinaryCoordinates(
val group: String,
val module: String,
val version: String?,
val sourceSetName: String?
val sourceSetName: String? = null,
) : IdeaKotlinDependencyCoordinates {
override fun toString(): String {
return "$group:$module:$version${sourceSetName?.let { ":$it" }.orEmpty()}"
@@ -12,6 +12,8 @@ import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
import org.jetbrains.kotlin.gradle.idea.serialize.IdeaKotlinExtrasSerializationExtension
import org.jetbrains.kotlin.gradle.idea.serialize.IdeaKotlinExtrasSerializationExtensionBuilder
import org.jetbrains.kotlin.gradle.idea.tcs.IdeaKotlinDependency
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType.androidJvm
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType.jvm
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.extraProperties
import org.jetbrains.kotlin.gradle.plugin.ide.IdeMultiplatformImport.DependencyResolutionLevel.Default
@@ -139,6 +141,10 @@ interface IdeMultiplatformImport {
val unconstrained = SourceSetConstraint { true }
val isNative = SourceSetConstraint { sourceSet -> isNativeSourceSet(sourceSet) }
val isMetadata = SourceSetConstraint { sourceSet -> sourceSet.internal.compilations.any { it is KotlinMetadataCompilation } }
val isPlatform = !isMetadata and SourceSetConstraint { sourceSet -> sourceSet.internal.compilations.any() }
val isJvmAndAndroid = SourceSetConstraint { sourceSet ->
sourceSet.internal.compilations.map { it.platformType }.toSet() == setOf(jvm, androidJvm)
}
}
}
@@ -160,3 +166,19 @@ fun IdeMultiplatformImport.registerExtrasSerializationExtension(
) {
registerExtrasSerializationExtension(IdeaKotlinExtrasSerializationExtension(builder))
}
infix fun SourceSetConstraint.or(
other: SourceSetConstraint
) = SourceSetConstraint { sourceSet ->
this@or(sourceSet) || other(sourceSet)
}
infix fun SourceSetConstraint.and(
other: SourceSetConstraint
) = SourceSetConstraint { sourceSet ->
this@and(sourceSet) && other(sourceSet)
}
operator fun SourceSetConstraint.not() = SourceSetConstraint { sourceSet ->
this@not(sourceSet).not()
}
@@ -9,6 +9,8 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.idea.serialize.IdeaKotlinExtrasSerializer
import org.jetbrains.kotlin.gradle.kpm.idea.kotlinDebugKey
import org.jetbrains.kotlin.gradle.plugin.ide.dependencyResolvers.IdeDependsOnDependencyResolver
import org.jetbrains.kotlin.gradle.plugin.ide.dependencyResolvers.IdeJvmAndAndroidPlatformDependencyResolver
import org.jetbrains.kotlin.gradle.plugin.ide.dependencyResolvers.IdePlatformDependencyResolver
import org.jetbrains.kotlin.gradle.plugin.ide.dependencyResolvers.IdeTransformedMetadataDependencyResolver
fun IdeMultiplatformImport(extension: KotlinMultiplatformExtension): IdeMultiplatformImport {
@@ -28,6 +30,24 @@ fun IdeMultiplatformImport(extension: KotlinMultiplatformExtension): IdeMultipla
level = IdeMultiplatformImport.DependencyResolutionLevel.Default
)
registerDependencyResolver(
resolver = IdePlatformDependencyResolver(),
constraint = IdeMultiplatformImport.SourceSetConstraint.isPlatform,
phase = IdeMultiplatformImport.DependencyResolutionPhase.BinaryDependencyResolution,
level = IdeMultiplatformImport.DependencyResolutionLevel.Default
)
/*
Register resolution of dependencies for jvm+android dependencies:
This resolver will resolve dependencies visible to the source set from a 'jvm' perspective.
*/
registerDependencyResolver(
resolver = IdeJvmAndAndroidPlatformDependencyResolver(extension.project),
constraint = IdeMultiplatformImport.SourceSetConstraint.isJvmAndAndroid,
phase = IdeMultiplatformImport.DependencyResolutionPhase.BinaryDependencyResolution,
level = IdeMultiplatformImport.DependencyResolutionLevel.Default
)
registerExtrasSerializationExtension {
register(kotlinDebugKey, IdeaKotlinExtrasSerializer.javaIoSerializable())
}
@@ -0,0 +1,36 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
@file:Suppress("FunctionName")
package org.jetbrains.kotlin.gradle.plugin.ide.dependencyResolvers
import org.gradle.api.Project
import org.gradle.api.attributes.Category
import org.gradle.api.attributes.Usage
import org.gradle.api.attributes.java.TargetJvmEnvironment
import org.jetbrains.kotlin.gradle.idea.tcs.IdeaKotlinDependency
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.ide.IdeDependencyResolver
import org.jetbrains.kotlin.gradle.plugin.usageByName
import org.jetbrains.kotlin.gradle.utils.named
/**
* Resolves dependencies of jvm and Android source sets from the perspective jvm
*/
internal fun IdeJvmAndAndroidPlatformDependencyResolver(project: Project): IdeDependencyResolver = IdePlatformDependencyResolver(
binaryType = IdeaKotlinDependency.CLASSPATH_BINARY_TYPE,
artifactResolutionStrategy = IdePlatformDependencyResolver.ArtifactResolutionStrategy.PlatformLikeSourceSet(
setupPlatformResolutionAttributes = {
attributes.attribute(Usage.USAGE_ATTRIBUTE, project.usageByName(Usage.JAVA_API))
attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.objects.named(Category.LIBRARY))
attributes.attribute(KotlinPlatformType.attribute, KotlinPlatformType.jvm)
attributes.attribute(
TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE,
project.objects.named(TargetJvmEnvironment.STANDARD_JVM)
)
},
)
)
@@ -0,0 +1,158 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.gradle.plugin.ide.dependencyResolvers
import org.gradle.api.artifacts.ArtifactView
import org.gradle.api.artifacts.component.ComponentIdentifier
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
import org.gradle.api.artifacts.component.ModuleComponentSelector
import org.gradle.api.artifacts.component.ProjectComponentIdentifier
import org.gradle.api.attributes.AttributeContainer
import org.gradle.api.logging.Logging
import org.gradle.internal.resolve.ModuleVersionResolveException
import org.jetbrains.kotlin.gradle.idea.tcs.IdeaKotlinBinaryCoordinates
import org.jetbrains.kotlin.gradle.idea.tcs.IdeaKotlinDependency
import org.jetbrains.kotlin.gradle.idea.tcs.IdeaKotlinResolvedBinaryDependency
import org.jetbrains.kotlin.gradle.idea.tcs.IdeaKotlinUnresolvedBinaryDependency
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.ide.IdeDependencyResolver
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMetadataCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.internal
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.GradleKpmFragment
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.GradleKpmVariant
import org.jetbrains.kotlin.gradle.plugin.sources.*
import org.jetbrains.kotlin.gradle.targets.metadata.ALL_COMPILE_METADATA_CONFIGURATION_NAME
import org.jetbrains.kotlin.tooling.core.mutableExtrasOf
internal class IdePlatformDependencyResolver(
private val binaryType: String = IdeaKotlinDependency.CLASSPATH_BINARY_TYPE,
private val artifactResolutionStrategy: ArtifactResolutionStrategy = ArtifactResolutionStrategy.Compilation()
) : IdeDependencyResolver {
sealed class ArtifactResolutionStrategy {
/**
* Resolve the artifacts from a [GradleKpmVariant] using its [GradleKpmVariant.compileDependenciesConfiguration],
* which already knows how to resolve platform artifacts.
* @param setupArtifactViewAttributes: Additional attributes that will be used to create an [ArtifactView] for resolving the dependencies.
*/
data class Compilation(
internal val compilationSelector: (Set<KotlinCompilation<*>>) -> KotlinCompilation<*>? = { it.singleOrNull() },
internal val setupArtifactViewAttributes: AttributeContainer.(sourceSet: KotlinSourceSet) -> Unit = {}
) : ArtifactResolutionStrategy()
/**
* Capable of resolving artifacts from a plain [GradleKpmFragment] which does not have to implement [GradleKpmVariant].
* Such fragments are called 'platform-like', since they still resolve the linkable platform dependencies.
* @param setupPlatformResolutionAttributes: Attributes describing how to resolve platform artifacts in general.
* @param setupArtifactViewAttributes: Additional attributes that will be used to create an [ArtifactView] for
* resolving the dependencies
*/
data class PlatformLikeSourceSet(
internal val setupPlatformResolutionAttributes: AttributeContainer.(sourceSet: KotlinSourceSet) -> Unit,
internal val setupArtifactViewAttributes: AttributeContainer.(sourceSet: KotlinSourceSet) -> Unit = {},
) : ArtifactResolutionStrategy()
}
override fun resolve(sourceSet: KotlinSourceSet): Set<IdeaKotlinDependency> {
val artifacts = artifactResolutionStrategy.createArtifactView(sourceSet.internal)?.artifacts ?: return emptySet()
val unresolvedDependencies = artifacts.failures
.onEach { reason -> sourceSet.project.logger.error("Failed to resolve dependency", reason) }
.map { reason ->
val selector = (reason as? ModuleVersionResolveException)?.selector as? ModuleComponentSelector
/* Can't figure out the dependency here :( */
?: return@map IdeaKotlinUnresolvedBinaryDependency(
coordinates = null, cause = reason.message?.takeIf { it.isNotBlank() }, extras = mutableExtrasOf()
)
IdeaKotlinUnresolvedBinaryDependency(
coordinates = IdeaKotlinBinaryCoordinates(selector.group, selector.module, selector.version, null),
cause = reason.message?.takeIf { it.isNotBlank() },
extras = mutableExtrasOf()
)
}.toSet()
val resolvedDependencies = artifacts.artifacts.mapNotNull { artifact ->
IdeaKotlinResolvedBinaryDependency(
coordinates = artifact.variant.owner.toIdeaKotlinBinaryCoordinatesOrNull(),
binaryType = binaryType,
binaryFile = artifact.file,
extras = mutableExtrasOf()
)
}.toSet()
return resolvedDependencies + unresolvedDependencies
}
private fun ArtifactResolutionStrategy.createArtifactView(sourceSet: InternalKotlinSourceSet): ArtifactView? {
return when (this) {
is ArtifactResolutionStrategy.Compilation -> createArtifactView(sourceSet)
is ArtifactResolutionStrategy.PlatformLikeSourceSet -> createArtifactView(sourceSet)
}
}
private fun ArtifactResolutionStrategy.Compilation.createArtifactView(sourceSet: InternalKotlinSourceSet): ArtifactView? {
val compilation = compilationSelector(sourceSet.compilations) ?: return null
/*
Prevent case where this resolver was configured to resolve dependencies for a metadata compilation:
Refuse resolution. Write your own code if you really want to do this!
*/
if (compilation is KotlinMetadataCompilation<*>) {
logger.warn("Unexpected ${KotlinMetadataCompilation::class.java}(${compilation.name}) for $sourceSet")
return null
}
return compilation.internal.configurations.compileDependencyConfiguration.incoming.artifactView { view ->
view.isLenient = true
view.componentFilter { id -> id !is ProjectComponentIdentifier }
view.attributes.setupArtifactViewAttributes(sourceSet)
}
}
private fun ArtifactResolutionStrategy.PlatformLikeSourceSet.createArtifactView(sourceSet: InternalKotlinSourceSet): ArtifactView? {
if (sourceSet !is DefaultKotlinSourceSet) return null
val sourceSetCompileDependencies = sourceSet.project.configurations.detachedConfiguration()
sourceSetCompileDependencies.attributes.setupPlatformResolutionAttributes(sourceSet)
((sourceSet.getAdditionalVisibleSourceSets() + sourceSet).withDependsOnClosure).forEach { visibleSourceSet ->
sourceSetCompileDependencies.dependencies.addAll(
sourceSet.project.configurations.getByName(visibleSourceSet.apiConfigurationName).allDependencies
)
sourceSetCompileDependencies.dependencies.addAll(
sourceSet.project.configurations.getByName(visibleSourceSet.implementationConfigurationName).allDependencies
)
sourceSetCompileDependencies.dependencies.addAll(
sourceSet.project.configurations.getByName(visibleSourceSet.compileOnlyConfigurationName).allDependencies
)
}
/* Ensure consistent dependency resolution result within the whole module */
sourceSetCompileDependencies.shouldResolveConsistentlyWith(
sourceSet.project.configurations.getByName(ALL_COMPILE_METADATA_CONFIGURATION_NAME)
)
return sourceSetCompileDependencies.incoming.artifactView { view ->
view.isLenient = true
view.componentFilter { id -> id !is ProjectComponentIdentifier }
view.attributes.setupArtifactViewAttributes(sourceSet)
}
}
private fun ComponentIdentifier.toIdeaKotlinBinaryCoordinatesOrNull(): IdeaKotlinBinaryCoordinates? {
return when (this) {
is ModuleComponentIdentifier -> IdeaKotlinBinaryCoordinates(group, module, version)
else -> null
}
}
private companion object {
val logger = Logging.getLogger(IdePlatformDependencyResolver::class.java)
}
}
@@ -23,3 +23,5 @@ internal interface InternalKotlinSourceSet : KotlinSourceSet {
val withDependsOnClosure: ObservableSet<KotlinSourceSet>
val compilations: MutableObservableSet<KotlinCompilation<*>>
}
internal val KotlinSourceSet.project: Project get() = this.internal.project
@@ -34,6 +34,7 @@ class IdeTransformedMetadataDependencyResolverTest {
kotlin.linuxArm64()
val commonMain = kotlin.sourceSets.getByName("commonMain")
val commonTest = kotlin.sourceSets.getByName("commonTest")
val linuxMain = kotlin.sourceSets.getByName("linuxMain")
commonMain.dependencies {
@@ -51,6 +52,13 @@ class IdeTransformedMetadataDependencyResolverTest {
binaryCoordinates("com.arkivanov.essenty:instance-keeper:0.4.2:commonMain")
)
IdeTransformedMetadataDependencyResolver.resolve(commonTest)
.assertMatches(
binaryCoordinates("com.arkivanov.mvikotlin:mvikotlin:3.0.2:commonMain"),
binaryCoordinates("com.arkivanov.essenty:lifecycle:0.4.2:commonMain"),
binaryCoordinates("com.arkivanov.essenty:instance-keeper:0.4.2:commonMain")
)
IdeTransformedMetadataDependencyResolver.resolve(linuxMain)
.assertMatches(
binaryCoordinates("com.arkivanov.mvikotlin:mvikotlin:3.0.2:commonMain"),