diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KlibBasedMppIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KlibBasedMppIT.kt index 41f613a1264..9c7ef7539bf 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KlibBasedMppIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KlibBasedMppIT.kt @@ -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) diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinSpecificDependenciesIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinSpecificDependenciesIT.kt index a205fcc75d6..d8081040590 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinSpecificDependenciesIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/KotlinSpecificDependenciesIT.kt @@ -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 = emptyList(), - checkModulesNotInClasspath: List = 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, - checkNoItemContains: List - ) { - setupWorkingDir() - val printingTaskName = "printItems${testBuildRunId++}" - gradleBuildScript(subproject).appendText( - """ - ${'\n'} - tasks.create("$printingTaskName") { - doLast { - println("###$printingTaskName" + $itemsExpression) - } +fun BaseGradleIT.Project.checkTaskCompileClasspath( + taskPath: String, + checkModulesInClasspath: List = emptyList(), + checkModulesNotInClasspath: List = 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, + checkNoItemContains: List +) = 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 } } } } } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTarget.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTarget.kt index c0718fda756..869d96ac3bf 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTarget.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/native/KotlinNativeTarget.kt @@ -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 =