Fix transitive deps on MPP with host-specific source sets (KT-41083)

A consumer could not resolve the host-specific metadata artifact
of the transitive dependency because the transitive dependency's parent
in the platform dependencies graph did not include the dependency on it
in the variant that is chosen for host-specific source sets metadata:
the dependencies of that variant were empty.

Export the dependencies of the Kotlin/Native target in the host-specific
metadata variant as well.

Issue #KT-41083 Fixed
This commit is contained in:
Sergey Igushkin
2020-08-27 14:37:28 +04:00
committed by Sergey Igushkin
parent 9097d0918c
commit 5c88eb722d
3 changed files with 98 additions and 35 deletions
@@ -40,6 +40,67 @@ class KlibBasedMppIT : BaseGradleIT() {
publishProjectDepAndAddDependency(validateHostSpecificPublication = false)
}
@Test
fun testHostSpecificSourceSetsInTransitiveDependencies() = with(Project("common-klib-lib-and-app")) {
// KT-41083
// Publish a lib with host specific source sets depending on another lib with host-specific source sets
setupWorkingDir()
val projectDepName = "dependency"
val publishedGroup = "published"
val producerProjectName = "producer"
embedProject(this, renameTo = projectDepName)
projectDir.resolve("$projectDepName/src").walkTopDown().filter { it.extension == "kt" }.forEach { ktFile ->
// Avoid FQN duplicates between producer & consumer
ktFile.modify { it.replace("package com.h0tk3y.hmpp.klib.demo", "package com.h0tk3y.hmpp.klib.lib") }
}
gradleBuildScript(projectDepName).appendText(
"""
${"\n"}
group = "$publishedGroup"
""".trimIndent()
)
gradleBuildScript().modify {
transformBuildScriptWithPluginsDsl(it) +
"""
${"\n"}
dependencies { "commonMainImplementation"(project(":$projectDepName")) }
group = "$publishedGroup"
""".trimIndent()
}
gradleSettingsScript().appendText("\nrootProject.name = \"$producerProjectName\"")
build("publish") {
assertSuccessful()
}
// Then consume the published project. To do that, rename the modules so that Gradle chooses the published ones given the original
// Maven coordinates and doesn't resolve them as project dependencies.
val localGroup = "local"
gradleBuildScript(projectDepName).appendText("""${"\n"}group = "$localGroup"""")
gradleBuildScript().appendText(
"""
${"\n"}
repositories { maven("${'$'}rootDir/repo") }
dependencies { "commonMainImplementation"("$publishedGroup:$producerProjectName:1.0") }
group = "$localGroup"
""".trimIndent()
)
// The consumer should correctly receive the klibs of the host-specific source sets
checkTaskCompileClasspath(
"compile${hostSpecificSourceSet.capitalize()}KotlinMetadata",
listOf(
"published-producer-metadata-$hostSpecificSourceSet.klib",
"published-producer-metadata-commonMain.klib",
"published-dependency-metadata-$hostSpecificSourceSet.klib",
"published-dependency-metadata-commonMain.klib"
)
)
}
@Test
fun testBuildWithPublishedDependency() = testBuildWithDependency {
publishProjectDepAndAddDependency(validateHostSpecificPublication = true)
@@ -21,8 +21,6 @@ class KotlinSpecificDependenciesIT : BaseGradleIT() {
override fun defaultBuildOptions(): BuildOptions =
super.defaultBuildOptions().copy(androidGradlePluginVersion = AGPVersion.v3_6_0, androidHome = KotlinTestUtils.findAndroidSdk())
private var testBuildRunId = 0
private fun Project.prepare() { // call this when reusing a project after a test, too, in order to remove any added dependencies
setupWorkingDir()
gradleSettingsScript().takeIf { it.exists() }?.modify(::transformBuildScriptWithPluginsDsl)
@@ -286,42 +284,44 @@ class KotlinSpecificDependenciesIT : BaseGradleIT() {
val expression = """configurations["$configurationName"].toList()"""
checkPrintedItems(subproject, expression, checkModulesInResolutionResult, checkModulesNotInResolutionResult)
}
}
private fun Project.checkTaskCompileClasspath(
taskPath: String,
checkModulesInClasspath: List<String> = emptyList(),
checkModulesNotInClasspath: List<String> = emptyList()
) {
val subproject = taskPath.substringBeforeLast(":").takeIf { it.isNotEmpty() && it != taskPath }
val taskName = taskPath.removePrefix(subproject.orEmpty())
val expression = """(tasks.getByName("$taskName") as AbstractCompile).classpath.toList()"""
checkPrintedItems(subproject, expression, checkModulesInClasspath, checkModulesNotInClasspath)
}
private var testBuildRunId = 0
private fun Project.checkPrintedItems(
subproject: String?,
itemsExpression: String,
checkAnyItemsContains: List<String>,
checkNoItemContains: List<String>
) {
setupWorkingDir()
val printingTaskName = "printItems${testBuildRunId++}"
gradleBuildScript(subproject).appendText(
"""
${'\n'}
tasks.create("$printingTaskName") {
doLast {
println("###$printingTaskName" + $itemsExpression)
}
fun BaseGradleIT.Project.checkTaskCompileClasspath(
taskPath: String,
checkModulesInClasspath: List<String> = emptyList(),
checkModulesNotInClasspath: List<String> = emptyList()
) {
val subproject = taskPath.substringBeforeLast(":").takeIf { it.isNotEmpty() && it != taskPath }
val taskName = taskPath.removePrefix(subproject.orEmpty())
val expression = """(tasks.getByName("$taskName") as AbstractCompile).classpath.toList()"""
checkPrintedItems(subproject, expression, checkModulesInClasspath, checkModulesNotInClasspath)
}
private fun BaseGradleIT.Project.checkPrintedItems(
subproject: String?,
itemsExpression: String,
checkAnyItemsContains: List<String>,
checkNoItemContains: List<String>
) = with(testCase) {
setupWorkingDir()
val printingTaskName = "printItems${testBuildRunId++}"
gradleBuildScript(subproject).appendText(
"""
${'\n'}
tasks.create("$printingTaskName") {
doLast {
println("###$printingTaskName" + $itemsExpression)
}
""".trimIndent()
)
build("${subproject?.prependIndent(":").orEmpty()}:$printingTaskName") {
assertSuccessful()
val itemsLine = output.lines().single { "###$printingTaskName" in it }.substringAfter(printingTaskName)
val items = itemsLine.removeSurrounding("[", "]").split(", ").toSet()
checkAnyItemsContains.forEach { pattern -> assertTrue { items.any { pattern in it } } }
checkNoItemContains.forEach { pattern -> assertFalse { items.any { pattern in it } } }
}
""".trimIndent()
)
build("${subproject?.prependIndent(":").orEmpty()}:$printingTaskName") {
assertSuccessful()
val itemsLine = output.lines().single { "###$printingTaskName" in it }.substringAfter(printingTaskName)
val items = itemsLine.removeSurrounding("[", "]").split(", ").toSet()
checkAnyItemsContains.forEach { pattern -> assertTrue { items.any { pattern in it } } }
checkNoItemContains.forEach { pattern -> assertFalse { items.any { pattern in it } } }
}
}
@@ -88,6 +88,8 @@ open class KotlinNativeTarget @Inject constructor(
project.artifacts.add(configuration.name, hostSpecificMetadataJar) { artifact ->
artifact.classifier = "metadata"
}
configuration.extendsFrom(*configurations.getByName(apiElementsConfigurationName).extendsFrom.toTypedArray())
}
val metadataAttributes =