[MPP] Fix dependencies of a platform compilation source set
Leave empty new visible source sets in a metadata transformation for a platform (leaf) source set. Motivation: * Platform source sets should get their deps from the compilation * Metadata dependencies in platform source set shouldn't be used as transitive library dependencies won't be correct in this case KT-52216
This commit is contained in:
committed by
Space
parent
b472ccd358
commit
4d023e873d
+71
-1
@@ -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<DependencyTransformationReport>) -> Unit
|
||||
|
||||
+25
@@ -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")
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package pkg
|
||||
|
||||
expect class Lib() : Exception
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package pkg
|
||||
|
||||
actual class Lib : Exception()
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package pkg
|
||||
|
||||
actual class Lib : Exception()
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package pkg
|
||||
|
||||
actual class Lib : Exception()
|
||||
+36
@@ -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"
|
||||
+42
@@ -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"
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
rootProject.name = "kt-52216"
|
||||
|
||||
include(":lib")
|
||||
include(":p1")
|
||||
include(":p2")
|
||||
+34
-5
@@ -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<ResolvedDependencyResult>,
|
||||
) : Exclude(dependency, null)
|
||||
}
|
||||
|
||||
class ChooseVisibleSourceSets internal constructor(
|
||||
dependency: ResolvedComponentResult,
|
||||
@@ -157,7 +176,8 @@ internal class GranularMetadataTransformation(
|
||||
is MetadataDependencyResolution.KeepOriginalDependency ->
|
||||
resolvedDependency.dependencies.filterIsInstance<ResolvedDependencyResult>()
|
||||
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<ResolvedDependencyResult>()
|
||||
.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<KotlinSourceSet>
|
||||
get() = targets.filterNot { it is KotlinMetadataTarget }
|
||||
.flatMap { target -> target.compilations }
|
||||
.flatMap { it.kotlinSourceSetsIncludingDefault }
|
||||
.toSet()
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+4
-1
@@ -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 -> {
|
||||
|
||||
+2
-2
@@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user