diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/CommonizerIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/CommonizerIT.kt index b0c8fe192e1..83c05e1809e 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/CommonizerIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/CommonizerIT.kt @@ -700,6 +700,20 @@ class CommonizerIT : BaseGradleIT() { } } + @Test + fun `test KT-51517 commonization with transitive cinterop`() { + with(Project("commonize-kt-51517-transitive-cinterop")) { + build(":app:assemble") { + assertSuccessful() + assertTasksExecuted(":lib:transformCommonMainCInteropDependenciesMetadata") + assertTasksExecuted(":lib:commonizeCInterop") + assertTasksExecuted(":lib:compileCommonMainKotlinMetadata") + assertTasksExecuted(":app:commonizeCInterop") + assertTasksExecuted(":app:compileNativeMainKotlinMetadata") + } + } + } + private fun preparedProject(name: String): Project { return Project(name).apply { setupWorkingDir() diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/commonize-kt-51517-transitive-cinterop/app/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/commonize-kt-51517-transitive-cinterop/app/build.gradle.kts new file mode 100644 index 00000000000..8f85695f835 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/commonize-kt-51517-transitive-cinterop/app/build.gradle.kts @@ -0,0 +1,50 @@ +plugins { + kotlin("multiplatform") +} + +kotlin { + jvm() + listOf(linuxX64(), linuxArm64(), mingwX64()).forEach { + it.compilations.getByName("main") { + cinterops.create("yummy") { + val nativeLibs = rootDir.resolve("native-libs") + defFile = nativeLibs.resolve("yummy.def") + compilerOpts += "-I" + nativeLibs.absolutePath + } + } + } + + sourceSets { + val commonMain by getting + val linuxX64Main by getting + val linuxArm64Main by getting + val mingwX64Main by getting + + val linuxMain by creating { + linuxX64Main.dependsOn(this) + linuxArm64Main.dependsOn(this) + } + val nativeMain by creating { + this.dependsOn(commonMain) + linuxMain.dependsOn(this) + mingwX64Main.dependsOn(this) + dependencies { + implementation(project(":lib")) + } + } + val commonTest by getting + val linuxX64Test by getting + val linuxArm64Test by getting + val mingwX64Test by getting + + val linuxTest by creating { + linuxX64Test.dependsOn(this) + linuxArm64Test.dependsOn(this) + } + val nativeTest by creating { + this.dependsOn(commonTest) + linuxTest.dependsOn(this) + mingwX64Test.dependsOn(this) + } + } +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/commonize-kt-51517-transitive-cinterop/app/src/nativeMain/kotlin/main.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/commonize-kt-51517-transitive-cinterop/app/src/nativeMain/kotlin/main.kt new file mode 100644 index 00000000000..db0506227cb --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/commonize-kt-51517-transitive-cinterop/app/src/nativeMain/kotlin/main.kt @@ -0,0 +1,16 @@ +import dummy.LENGTH +import dummy.Y +import dummy.foo +import kotlinx.cinterop.cValue +import yummy.sel +import yummy.yummy + +fun nativeMain() { + val y = cValue { + n = 42 + } + yummy(y) + foo() + sel(LENGTH) + dummyMain() +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/commonize-kt-51517-transitive-cinterop/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/commonize-kt-51517-transitive-cinterop/build.gradle.kts new file mode 100644 index 00000000000..f556befce79 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/commonize-kt-51517-transitive-cinterop/build.gradle.kts @@ -0,0 +1,10 @@ +plugins { + kotlin("multiplatform").apply(false) +} + +allprojects { + repositories { + mavenLocal() + mavenCentral() + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/commonize-kt-51517-transitive-cinterop/gradle.properties b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/commonize-kt-51517-transitive-cinterop/gradle.properties new file mode 100644 index 00000000000..2db016ae4e4 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/commonize-kt-51517-transitive-cinterop/gradle.properties @@ -0,0 +1 @@ +kotlin.mpp.enableCInteropCommonization=true \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/commonize-kt-51517-transitive-cinterop/lib/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/commonize-kt-51517-transitive-cinterop/lib/build.gradle.kts new file mode 100644 index 00000000000..6af48fd796f --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/commonize-kt-51517-transitive-cinterop/lib/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + kotlin("multiplatform") +} + +kotlin { + listOf(linuxX64(), linuxArm64(), mingwX64()).forEach { + it.compilations.getByName("main") { + cinterops.create("dummy") { + val nativeLibs = rootDir.resolve("native-libs") + defFile = nativeLibs.resolve("dummy.def") + compilerOpts += "-I" + nativeLibs.absolutePath + } + } + } +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/commonize-kt-51517-transitive-cinterop/lib/src/commonMain/kotlin/A.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/commonize-kt-51517-transitive-cinterop/lib/src/commonMain/kotlin/A.kt new file mode 100644 index 00000000000..211ff908c52 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/commonize-kt-51517-transitive-cinterop/lib/src/commonMain/kotlin/A.kt @@ -0,0 +1,5 @@ +import dummy.foo + +fun dummyMain() { + foo() +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/commonize-kt-51517-transitive-cinterop/native-libs/dummy.def b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/commonize-kt-51517-transitive-cinterop/native-libs/dummy.def new file mode 100644 index 00000000000..940db708b0a --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/commonize-kt-51517-transitive-cinterop/native-libs/dummy.def @@ -0,0 +1 @@ +headers = dummy.h diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/commonize-kt-51517-transitive-cinterop/native-libs/dummy.h b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/commonize-kt-51517-transitive-cinterop/native-libs/dummy.h new file mode 100644 index 00000000000..0784b9bf258 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/commonize-kt-51517-transitive-cinterop/native-libs/dummy.h @@ -0,0 +1,3 @@ +#define LENGTH 10 +struct Y { int n; }; +int foo(); \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/commonize-kt-51517-transitive-cinterop/native-libs/yummy.def b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/commonize-kt-51517-transitive-cinterop/native-libs/yummy.def new file mode 100644 index 00000000000..6cde5c8595d --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/commonize-kt-51517-transitive-cinterop/native-libs/yummy.def @@ -0,0 +1 @@ +headers = yummy.h diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/commonize-kt-51517-transitive-cinterop/native-libs/yummy.h b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/commonize-kt-51517-transitive-cinterop/native-libs/yummy.h new file mode 100644 index 00000000000..01a10caf747 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/commonize-kt-51517-transitive-cinterop/native-libs/yummy.h @@ -0,0 +1,4 @@ +#include "dummy.h" + +void yummy(struct Y * ee); +void sel(int i); diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/commonize-kt-51517-transitive-cinterop/settings.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/commonize-kt-51517-transitive-cinterop/settings.gradle.kts new file mode 100644 index 00000000000..87b1985caaf --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/commonize-kt-51517-transitive-cinterop/settings.gradle.kts @@ -0,0 +1,2 @@ +include(":app") +include(":lib") \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropCommonizerTask.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropCommonizerTask.kt index 56d942c20b7..7f552fbb7c6 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropCommonizerTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropCommonizerTask.kt @@ -6,6 +6,7 @@ package org.jetbrains.kotlin.gradle.targets.native.internal import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.FileCollection import org.gradle.api.file.ProjectLayout import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.ListProperty @@ -13,21 +14,21 @@ import org.gradle.api.provider.Property import org.gradle.api.provider.Provider import org.gradle.api.tasks.* import org.gradle.process.ExecOperations -import org.jetbrains.kotlin.commonizer.CommonizerDependency -import org.jetbrains.kotlin.commonizer.TargetedCommonizerDependency -import org.jetbrains.kotlin.commonizer.allLeaves -import org.jetbrains.kotlin.compilerRunner.* +import org.jetbrains.kotlin.commonizer.* import org.jetbrains.kotlin.compilerRunner.GradleCliCommonizer import org.jetbrains.kotlin.compilerRunner.KotlinNativeCommonizerToolRunner +import org.jetbrains.kotlin.compilerRunner.KotlinToolRunner import org.jetbrains.kotlin.compilerRunner.konanHome import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinSharedNativeCompilation +import org.jetbrains.kotlin.gradle.plugin.sources.DefaultKotlinSourceSet import org.jetbrains.kotlin.gradle.plugin.sources.withDependsOnClosure import org.jetbrains.kotlin.gradle.targets.native.internal.CInteropCommonizerTask.CInteropGist import org.jetbrains.kotlin.gradle.tasks.CInteropProcess import org.jetbrains.kotlin.gradle.utils.chainedFinalizeValueOnRead +import org.jetbrains.kotlin.gradle.utils.filesProvider import org.jetbrains.kotlin.gradle.utils.listProperty import org.jetbrains.kotlin.gradle.utils.property import org.jetbrains.kotlin.konan.target.KonanTarget @@ -48,7 +49,10 @@ internal open class CInteropCommonizerTask sourceSets: Provider>, @get:Classpath - val libraryFile: Provider + val libraryFile: Provider, + + @get:Classpath + val dependencies: FileCollection ) { @Suppress("unused") // Used for UP-TO-DATE check @get:Input @@ -65,6 +69,7 @@ internal open class CInteropCommonizerTask if (identifier != other.identifier) return false if (konanTarget != other.konanTarget) return false if (libraryFile != other.libraryFile) return false + if (dependencies != other.dependencies) return false if (allSourceSetNames != other.allSourceSetNames) return false return true @@ -75,6 +80,7 @@ internal open class CInteropCommonizerTask var result = identifier.hashCode() result = 31 * result + konanTarget.hashCode() result = 31 * result + libraryFile.hashCode() + result = 31 * result + dependencies.hashCode() result = 31 * result + allSourceSetNames.hashCode() return result } @@ -109,18 +115,58 @@ internal open class CInteropCommonizerTask private val commonizerLogLevel = project.commonizerLogLevel private val additionalCommonizerSettings = project.additionalCommonizerSettings + data class CInteropCommonizerDependencies( + val commonizerTarget: CommonizerTarget, + val dependencies: FileCollection + ) + /** * For Gradle Configuration Cache support the Group-to-Dependencies relation should be pre-cached. * It is used during execution phase. */ - private val nativeDistributionDependenciesMap: Map> by lazy { + private val groupedCommonizerDependencies: Map> by lazy { + val multiplatformExtension = project.multiplatformExtensionOrNull ?: return@lazy emptyMap() + + val sourceSetsByTarget = multiplatformExtension.sourceSets.groupBy { sourceSet -> + project.getCommonizerTarget(sourceSet) + } + val sourceSetsByGroup = multiplatformExtension.sourceSets.groupBy { sourceSet -> + CInteropCommonizerDependent.from(project, sourceSet)?.let { findInteropsGroup(it) } + } getAllInteropsGroups().associateWith { group -> - (group.targets + group.targets.allLeaves()).flatMapTo(mutableSetOf()) { target -> - project.getNativeDistributionDependencies(target).map { dependency -> TargetedCommonizerDependency(target, dependency) } + (group.targets + group.targets.allLeaves()).map { target -> + val externalDependencyFiles = when (target) { + is LeafCommonizerTarget -> { + cinterops + .filter { cinterop -> cinterop.identifier in group.interops && cinterop.konanTarget == target.konanTarget } + .map { cinterop -> cinterop.dependencies } + } + + is SharedCommonizerTarget -> { + val targetSourceSets = sourceSetsByTarget[target].orEmpty() + val groupSourceSets = sourceSetsByGroup[group].orEmpty().toSet() + targetSourceSets.intersect(groupSourceSets) + .filterIsInstance() + .map { sourceSet -> project.createCInteropMetadataDependencyClasspath(sourceSet) } + } + } + val nativeDistributionDependencyFiles = project.getNativeDistributionDependencies(target) + + CInteropCommonizerDependencies( + target, + project.files(externalDependencyFiles + nativeDistributionDependencyFiles) + ) } } } + @Suppress("unused") // Used for UP-TO-DATE check + @get:Classpath + protected val commonizerDependenciesClasspath: FileCollection + get() = project.files( + groupedCommonizerDependencies.values.flatten().map { it.dependencies } + ) + @get:Nested internal var cinterops = setOf() private set @@ -169,15 +215,21 @@ internal open class CInteropCommonizerTask konanHome = konanHome, outputTargets = group.targets, inputLibraries = cinteropsForTarget.map { it.libraryFile.get() }.filter { it.exists() }.toSet(), - dependencyLibraries = getNativeDistributionDependencies(group), + dependencyLibraries = getCInteropCommonizerGroupDependencies(group), outputDirectory = outputDirectory(group), logLevel = commonizerLogLevel, additionalSettings = additionalCommonizerSettings, ) } - private fun getNativeDistributionDependencies(group: CInteropCommonizerGroup): Set { - val dependencies = nativeDistributionDependenciesMap[group] + private fun getCInteropCommonizerGroupDependencies(group: CInteropCommonizerGroup): Set { + val dependencies = groupedCommonizerDependencies[group] + ?.flatMap { (target, dependencies) -> + dependencies.files + .filter { file -> file.exists() && (file.isDirectory || file.extension == "klib") } + .map { file -> TargetedCommonizerDependency(target, file) } + } + ?.toSet() requireNotNull(dependencies) { "Unexpected $group" } return dependencies @@ -242,6 +294,7 @@ private fun CInteropProcess.toGist(): CInteropGist { konanTarget = konanTarget, // FIXME support cinterop with PM20 sourceSets = project.provider { (settings.compilation as? KotlinCompilation<*>)?.kotlinSourceSets }, - libraryFile = outputFileProvider + libraryFile = outputFileProvider, + dependencies = libraries ) }