diff --git a/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinDependencyCoordinates.kt b/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinDependencyCoordinates.kt index 99b23c587a9..fe3ebf1eb06 100644 --- a/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinDependencyCoordinates.kt +++ b/libraries/tools/kotlin-gradle-plugin-idea/src/main/kotlin/org/jetbrains/kotlin/gradle/idea/tcs/IdeaKotlinDependencyCoordinates.kt @@ -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()}" diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/IdeMultiplatformImport.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/IdeMultiplatformImport.kt index 560cf0037c3..af577749c49 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/IdeMultiplatformImport.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/IdeMultiplatformImport.kt @@ -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() +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/IdeMultiplatformImportFactory.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/IdeMultiplatformImportFactory.kt index 8d9d67cf191..cc23ddf6bac 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/IdeMultiplatformImportFactory.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/IdeMultiplatformImportFactory.kt @@ -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()) } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/dependencyResolvers/IdeJvmAndAndroidPlatformDependencyResolver.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/dependencyResolvers/IdeJvmAndAndroidPlatformDependencyResolver.kt new file mode 100644 index 00000000000..915f49b775a --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/dependencyResolvers/IdeJvmAndAndroidPlatformDependencyResolver.kt @@ -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) + ) + }, + ) +) diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/dependencyResolvers/IdePlatformDependencyResolver.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/dependencyResolvers/IdePlatformDependencyResolver.kt new file mode 100644 index 00000000000..55b69bb229e --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/dependencyResolvers/IdePlatformDependencyResolver.kt @@ -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<*>? = { 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 { + 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) + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/InternalKotlinSourceSet.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/InternalKotlinSourceSet.kt index cc4ab373f4a..7958abcb9f7 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/InternalKotlinSourceSet.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/InternalKotlinSourceSet.kt @@ -23,3 +23,5 @@ internal interface InternalKotlinSourceSet : KotlinSourceSet { val withDependsOnClosure: ObservableSet val compilations: MutableObservableSet> } + +internal val KotlinSourceSet.project: Project get() = this.internal.project \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/ide/IdeTransformedMetadataDependencyResolverTest.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/ide/IdeTransformedMetadataDependencyResolverTest.kt index a0b4eafc3dd..622947a3a87 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/ide/IdeTransformedMetadataDependencyResolverTest.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/ide/IdeTransformedMetadataDependencyResolverTest.kt @@ -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"),