From 9771e57951ffe77dd2bf27ca2a48e8eaa7592331 Mon Sep 17 00:00:00 2001 From: Sebastian Sellmair Date: Thu, 10 Nov 2022 14:09:23 +0100 Subject: [PATCH] [Gradle] Implement IdeTransformedMetadataDependencyResolver KT-54825 --- .../src/main/proto/proto_tcs.proto | 52 +++++++++++++++ .../kpm/idea/IdeaKpmBuildProjectModelTask.kt | 2 +- ...IdeaKpmMetadataBinaryDependencyResolver.kt | 1 + .../plugin/ide/IdeMultiplatformImport.kt | 1 + .../ide/IdeMultiplatformImportFactory.kt | 8 +++ ...deTransformedMetadataDependencyResolver.kt | 64 +++++++++++++++++++ .../plugin/mpp/CompositeMetadataArtifact.kt | 15 ++--- ...ansformedMetadataDependencyResolverTest.kt | 62 ++++++++++++++++++ 8 files changed, 196 insertions(+), 9 deletions(-) create mode 100644 libraries/tools/kotlin-gradle-plugin-idea-proto/src/main/proto/proto_tcs.proto create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/dependencyResolvers/IdeTransformedMetadataDependencyResolver.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/ide/IdeTransformedMetadataDependencyResolverTest.kt diff --git a/libraries/tools/kotlin-gradle-plugin-idea-proto/src/main/proto/proto_tcs.proto b/libraries/tools/kotlin-gradle-plugin-idea-proto/src/main/proto/proto_tcs.proto new file mode 100644 index 00000000000..8fc2851eb93 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-idea-proto/src/main/proto/proto_tcs.proto @@ -0,0 +1,52 @@ +syntax = "proto3"; + +package org.jetbrains.kotlin.gradle.idea.proto.generated.tcs; +import "proto_extras.proto"; +option java_multiple_files = true; + +message IdeaKotlinDependencyProto { + oneof dependency { + IdeaKotlinSourceDependencyProto source_dependency = 1; + IdeaKotlinResolvedBinaryDependencyProto resolved_binary_dependency = 2; + IdeaKotlinUnresolvedBinaryDependencyProto unresolved_binary_dependency = 3; + } +} + +message IdeaKotlinSourceDependencyProto { + enum Type { + REGULAR = 0; + FRIEND = 1; + DEPENDS_ON = 2; + } + + optional IdeaExtrasProto extras = 1; + optional Type type = 2; + optional IdeaKotlinSourceCoordinatesProto coordinates = 3; +} + +message IdeaKotlinUnresolvedBinaryDependencyProto { + optional IdeaExtrasProto extras = 1; + optional IdeaKotlinBinaryCoordinatesProto coordinates = 2; + optional string cause = 3; +} + +message IdeaKotlinResolvedBinaryDependencyProto { + optional IdeaExtrasProto extras = 1; + optional IdeaKotlinBinaryCoordinatesProto coordinates = 2; + optional string binary_type = 3; + optional string binary_file = 4; +} + +message IdeaKotlinBinaryCoordinatesProto { + optional string group = 1; + optional string module = 2; + optional string version = 3; + optional string source_set_name = 4; +} + +message IdeaKotlinSourceCoordinatesProto { + optional string build_id = 1; + optional string project_path = 2; + optional string project_name = 3; + optional string source_set_name = 4; +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKpmBuildProjectModelTask.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKpmBuildProjectModelTask.kt index b1777090a1b..33c4717dec5 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKpmBuildProjectModelTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKpmBuildProjectModelTask.kt @@ -14,7 +14,7 @@ import org.gradle.api.tasks.TaskProvider import org.jetbrains.kotlin.gradle.dsl.pm20Extension import org.jetbrains.kotlin.gradle.tasks.locateOrRegisterTask import org.jetbrains.kotlin.gradle.utils.doNotTrackStateCompat -import org.jetbrains.kotlin.kpm.idea.proto.writeTo +import org.jetbrains.kotlin.gradle.idea.proto.kpm.writeTo import java.io.ByteArrayOutputStream import java.io.ObjectOutputStream diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKpmMetadataBinaryDependencyResolver.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKpmMetadataBinaryDependencyResolver.kt index fb4ed3bd5ed..006169e8d79 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKpmMetadataBinaryDependencyResolver.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/IdeaKpmMetadataBinaryDependencyResolver.kt @@ -17,6 +17,7 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.GradleKpmFragment import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.GradleKpmFragmentGranularMetadataResolverFactory import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.GradleKpmModule.Companion.moduleName import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.toKpmModuleDependency +import org.jetbrains.kotlin.gradle.plugin.mpp.read internal class IdeaKpmMetadataBinaryDependencyResolver( private val fragmentGranularMetadataResolverFactory: GradleKpmFragmentGranularMetadataResolverFactory 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 910d1be7d11..6a86193874a 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 @@ -138,6 +138,7 @@ interface IdeMultiplatformImport { companion object { val unconstrained = SourceSetConstraint { true } val isNative = SourceSetConstraint { sourceSet -> isNativeSourceSet(sourceSet) } + val isMetadata = SourceSetConstraint { sourceSet -> sourceSet.internal.compilations.any { it is KotlinMetadataCompilation } } } } 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 3399d4b60b2..a59fdbe58d8 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,7 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.idea.serialize.IdeaExtrasSerializer 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.IdeTransformedMetadataDependencyResolver fun IdeMultiplatformImport(extension: KotlinMultiplatformExtension): IdeMultiplatformImport { return IdeMultiplatformImportImpl(extension).apply { @@ -20,6 +21,13 @@ fun IdeMultiplatformImport(extension: KotlinMultiplatformExtension): IdeMultipla level = IdeMultiplatformImport.DependencyResolutionLevel.Default ) + registerDependencyResolver( + resolver = IdeTransformedMetadataDependencyResolver, + constraint = IdeMultiplatformImport.SourceSetConstraint.isMetadata, + phase = IdeMultiplatformImport.DependencyResolutionPhase.BinaryDependencyResolution, + level = IdeMultiplatformImport.DependencyResolutionLevel.Default + ) + registerExtrasSerializationExtension { register(kotlinDebugKey, IdeaExtrasSerializer.javaIoSerializable()) } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/dependencyResolvers/IdeTransformedMetadataDependencyResolver.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/dependencyResolvers/IdeTransformedMetadataDependencyResolver.kt new file mode 100644 index 00000000000..a51fd3f25a8 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/dependencyResolvers/IdeTransformedMetadataDependencyResolver.kt @@ -0,0 +1,64 @@ +/* + * 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.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.plugin.KotlinSourceSet +import org.jetbrains.kotlin.gradle.plugin.ide.IdeDependencyResolver +import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets +import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets.MetadataProvider.ArtifactMetadataProvider +import org.jetbrains.kotlin.gradle.plugin.mpp.kotlinTransformedMetadataLibraryDirectoryForIde +import org.jetbrains.kotlin.gradle.plugin.mpp.read +import org.jetbrains.kotlin.gradle.plugin.sources.DefaultKotlinSourceSet +import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope.* +import org.jetbrains.kotlin.gradle.plugin.sources.internal +import org.jetbrains.kotlin.tooling.core.mutableExtrasOf + +internal object IdeTransformedMetadataDependencyResolver : IdeDependencyResolver { + override fun resolve(sourceSet: KotlinSourceSet): Set { + if (sourceSet !is DefaultKotlinSourceSet) return emptySet() + return sourceSet.dependencyTransformations[API_SCOPE]?.metadataDependencyResolutions?.toList().orEmpty() + .plus(sourceSet.dependencyTransformations[IMPLEMENTATION_SCOPE]?.metadataDependencyResolutions?.toList().orEmpty()) + .plus(sourceSet.dependencyTransformations[COMPILE_ONLY_SCOPE]?.metadataDependencyResolutions?.toList().orEmpty()) + .filterIsInstance() + .flatMap { resolution -> resolve(sourceSet, resolution) } + .toSet() + } + + private fun resolve(sourceSet: KotlinSourceSet, resolution: ChooseVisibleSourceSets): Iterable { + val metadataProvider = resolution.metadataProvider as? ArtifactMetadataProvider ?: return emptySet() + + return metadataProvider.read { artifactContent -> + resolution.allVisibleSourceSetNames.mapNotNull { visibleSourceSet -> + val sourceSetContent = artifactContent.findSourceSet(visibleSourceSet) ?: return@mapNotNull null + val sourceSetMetadataBinary = sourceSetContent.metadataBinary ?: return@mapNotNull null + + val metadataLibraryOutputFile = sourceSet.internal.project.kotlinTransformedMetadataLibraryDirectoryForIde + .resolve(sourceSetMetadataBinary.relativeFile) + + metadataLibraryOutputFile.parentFile.mkdirs() + if (!metadataLibraryOutputFile.exists()) { + sourceSetMetadataBinary.copyTo(metadataLibraryOutputFile) + if (!metadataLibraryOutputFile.exists()) return@mapNotNull null + } + + IdeaKotlinResolvedBinaryDependency( + binaryType = IdeaKotlinDependency.CLASSPATH_BINARY_TYPE, + binaryFile = metadataLibraryOutputFile, + extras = mutableExtrasOf(), + coordinates = IdeaKotlinBinaryCoordinates( + group = metadataProvider.moduleDependencyIdentifier.groupId ?: "", + module = metadataProvider.moduleDependencyIdentifier.moduleId, + version = metadataProvider.moduleDependencyVersion, + sourceSetName = visibleSourceSet + ) + ) + } + } + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/CompositeMetadataArtifact.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/CompositeMetadataArtifact.kt index 0f0f2d407a3..4be871a63b0 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/CompositeMetadataArtifact.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/CompositeMetadataArtifact.kt @@ -21,16 +21,15 @@ internal interface CompositeMetadataArtifact { * Alternatively use the [read] function instead. */ fun open(): CompositeMetadataArtifactContent - - /** - * Safe shortcut function for opening and reading the content of this artifact. - * The [CompositeMetadataArtifactContent] will be closed after the [action] executed. - */ - fun read(action: (artifactContent: CompositeMetadataArtifactContent) -> T): T { - return open().use(action) - } } +/** + * Safe shortcut function for opening and reading the content of this artifact. + * The [CompositeMetadataArtifactContent] will be closed after the [action] executed. + */ +internal inline fun CompositeMetadataArtifact.read(action: (artifactContent: CompositeMetadataArtifactContent) -> T): T { + return open().use(action) +} /** * This [CompositeMetadataArtifactContent] abstraction provides access into a metadata artifact published by a Multiplatform Library 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 new file mode 100644 index 00000000000..a0b4eafc3dd --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/ide/IdeTransformedMetadataDependencyResolverTest.kt @@ -0,0 +1,62 @@ +/* + * 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.ide + +import org.jetbrains.kotlin.gradle.* +import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension +import org.jetbrains.kotlin.gradle.idea.testFixtures.tcs.assertMatches +import org.jetbrains.kotlin.gradle.idea.testFixtures.tcs.binaryCoordinates +import org.jetbrains.kotlin.gradle.kpm.idea.mavenCentralCacheRedirector +import org.jetbrains.kotlin.gradle.plugin.ide.dependencyResolvers.IdeTransformedMetadataDependencyResolver +import org.junit.Test + +class IdeTransformedMetadataDependencyResolverTest { + + @Test + fun `test - MVIKotlin`() { + val project = buildProject { + enableDefaultStdlibDependency(false) + enableDependencyVerification(false) + applyMultiplatformPlugin() + repositories.mavenCentralCacheRedirector() + } + + val kotlin = project.multiplatformExtension + kotlin.targetHierarchy.default() + + kotlin.jvm() + kotlin.linuxX64() + kotlin.linuxArm64() + + val commonMain = kotlin.sourceSets.getByName("commonMain") + val linuxMain = kotlin.sourceSets.getByName("linuxMain") + + commonMain.dependencies { + implementation("com.arkivanov.mvikotlin:mvikotlin:3.0.2") + implementation("com.arkivanov.essenty:lifecycle:0.4.2") + implementation("com.arkivanov.essenty:instance-keeper:0.4.2") + } + + project.evaluate() + + IdeTransformedMetadataDependencyResolver.resolve(commonMain) + .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"), + binaryCoordinates("com.arkivanov.mvikotlin:mvikotlin:3.0.2:jsNativeMain"), + binaryCoordinates("com.arkivanov.essenty:lifecycle:0.4.2:commonMain"), + binaryCoordinates("com.arkivanov.essenty:instance-keeper:0.4.2:commonMain") + ) + } +} \ No newline at end of file