diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/HierarchicalMppIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/HierarchicalMppIT.kt index fe845c7699f..b233b2e572e 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/HierarchicalMppIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/HierarchicalMppIT.kt @@ -101,7 +101,7 @@ class HierarchicalMppIT : KGPBaseTest() { val thirdPartyLibApiVisibility = reports.filter { report -> report.groupAndModule.startsWith("com.example.thirdparty:third-party-lib") && report.scope == "api" } - val jvmJsSourceSets = setOf("jvmMain", "jsMain", "jvmTest", "jsTest", "jvmAndJsMain", "jvmAndJsTest") + val jvmJsSourceSets = setOf("jvmAndJsMain", "jvmAndJsTest") thirdPartyLibApiVisibility.forEach { if (it.sourceSetName in jvmJsSourceSets) assertTrue("$it") { it.allVisibleSourceSets == setOf("commonMain") } @@ -771,6 +771,76 @@ class HierarchicalMppIT : KGPBaseTest() { } } + @GradleTest + @DisplayName("KT-52216: [TYPE_MISMATCH] Caused by unexpected metadata dependencies of leaf source sets") + fun `test default platform compilation source set has no metadata dependencies`(gradleVersion: GradleVersion) { + with(project("kt-52216", gradleVersion = gradleVersion)) { + build(":lib:publish") + testDependencyTransformations("p1") { reports -> + for (leafSourceSetName in listOf("jvmMain", "jsMain", "linuxX64Main")) { + val report = reports.singleOrNull { + it.sourceSetName == leafSourceSetName && it.scope == "implementation" && it.groupAndModule == "kt52216:lib" + } + assertNotNull(report, "No transformation for $leafSourceSetName implementation") + assert(report.allVisibleSourceSets.isEmpty()) { + "All visible source sets for leaf platform source set should always be empty, but found: ${ + report.allVisibleSourceSets.joinToString(prefix = "[", postfix = "]", separator = "; ") + }" + } + assert(report.newVisibleSourceSets.isEmpty()) { + "New visible source sets for leaf platform source set should always be empty, but found: ${ + report.newVisibleSourceSets.joinToString(prefix = "[", postfix = "]", separator = "; ") + }" + } + } + } + + testDependencyTransformations("p2") { reports -> + val commonReport = reports.singleOrNull { + it.sourceSetName == "commonMain" && it.scope == "implementation" && it.groupAndModule == "kt52216:lib" + } + assertNotNull(commonReport, "No transformation for commonMain implementation") + assert(commonReport.allVisibleSourceSets.singleOrNull() == "commonMain") { + "All visible source sets of commonMain don't include library's commonMain" + } + assert(commonReport.newVisibleSourceSets.singleOrNull() == "commonMain") { + "New visible source sets of commonMain don't include library's commonMain" + } + + for (targetName in listOf("jvm", "js", "linuxX64")) { + val intermediateReport = reports.singleOrNull { + it.sourceSetName == "${targetName}Intermediate" && it.scope == "implementation" && it.groupAndModule == "kt52216:lib" + } + val leafReport = reports.singleOrNull { + it.sourceSetName == "${targetName}Main" && it.scope == "implementation" && it.groupAndModule == "kt52216:lib" + } + + assertNotNull(intermediateReport, "No transformation for ${targetName}Intermediate implementation") + assertNotNull(leafReport, "No transformation for ${targetName}Main implementation") + + assert(intermediateReport.allVisibleSourceSets.singleOrNull() == "commonMain") { + "Intermediate transformation should contain commonMain in all visible source sets, but it doesn't for target: $targetName" + } + assert(leafReport.allVisibleSourceSets.isEmpty()) { + "All visible source sets for leaf platform source set should should be empty, but for target $targetName found: ${ + leafReport.allVisibleSourceSets.joinToString(prefix = "[", postfix = "]", separator = "; ") + }" + } + assert(intermediateReport.newVisibleSourceSets.isEmpty()) { + "New visible source sets for intermediate source set should should be empty, but for target $targetName found: ${ + leafReport.newVisibleSourceSets.joinToString(prefix = "[", postfix = "]", separator = "; ") + }" + } + assert(leafReport.newVisibleSourceSets.isEmpty()) { + "New visible source sets for leaf platform source set should always be empty, but for target $targetName found: ${ + leafReport.newVisibleSourceSets.joinToString(prefix = "[", postfix = "]", separator = "; ") + }" + } + } + } + } + } + private fun TestProject.testDependencyTransformations( subproject: String? = null, check: BuildResult.(reports: Iterable) -> Unit diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-52216/lib/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-52216/lib/build.gradle.kts new file mode 100644 index 00000000000..ce56085ea69 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-52216/lib/build.gradle.kts @@ -0,0 +1,25 @@ +plugins { + kotlin("multiplatform") + `maven-publish` +} + +group = "kt52216" +version = "1.0" + +repositories { + mavenLocal() + mavenCentral() +} + +kotlin { + jvm() + js() + linuxX64() +} + + +publishing { + repositories { + maven("$rootDir/../repo") + } +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-52216/lib/src/commonMain/kotlin/pkg/lib-common.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-52216/lib/src/commonMain/kotlin/pkg/lib-common.kt new file mode 100644 index 00000000000..f9cafef3507 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-52216/lib/src/commonMain/kotlin/pkg/lib-common.kt @@ -0,0 +1,3 @@ +package pkg + +expect class Lib() : Exception diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-52216/lib/src/jsMain/kotlin/pkg/lib-js.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-52216/lib/src/jsMain/kotlin/pkg/lib-js.kt new file mode 100644 index 00000000000..a016ddc1a13 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-52216/lib/src/jsMain/kotlin/pkg/lib-js.kt @@ -0,0 +1,3 @@ +package pkg + +actual class Lib : Exception() diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-52216/lib/src/jvmMain/kotlin/pkg/lib-jvm.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-52216/lib/src/jvmMain/kotlin/pkg/lib-jvm.kt new file mode 100644 index 00000000000..a016ddc1a13 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-52216/lib/src/jvmMain/kotlin/pkg/lib-jvm.kt @@ -0,0 +1,3 @@ +package pkg + +actual class Lib : Exception() diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-52216/lib/src/linuxX64Main/kotlin/pkg/lib-linux.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-52216/lib/src/linuxX64Main/kotlin/pkg/lib-linux.kt new file mode 100644 index 00000000000..a016ddc1a13 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-52216/lib/src/linuxX64Main/kotlin/pkg/lib-linux.kt @@ -0,0 +1,3 @@ +package pkg + +actual class Lib : Exception() diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-52216/p1/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-52216/p1/build.gradle.kts new file mode 100644 index 00000000000..a0438832bf6 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-52216/p1/build.gradle.kts @@ -0,0 +1,36 @@ +plugins { + kotlin("multiplatform") +} + +group = "me.user" +version = "1.0" + +repositories { + maven("$rootDir/../repo") + mavenLocal() + mavenCentral() +} + +kotlin { + val targets = listOf( + jvm(), + js(), + linuxX64() + ) + + sourceSets { + val commonMain by getting + + for (target in targets) { + getByName(target.leafSourceSetName) { + dependsOn(commonMain) + dependencies { + implementation("kt52216:lib:1.0") + } + } + } + } +} + +val org.jetbrains.kotlin.gradle.plugin.KotlinTarget.leafSourceSetName: String + get() = "${name}Main" diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-52216/p2/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-52216/p2/build.gradle.kts new file mode 100644 index 00000000000..66331a0b121 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-52216/p2/build.gradle.kts @@ -0,0 +1,42 @@ +plugins { + kotlin("multiplatform") +} + +group = "me.user" +version = "1.0" + +repositories { + maven("$rootDir/../repo") + mavenLocal() + mavenCentral() +} + +kotlin { + val targets = listOf( + jvm(), + js(), + linuxX64() + ) + + sourceSets { + val commonMain by getting { + dependencies { + implementation("kt52216:lib:1.0") + } + } + + for (target in targets) { + val leafSourceSet = getByName(target.leafSourceSetName) + create(target.intermediateSourceSetName) { + leafSourceSet.dependsOn(this) + dependsOn(commonMain) + } + } + } +} + +val org.jetbrains.kotlin.gradle.plugin.KotlinTarget.leafSourceSetName: String + get() = "${name}Main" + +val org.jetbrains.kotlin.gradle.plugin.KotlinTarget.intermediateSourceSetName: String + get() = "${name}Intermediate" diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-52216/settings.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-52216/settings.gradle.kts new file mode 100644 index 00000000000..c5f484f13ce --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kt-52216/settings.gradle.kts @@ -0,0 +1,5 @@ +rootProject.name = "kt-52216" + +include(":lib") +include(":p1") +include(":p2") diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GranularMetadataTransformation.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GranularMetadataTransformation.kt index 3e3d40e293c..d32ce21f7ed 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GranularMetadataTransformation.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GranularMetadataTransformation.kt @@ -13,6 +13,8 @@ import org.gradle.api.artifacts.result.ResolvedComponentResult import org.gradle.api.artifacts.result.ResolvedDependencyResult import org.gradle.api.attributes.Attribute import org.gradle.api.file.FileCollection +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension +import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets.MetadataProvider.Companion.asMetadataProvider import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope @@ -34,7 +36,7 @@ internal sealed class MetadataDependencyResolution( override fun toString(): String { val verb = when (this) { is KeepOriginalDependency -> "keep" - is ExcludeAsUnrequested -> "exclude" + is Exclude -> "exclude" is ChooseVisibleSourceSets -> "choose" } return "$verb, dependency = $dependency" @@ -45,10 +47,27 @@ internal sealed class MetadataDependencyResolution( projectDependency: Project? ) : MetadataDependencyResolution(dependency, projectDependency) - class ExcludeAsUnrequested( + sealed class Exclude( dependency: ResolvedComponentResult, projectDependency: Project? - ) : MetadataDependencyResolution(dependency, projectDependency) + ) : MetadataDependencyResolution(dependency, projectDependency) { + + class Unrequested( + dependency: ResolvedComponentResult, + projectDependency: Project? + ) : Exclude(dependency, projectDependency) + + /** + * Resolution for metadata dependencies of leaf platform source sets. + * They are excluded since platform source sets should receive + * platform dependencies from corresponding compilations and should not get metadata ones. + * See KT-52216 + */ + class PublishedPlatformSourceSetDependency( + dependency: ResolvedComponentResult, + val visibleTransitiveDependencies: Set, + ) : Exclude(dependency, null) + } class ChooseVisibleSourceSets internal constructor( dependency: ResolvedComponentResult, @@ -157,7 +176,8 @@ internal class GranularMetadataTransformation( is MetadataDependencyResolution.KeepOriginalDependency -> resolvedDependency.dependencies.filterIsInstance() is MetadataDependencyResolution.ChooseVisibleSourceSets -> dependencyResult.visibleTransitiveDependencies - is MetadataDependencyResolution.ExcludeAsUnrequested -> error("a visited dependency is erroneously considered unrequested") + is MetadataDependencyResolution.Exclude.PublishedPlatformSourceSetDependency -> dependencyResult.visibleTransitiveDependencies + is MetadataDependencyResolution.Exclude.Unrequested -> error("a visited dependency is erroneously considered unrequested") } resolvedDependencyQueue.addAll( @@ -170,7 +190,7 @@ internal class GranularMetadataTransformation( if (resolvedDependency.selected !in visitedDependencies) { // val files = resolvedDependency.moduleArtifacts.map { it.file } result.add( - MetadataDependencyResolution.ExcludeAsUnrequested( + MetadataDependencyResolution.Exclude.Unrequested( resolvedDependency.selected, (resolvedDependency.selected.id as? ProjectComponentIdentifier) ?.takeIf { it.build.isCurrentBuild() } @@ -243,6 +263,9 @@ internal class GranularMetadataTransformation( .filterIsInstance() .filterTo(mutableSetOf()) { ModuleIds.fromComponent(project, it.selected) in requestedTransitiveDependencies } + if (kotlinSourceSet in project.multiplatformExtension.platformCompilationSourceSets && resolvedToProject == null) + return MetadataDependencyResolution.Exclude.PublishedPlatformSourceSetDependency(module, transitiveDependenciesToVisit) + val visibleSourceSetsExcludingDependsOn = allVisibleSourceSets.filterTo(mutableSetOf()) { it !in sourceSetsVisibleInParents } val metadataProvider = when (mppDependencyMetadataExtractor) { @@ -345,3 +368,9 @@ internal fun requestedDependencies( val otherContributingSourceSets = dependsOnClosureWithInterCompilationDependencies(project, sourceSet) return listOf(sourceSet, *otherContributingSourceSets.toTypedArray()).flatMap(::collectScopedDependenciesFromSourceSet) } + +private val KotlinMultiplatformExtension.platformCompilationSourceSets: Set + get() = targets.filterNot { it is KotlinMetadataTarget } + .flatMap { target -> target.compilations } + .flatMap { it.kotlinSourceSetsIncludingDefault } + .toSet() diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/GradleKpmFragmentGranularMetadataResolver.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/GradleKpmFragmentGranularMetadataResolver.kt index 84b23c1c889..961617f2441 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/GradleKpmFragmentGranularMetadataResolver.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/GradleKpmFragmentGranularMetadataResolver.kt @@ -134,7 +134,7 @@ internal class GradleKpmFragmentGranularMetadataResolver( // FIXME this code is based on whole components; use module IDs with classifiers instead val resultSourceComponents = results.mapTo(mutableSetOf()) { it.dependency } resolvedComponentsByModuleId.values.minus(resultSourceComponents).forEach { - results.add(MetadataDependencyResolution.ExcludeAsUnrequested(it, it.toProjectOrNull(project))) + results.add(MetadataDependencyResolution.Exclude.Unrequested(it, it.toProjectOrNull(project))) } return results diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/DefaultKotlinSourceSet.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/DefaultKotlinSourceSet.kt index b129a65196a..174061392e7 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/DefaultKotlinSourceSet.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/sources/DefaultKotlinSourceSet.kt @@ -174,9 +174,12 @@ class DefaultKotlinSourceSet( val (group, name) = groupAndName val projectPath = resolution.projectDependency?.path when (resolution) { + // No metadata transformation leads to original dependency being used during import is MetadataDependencyResolution.KeepOriginalDependency -> null - is MetadataDependencyResolution.ExcludeAsUnrequested -> + // We should pass empty transformation for excluded dependencies. + // No transformation at all will result in a composite metadata jar being used as a dependency. + is MetadataDependencyResolution.Exclude -> MetadataDependencyTransformation(group, name, projectPath, null, emptySet(), emptyMap()) is MetadataDependencyResolution.ChooseVisibleSourceSets -> { diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/metadata/KotlinMetadataTargetConfigurator.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/metadata/KotlinMetadataTargetConfigurator.kt index 9539f89913e..d9bbc6bc513 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/metadata/KotlinMetadataTargetConfigurator.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/metadata/KotlinMetadataTargetConfigurator.kt @@ -409,7 +409,7 @@ class KotlinMetadataTargetConfigurator : // Run this action immediately before the configuration first takes part in dependency resolution: configuration.withDependencies { val (unrequested, requested) = metadataDependencyResolutions - .partition { it is MetadataDependencyResolution.ExcludeAsUnrequested } + .partition { it is MetadataDependencyResolution.Exclude } unrequested.forEach { val (group, name) = it.projectDependency?.run { @@ -528,7 +528,7 @@ internal fun createMetadataDependencyTransformationClasspath( val artifactView = fromFiles.incoming.artifactView { view -> view.componentFilter { id -> allResolutionsByComponentId[id].let { resolutions -> - resolutions == null || resolutions.any { it !is MetadataDependencyResolution.ExcludeAsUnrequested } + resolutions == null || resolutions.any { it !is MetadataDependencyResolution.Exclude } } } }