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 7105018d7a1..6718525672f 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 @@ -479,10 +479,10 @@ open class CommonizerIT : KGPBaseTest() { @GradleTest fun testCommonizationWithTwoCInteropCommonizerGroups(gradleVersion: GradleVersion) { nativeProject("commonize-kt-57796-twoCInteropCommonizerGroups", gradleVersion) { - build(":app:commonizeCIntero") { - assertTasksExecuted(":lib:transformCommonMainCInteropDependenciesMetadata") + build(":app:commonizeCInterop") { + assertTasksNotExecuted(":lib:transformCommonMainCInteropDependenciesMetadata") assertTasksExecuted(":lib:commonizeCInterop") - assertTasksExecuted(":app:transformCommonMainCInteropDependenciesMetadata") + assertTasksNotExecuted(":app:transformCommonMainCInteropDependenciesMetadata") assertTasksExecuted(":app:commonizeCInterop") } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt index 838f02f2aff..26992613fbb 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt @@ -28,6 +28,8 @@ import org.jetbrains.kotlin.gradle.targets.js.KotlinWasmTargetType import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTargetPreset import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinWasmTargetPreset import org.jetbrains.kotlin.gradle.targets.native.createFatFrameworks +import org.jetbrains.kotlin.gradle.targets.native.internal.commonizeCInteropTask +import org.jetbrains.kotlin.gradle.targets.native.internal.createCommonizedCInteropApiElementsKlibArtifact import org.jetbrains.kotlin.gradle.targets.native.tasks.artifact.registerKotlinArtifactsExtension import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompileTool import org.jetbrains.kotlin.gradle.utils.checkGradleCompatibility @@ -73,7 +75,13 @@ class KotlinMultiplatformPlugin : Plugin { // Ensure that the instance is created and configured during apply project.kotlinIdeMultiplatformImport project.locateOrRegisterIdeResolveDependenciesTask() - + // FIXME: This code creates consumable configurations for Commonized CInterops + // FIXME: Unfortunately, it also creates [commonizeCInteropTask] eagerly + // FIXME: Decouple [allInteropGroups] from task internals and then create configuration without task materialization. + project.launch { + val task = project.commonizeCInteropTask()?.get() ?: return@launch + project.createCommonizedCInteropApiElementsKlibArtifact(task) + } project.addBuildListenerForXcode() project.whenEvaluated { kotlinMultiplatformExtension.createFatFrameworks() } } 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 2457894fe7c..7eb97426bb4 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 @@ -137,6 +137,7 @@ internal abstract class CInteropCommonizerTask val sourceSetsByGroup = multiplatformExtension.sourceSets.groupBy { sourceSet -> CInteropCommonizerDependent.from(sourceSet)?.let { findInteropsGroup(it) } } + allInteropGroups.await().associateWith { group -> (group.targets + group.targets.allLeaves()).map { target -> val externalDependencyFiles: List = when (target) { @@ -157,7 +158,19 @@ internal abstract class CInteropCommonizerTask will provide the same dependencies (since cinterops are just based upon KonanTarget) */ .take(1) - .map { sourceSet -> project.createCInteropMetadataDependencyClasspath(sourceSet) } + .map { sourceSet -> + val configuration = project.locateOrCreateCommonizedCInteropDependencyConfiguration(sourceSet) + if (configuration != null) { + configuration.incoming + // Lenient is required since not every dependency exposes commonized CInterop KLibs + // Only Projects with CInterops would expose their commonization results. + // See: [createCommonizedCInteropApiElementsKlibArtifact] + .artifactView { it.isLenient = true } + .files + } else { + project.files() + } + } } } @@ -226,6 +239,14 @@ internal abstract class CInteropCommonizerTask ?.flatMap { (target, dependencies) -> dependencies.files .filter { file -> file.exists() && (file.isDirectory || file.extension == "klib") } + .flatMap flatMap2@{ dir -> + // FIXME: dir can be either a klib dir or parent dir of few klib dirs + // FIXME: Find a way to expose exact klib-dirs in consumable configuration + if (!dir.isDirectory) return@flatMap2 listOf(dir) + val subDirs = dir.listFiles().orEmpty().toList() + fun List.isKlibDir() = any { it.isDirectory && it.name == "default" } + if (subDirs.isKlibDir()) return@flatMap2 listOf(dir) else subDirs + } .map { file -> TargetedCommonizerDependency(target, file) } } ?.toSet() diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropConfigurations.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropConfigurations.kt index 7d5fcd6e2f1..c638fd1f57c 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropConfigurations.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropConfigurations.kt @@ -9,17 +9,21 @@ import org.gradle.api.Project import org.gradle.api.artifacts.Configuration import org.gradle.api.attributes.* import org.gradle.api.tasks.TaskProvider +import org.jetbrains.kotlin.commonizer.CommonizerOutputFileLayout +import org.jetbrains.kotlin.commonizer.SharedCommonizerTarget +import org.jetbrains.kotlin.gradle.plugin.* import org.jetbrains.kotlin.gradle.plugin.KotlinNativeTargetConfigurator.NativeArtifactFormat import org.jetbrains.kotlin.gradle.plugin.KotlinPluginLifecycle.Stage.AfterFinaliseDsl -import org.jetbrains.kotlin.gradle.plugin.KotlinTarget import org.jetbrains.kotlin.gradle.plugin.categoryByName import org.jetbrains.kotlin.gradle.plugin.internal.artifactTypeAttribute import org.jetbrains.kotlin.gradle.plugin.launchInStage import org.jetbrains.kotlin.gradle.plugin.mpp.* import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.copyAttributes -import org.jetbrains.kotlin.gradle.plugin.usesPlatformOf +import org.jetbrains.kotlin.gradle.plugin.sources.internal import org.jetbrains.kotlin.gradle.targets.native.internal.CInteropKlibLibraryElements.cinteropKlibLibraryElements import org.jetbrains.kotlin.gradle.tasks.CInteropProcess +import org.jetbrains.kotlin.gradle.utils.markConsumable +import org.jetbrains.kotlin.gradle.utils.markResolvable internal fun createCInteropApiElementsKlibArtifact( target: KotlinNativeTarget, @@ -37,6 +41,23 @@ internal fun createCInteropApiElementsKlibArtifact( } } +internal suspend fun Project.createCommonizedCInteropApiElementsKlibArtifact( + interopTask: CInteropCommonizerTask +) { + for (commonizerGroup in interopTask.allInteropGroups.await()) { + for (sharedCommonizerTargets in commonizerGroup.targets) { + val configuration = locateOrCreateCommonizedCInteropApiElementsConfiguration(sharedCommonizerTargets) + val artifactPath = CommonizerOutputFileLayout.resolveCommonizedDirectory(interopTask.outputDirectory(commonizerGroup), sharedCommonizerTargets) + project.artifacts.add(configuration.name, artifactPath) { artifact -> + artifact.extension = "klib" + artifact.type = "klib" + artifact.classifier = "cinterop-" + sharedCommonizerTargets.dashedIdentityString() + artifact.builtBy(interopTask) + } + } + } +} + internal fun Project.locateOrCreateCInteropDependencyConfiguration( compilation: KotlinNativeCompilation, ): Configuration { @@ -48,8 +69,7 @@ internal fun Project.locateOrCreateCInteropDependencyConfiguration( return configurations.create(compilation.cInteropDependencyConfigurationName).apply { extendsFrom(compileOnlyConfiguration, implementationConfiguration) isVisible = false - isCanBeResolved = true - isCanBeConsumed = false + markResolvable() /* Deferring attributes to wait for compilation.attributes to be configured by user*/ launchInStage(AfterFinaliseDsl) { @@ -63,9 +83,46 @@ internal fun Project.locateOrCreateCInteropDependencyConfiguration( } } +private fun Configuration.applyAttributesForCommonizerTarget(commonizerTarget: SharedCommonizerTarget) { + attributes.attribute(KotlinPlatformType.attribute, KotlinPlatformType.native) + // TODO: [KotlinNativeTarget.konanTargetAttribute] is a public attribute. It should be better to introduce dedicated private attribute + // TODO: for commonized target + attributes.attribute(KotlinNativeTarget.konanTargetAttribute, commonizerTarget.dashedIdentityString()) +} + +internal suspend fun Project.locateOrCreateCommonizedCInteropDependencyConfiguration( + sourceSet: KotlinSourceSet, +): Configuration? { + val commonizerTarget = sourceSet.commonizerTarget.await() ?: return null + if (commonizerTarget !is SharedCommonizerTarget) return null + + configurations.findByName(commonizerTarget.commonizedCInteropDependencyConfigurationName)?.let { return it } + + val configuration = configurations.create(commonizerTarget.commonizedCInteropDependencyConfigurationName).apply { + isVisible = false + markResolvable() + + /* Deferring attributes to wait for compilation.attributes to be configured by user*/ + launchInStage(AfterFinaliseDsl) { + // Extends from Metadata Configuration associated with given source set to ensure matching + extendsFrom(sourceSet.internal.resolvableMetadataConfiguration) + applyAttributesForCommonizerTarget(commonizerTarget) + attributes.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, cinteropKlibLibraryElements()) + attributes.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage::class.java, KotlinUsages.KOTLIN_CINTEROP)) + attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.categoryByName(Category.LIBRARY)) + description = "Commonized CInterop dependencies for targets: '$commonizerTarget'." + } + } + + return configuration +} + internal val KotlinNativeCompilation.cInteropDependencyConfigurationName: String get() = compilation.disambiguateName("CInterop") +internal val SharedCommonizerTarget.commonizedCInteropDependencyConfigurationName: String + get() = dashedIdentityString() + "CInterop" + internal fun Project.locateOrCreateCInteropApiElementsConfiguration(target: KotlinTarget): Configuration { val configurationName = cInteropApiElementsConfigurationName(target) configurations.findByName(configurationName)?.let { return it } @@ -86,10 +143,37 @@ internal fun Project.locateOrCreateCInteropApiElementsConfiguration(target: Kotl } } +internal fun Project.locateOrCreateCommonizedCInteropApiElementsConfiguration(commonizerTarget: SharedCommonizerTarget): Configuration { + val configurationName = commonizedCInteropApiElementsConfigurationName(commonizerTarget) + configurations.findByName(configurationName)?.let { return it } + + return configurations.create(configurationName).apply { + markConsumable() + + /* Deferring attributes to wait for target.attributes to be configured by user */ + launchInStage(AfterFinaliseDsl) { + applyAttributesForCommonizerTarget(commonizerTarget) + + attributes.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, cinteropKlibLibraryElements()) + attributes.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage::class.java, KotlinUsages.KOTLIN_CINTEROP)) + attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.categoryByName(Category.LIBRARY)) + attributes.attribute(artifactTypeAttribute, NativeArtifactFormat.KLIB) + } + } +} + private fun cInteropApiElementsConfigurationName(target: KotlinTarget): String { return target.name + "CInteropApiElements" } +/** + * Same as [org.jetbrains.kotlin.commonizer.identityString] but target segments separated with dash + */ +private fun SharedCommonizerTarget.dashedIdentityString() = targets.map { it.name }.sorted().joinToString("-") +private fun commonizedCInteropApiElementsConfigurationName(commonizerTarget: SharedCommonizerTarget): String { + return commonizerTarget.dashedIdentityString() + "CInteropApiElements" +} + internal object CInteropKlibLibraryElements { const val CINTEROP_KLIB = "cinterop-klib"