[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 ->
|
val thirdPartyLibApiVisibility = reports.filter { report ->
|
||||||
report.groupAndModule.startsWith("com.example.thirdparty:third-party-lib") && report.scope == "api"
|
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 {
|
thirdPartyLibApiVisibility.forEach {
|
||||||
if (it.sourceSetName in jvmJsSourceSets)
|
if (it.sourceSetName in jvmJsSourceSets)
|
||||||
assertTrue("$it") { it.allVisibleSourceSets == setOf("commonMain") }
|
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(
|
private fun TestProject.testDependencyTransformations(
|
||||||
subproject: String? = null,
|
subproject: String? = null,
|
||||||
check: BuildResult.(reports: Iterable<DependencyTransformationReport>) -> Unit
|
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.artifacts.result.ResolvedDependencyResult
|
||||||
import org.gradle.api.attributes.Attribute
|
import org.gradle.api.attributes.Attribute
|
||||||
import org.gradle.api.file.FileCollection
|
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.KotlinSourceSet
|
||||||
import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets.MetadataProvider.Companion.asMetadataProvider
|
import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets.MetadataProvider.Companion.asMetadataProvider
|
||||||
import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope
|
import org.jetbrains.kotlin.gradle.plugin.sources.KotlinDependencyScope
|
||||||
@@ -34,7 +36,7 @@ internal sealed class MetadataDependencyResolution(
|
|||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
val verb = when (this) {
|
val verb = when (this) {
|
||||||
is KeepOriginalDependency -> "keep"
|
is KeepOriginalDependency -> "keep"
|
||||||
is ExcludeAsUnrequested -> "exclude"
|
is Exclude -> "exclude"
|
||||||
is ChooseVisibleSourceSets -> "choose"
|
is ChooseVisibleSourceSets -> "choose"
|
||||||
}
|
}
|
||||||
return "$verb, dependency = $dependency"
|
return "$verb, dependency = $dependency"
|
||||||
@@ -45,10 +47,27 @@ internal sealed class MetadataDependencyResolution(
|
|||||||
projectDependency: Project?
|
projectDependency: Project?
|
||||||
) : MetadataDependencyResolution(dependency, projectDependency)
|
) : MetadataDependencyResolution(dependency, projectDependency)
|
||||||
|
|
||||||
class ExcludeAsUnrequested(
|
sealed class Exclude(
|
||||||
dependency: ResolvedComponentResult,
|
dependency: ResolvedComponentResult,
|
||||||
projectDependency: Project?
|
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(
|
class ChooseVisibleSourceSets internal constructor(
|
||||||
dependency: ResolvedComponentResult,
|
dependency: ResolvedComponentResult,
|
||||||
@@ -157,7 +176,8 @@ internal class GranularMetadataTransformation(
|
|||||||
is MetadataDependencyResolution.KeepOriginalDependency ->
|
is MetadataDependencyResolution.KeepOriginalDependency ->
|
||||||
resolvedDependency.dependencies.filterIsInstance<ResolvedDependencyResult>()
|
resolvedDependency.dependencies.filterIsInstance<ResolvedDependencyResult>()
|
||||||
is MetadataDependencyResolution.ChooseVisibleSourceSets -> dependencyResult.visibleTransitiveDependencies
|
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(
|
resolvedDependencyQueue.addAll(
|
||||||
@@ -170,7 +190,7 @@ internal class GranularMetadataTransformation(
|
|||||||
if (resolvedDependency.selected !in visitedDependencies) {
|
if (resolvedDependency.selected !in visitedDependencies) {
|
||||||
// val files = resolvedDependency.moduleArtifacts.map { it.file }
|
// val files = resolvedDependency.moduleArtifacts.map { it.file }
|
||||||
result.add(
|
result.add(
|
||||||
MetadataDependencyResolution.ExcludeAsUnrequested(
|
MetadataDependencyResolution.Exclude.Unrequested(
|
||||||
resolvedDependency.selected,
|
resolvedDependency.selected,
|
||||||
(resolvedDependency.selected.id as? ProjectComponentIdentifier)
|
(resolvedDependency.selected.id as? ProjectComponentIdentifier)
|
||||||
?.takeIf { it.build.isCurrentBuild() }
|
?.takeIf { it.build.isCurrentBuild() }
|
||||||
@@ -243,6 +263,9 @@ internal class GranularMetadataTransformation(
|
|||||||
.filterIsInstance<ResolvedDependencyResult>()
|
.filterIsInstance<ResolvedDependencyResult>()
|
||||||
.filterTo(mutableSetOf()) { ModuleIds.fromComponent(project, it.selected) in requestedTransitiveDependencies }
|
.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 visibleSourceSetsExcludingDependsOn = allVisibleSourceSets.filterTo(mutableSetOf()) { it !in sourceSetsVisibleInParents }
|
||||||
|
|
||||||
val metadataProvider = when (mppDependencyMetadataExtractor) {
|
val metadataProvider = when (mppDependencyMetadataExtractor) {
|
||||||
@@ -345,3 +368,9 @@ internal fun requestedDependencies(
|
|||||||
val otherContributingSourceSets = dependsOnClosureWithInterCompilationDependencies(project, sourceSet)
|
val otherContributingSourceSets = dependsOnClosureWithInterCompilationDependencies(project, sourceSet)
|
||||||
return listOf(sourceSet, *otherContributingSourceSets.toTypedArray()).flatMap(::collectScopedDependenciesFromSourceSet)
|
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
|
// FIXME this code is based on whole components; use module IDs with classifiers instead
|
||||||
val resultSourceComponents = results.mapTo(mutableSetOf()) { it.dependency }
|
val resultSourceComponents = results.mapTo(mutableSetOf()) { it.dependency }
|
||||||
resolvedComponentsByModuleId.values.minus(resultSourceComponents).forEach {
|
resolvedComponentsByModuleId.values.minus(resultSourceComponents).forEach {
|
||||||
results.add(MetadataDependencyResolution.ExcludeAsUnrequested(it, it.toProjectOrNull(project)))
|
results.add(MetadataDependencyResolution.Exclude.Unrequested(it, it.toProjectOrNull(project)))
|
||||||
}
|
}
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|||||||
+4
-1
@@ -174,9 +174,12 @@ class DefaultKotlinSourceSet(
|
|||||||
val (group, name) = groupAndName
|
val (group, name) = groupAndName
|
||||||
val projectPath = resolution.projectDependency?.path
|
val projectPath = resolution.projectDependency?.path
|
||||||
when (resolution) {
|
when (resolution) {
|
||||||
|
// No metadata transformation leads to original dependency being used during import
|
||||||
is MetadataDependencyResolution.KeepOriginalDependency -> null
|
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())
|
MetadataDependencyTransformation(group, name, projectPath, null, emptySet(), emptyMap())
|
||||||
|
|
||||||
is MetadataDependencyResolution.ChooseVisibleSourceSets -> {
|
is MetadataDependencyResolution.ChooseVisibleSourceSets -> {
|
||||||
|
|||||||
+2
-2
@@ -409,7 +409,7 @@ class KotlinMetadataTargetConfigurator :
|
|||||||
// Run this action immediately before the configuration first takes part in dependency resolution:
|
// Run this action immediately before the configuration first takes part in dependency resolution:
|
||||||
configuration.withDependencies {
|
configuration.withDependencies {
|
||||||
val (unrequested, requested) = metadataDependencyResolutions
|
val (unrequested, requested) = metadataDependencyResolutions
|
||||||
.partition { it is MetadataDependencyResolution.ExcludeAsUnrequested }
|
.partition { it is MetadataDependencyResolution.Exclude }
|
||||||
|
|
||||||
unrequested.forEach {
|
unrequested.forEach {
|
||||||
val (group, name) = it.projectDependency?.run {
|
val (group, name) = it.projectDependency?.run {
|
||||||
@@ -528,7 +528,7 @@ internal fun createMetadataDependencyTransformationClasspath(
|
|||||||
val artifactView = fromFiles.incoming.artifactView { view ->
|
val artifactView = fromFiles.incoming.artifactView { view ->
|
||||||
view.componentFilter { id ->
|
view.componentFilter { id ->
|
||||||
allResolutionsByComponentId[id].let { resolutions ->
|
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