diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt index 386b544f5a8..677628273e0 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt @@ -25,7 +25,6 @@ import org.gradle.api.logging.Logging import org.gradle.api.model.ObjectFactory import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry import org.jetbrains.kotlin.compilerRunner.maybeCreateCommonizerClasspathConfiguration -import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi import org.jetbrains.kotlin.gradle.dsl.* import org.jetbrains.kotlin.gradle.internal.KOTLIN_BUILD_TOOLS_API_IMPL import org.jetbrains.kotlin.gradle.internal.KOTLIN_COMPILER_EMBEDDABLE @@ -54,6 +53,8 @@ import org.jetbrains.kotlin.gradle.targets.js.nodejs.UnameExecutor import org.jetbrains.kotlin.gradle.targets.js.npm.addNpmDependencyExtension import org.jetbrains.kotlin.gradle.targets.metadata.isKotlinGranularMetadataEnabled import org.jetbrains.kotlin.gradle.targets.native.internal.CInteropKlibLibraryElements +import org.jetbrains.kotlin.gradle.targets.native.internal.CInteropCommonizerArtifactTypeAttribute +import org.jetbrains.kotlin.gradle.targets.native.internal.CommonizerTargetAttribute import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompileTool import org.jetbrains.kotlin.gradle.testing.internal.KotlinTestsRegistry import org.jetbrains.kotlin.gradle.tooling.registerBuildKotlinToolingMetadataTask @@ -205,6 +206,8 @@ abstract class DefaultKotlinBasePlugin : KotlinBasePlugin { KotlinWasmTargetAttribute.setupAttributesMatchingStrategy(project.dependencies.attributesSchema) ProjectLocalConfigurations.setupAttributesMatchingStrategy(this) CInteropKlibLibraryElements.setupAttributesMatchingStrategy(this) + CommonizerTargetAttribute.setupAttributesMatchingStrategy(this) + CInteropCommonizerArtifactTypeAttribute.setupTransform(project) } open fun whenBuildEvaluated(project: Project) { diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GranularMetadataTransformation.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GranularMetadataTransformation.kt index 1883c3ab4fa..82690ff45a0 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GranularMetadataTransformation.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/GranularMetadataTransformation.kt @@ -76,9 +76,7 @@ internal sealed class MetadataDependencyResolution( abstract class ProjectMetadataProvider : MetadataProvider() { enum class MetadataConsumer { Ide, Cli } - abstract fun getSourceSetCompiledMetadata(sourceSetName: String): FileCollection? - abstract fun getSourceSetCInteropMetadata(sourceSetName: String, consumer: MetadataConsumer): FileCollection? } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/ProjectMetadataProviderImpl.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/ProjectMetadataProviderImpl.kt index e7dd4d9a123..f3e432aa1cc 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/ProjectMetadataProviderImpl.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/ProjectMetadataProviderImpl.kt @@ -27,13 +27,7 @@ internal fun ProjectMetadataProvider( internal class SourceSetMetadataOutputs( val metadata: FileCollection?, - val cinterop: CInterop?, -) { - class CInterop( - val forCli: FileCollection, - val forIde: FileCollection, - ) -} +) private class ProjectMetadataProviderImpl( private val sourceSetMetadataOutputs: Map, @@ -44,15 +38,6 @@ private class ProjectMetadataProviderImpl( return metadataOutputs.metadata } - - override fun getSourceSetCInteropMetadata(sourceSetName: String, consumer: MetadataConsumer): FileCollection? { - val metadataOutputs = sourceSetMetadataOutputs[sourceSetName] ?: error("Unexpected source set '$sourceSetName'") - val cinteropMetadata = metadataOutputs.cinterop ?: return null - return when (consumer) { - MetadataConsumer.Ide -> cinteropMetadata.forIde - MetadataConsumer.Cli -> cinteropMetadata.forCli - } - } } internal suspend fun Project.collectSourceSetMetadataOutputs(): Map { @@ -72,14 +57,9 @@ internal suspend fun Project.collectSourceSetMetadataOutputs(): Map - val cinteropMetadataOutput = sourceSetCInteropMetadata[sourceSet] - SourceSetMetadataOutputs( - metadata = metadata, - cinterop = cinteropMetadataOutput - ) + return sourceSetMetadata.mapValues { (_, metadata) -> + SourceSetMetadataOutputs(metadata = metadata,) }.mapKeys { it.key.name } } @@ -96,17 +76,4 @@ private suspend fun KotlinMultiplatformExtension.sourceSetsMetadataOutputs(): Ma } } -private suspend fun KotlinMultiplatformExtension.cInteropMetadataOfSourceSets( - sourceSets: Iterable, -): Map { - val taskForCLI = project.commonizeCInteropTask() ?: return emptyMap() - val taskForIde = project.copyCommonizeCInteropForIdeTask() ?: return emptyMap() - return sourceSets.associateWith { sourceSet -> - val dependent = CInteropCommonizerDependent.from(sourceSet) ?: return@associateWith null - SourceSetMetadataOutputs.CInterop( - forCli = taskForCLI.get().commonizedOutputLibraries(dependent), - forIde = taskForIde.get().commonizedOutputLibraries(dependent) - ) - } -} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/AbstractCInteropCommonizerTask.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/AbstractCInteropCommonizerTask.kt index 55543052cb3..0305cfda83e 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/AbstractCInteropCommonizerTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/AbstractCInteropCommonizerTask.kt @@ -23,8 +23,6 @@ import java.io.File internal abstract class AbstractCInteropCommonizerTask : DefaultTask(), UsesBuildMetricsService { @get:OutputDirectory abstract val outputDirectory: File - - internal abstract suspend fun findInteropsGroup(dependent: CInteropCommonizerDependent): CInteropCommonizerGroup? } internal fun AbstractCInteropCommonizerTask.outputDirectory(group: CInteropCommonizerGroup): File { @@ -48,7 +46,7 @@ internal fun AbstractCInteropCommonizerTask.commonizedOutputLibraries(dependent: } internal suspend fun AbstractCInteropCommonizerTask.commonizedOutputDirectory(dependent: CInteropCommonizerDependent): File? { - val group = findInteropsGroup(dependent) ?: return null + val group = project.findCInteropCommonizerGroup(dependent) ?: return null return CommonizerOutputFileLayout .resolveCommonizedDirectory(outputDirectory(group), dependent.target) } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropCommonizerArtifactTypeAttribute.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropCommonizerArtifactTypeAttribute.kt new file mode 100644 index 00000000000..d231844e43e --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropCommonizerArtifactTypeAttribute.kt @@ -0,0 +1,83 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.targets.native.internal + +import org.gradle.api.Project +import org.gradle.api.artifacts.transform.InputArtifact +import org.gradle.api.artifacts.transform.TransformAction +import org.gradle.api.artifacts.transform.TransformOutputs +import org.gradle.api.artifacts.transform.TransformParameters +import org.gradle.api.attributes.Attribute +import org.gradle.api.file.FileSystemLocation +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity +import org.gradle.work.DisableCachingByDefault +import org.jetbrains.kotlin.gradle.targets.native.internal.CInteropCommonizerArtifactTypeAttribute.KLIB +import org.jetbrains.kotlin.gradle.targets.native.internal.CInteropCommonizerArtifactTypeAttribute.KLIB_COLLECTION_DIR + +/** + * Commonized CInterop artifacts can come in two shapes: + * [KLIB]: Just a 'klib' directory or packaged file representing one commonized library + * [KLIB_COLLECTION_DIR]: A flat directory containing several klib dirs or files representing the output of a commonizer task + */ +internal object CInteropCommonizerArtifactTypeAttribute { + val attribute: Attribute = Attribute.of("org.jetbrains.kotlin.cinteropCommonizerArtifactType", String::class.java) + + /** + * 'Regular'/'Typical' klib in the form of a 'directory' or '.klib' file representing a single library + */ + const val KLIB = "klib" + + /** + * Directory containing klibs as direct children: + * e.g. output + * + * ``` + * build/some/output/ + * - foo.klib + * - bar.klib + * - ... + * ``` + * + * In this case the artifact will just be the 'output' directory that contains the collection of klibs + */ + const val KLIB_COLLECTION_DIR = "klib-collection-dir" + + /** + * Set up a transformation from artifacts of type 'collection dir' to a set of klibs. + */ + fun setupTransform(project: Project) { + project.dependencies.artifactTypes.maybeCreate(KLIB_COLLECTION_DIR).also { artifactType -> + artifactType.attributes.attribute(attribute, KLIB_COLLECTION_DIR) + } + + project.dependencies.artifactTypes.maybeCreate(KLIB).also { artifactType -> + artifactType.attributes.attribute(attribute, KLIB) + } + + project.dependencies.registerTransform(KlibCollectionDirTransform::class.java) { transform -> + transform.from.attribute(attribute, KLIB_COLLECTION_DIR) + transform.to.attribute(attribute, KLIB) + } + } + + @DisableCachingByDefault(because = "Trivial operation, building cache keys is presumably more expensive") + abstract class KlibCollectionDirTransform : TransformAction { + + @get:InputArtifact + @get:PathSensitive(PathSensitivity.RELATIVE) + abstract val inputArtifact: Provider + + override fun transform(outputs: TransformOutputs) { + val input = inputArtifact.get().asFile + input.listFiles()?.forEach { klib -> + if (klib.isDirectory) outputs.dir(klib) + if (klib.isFile) outputs.file(klib) + } + } + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropCommonizerConfigurations.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropCommonizerConfigurations.kt new file mode 100644 index 00000000000..8b63c790fed --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropCommonizerConfigurations.kt @@ -0,0 +1,86 @@ +package org.jetbrains.kotlin.gradle.targets.native.internal + +import org.gradle.api.Project +import org.gradle.api.artifacts.Configuration +import org.gradle.api.attributes.Category +import org.gradle.api.attributes.LibraryElements +import org.gradle.api.attributes.Usage +import org.gradle.api.file.FileCollection +import org.jetbrains.kotlin.commonizer.SharedCommonizerTarget +import org.jetbrains.kotlin.commonizer.identityString +import org.jetbrains.kotlin.gradle.plugin.* +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinUsages +import org.jetbrains.kotlin.gradle.plugin.mpp.resolvableMetadataConfiguration +import org.jetbrains.kotlin.gradle.plugin.sources.internal +import org.jetbrains.kotlin.gradle.targets.native.internal.CInteropKlibLibraryElements.cinteropKlibLibraryElements +import org.jetbrains.kotlin.gradle.utils.markConsumable +import org.jetbrains.kotlin.gradle.utils.markResolvable +import org.jetbrains.kotlin.tooling.core.UnsafeApi + +/* Elements configuration */ + +internal fun Project.locateOrCreateCommonizedCInteropApiElementsConfiguration(commonizerTarget: SharedCommonizerTarget): Configuration { + val configurationName = commonizerTarget.identityString + "CInteropApiElements" + configurations.findByName(configurationName)?.let { return it } + + return configurations.create(configurationName) { configuration -> + configuration.markConsumable() + setupBasicCommonizedCInteropConfigurationAttributes(configuration, commonizerTarget) + } +} + + +/* Dependency configuration */ + +/** + * Gives access the 'commonized cinterop dependency configuration' of the given [sourceSet]. + * The access is forced through this 'view' because the provided 'artifact view' is forced to be lenient as protective measure. + * + * If dependencies do not provide corresponding commonized cinterop element configurations then we should not fail the build! + */ +internal suspend fun Project.createCommonizedCInteropDependencyConfigurationView(sourceSet: KotlinSourceSet): FileCollection { + @OptIn(UnsafeApi::class) + val configuration = locateOrCreateCommonizedCInteropDependencyConfiguration(sourceSet) ?: return files() + return configuration.incoming.artifactView { view -> view.isLenient = true }.files +} + +@UnsafeApi("Use createCommonizedCInteropDependencyConfigurationView instead") +internal suspend fun Project.locateOrCreateCommonizedCInteropDependencyConfiguration( + sourceSet: KotlinSourceSet, +): Configuration? { + val commonizerTarget = sourceSet.commonizerTarget.await() ?: return null + if (commonizerTarget !is SharedCommonizerTarget) return null + + val configurationName = commonizerTarget.identityString + "CInterop" + configurations.findByName(configurationName)?.let { return it } + + val configuration = configurations.create(configurationName) { configuration -> + configuration.isVisible = false + configuration.markResolvable() + + // Extends from Metadata Configuration associated with given source set to ensure matching + configuration.extendsFrom(sourceSet.internal.resolvableMetadataConfiguration) + setupBasicCommonizedCInteropConfigurationAttributes(configuration, commonizerTarget) + + /** + * Dependencies require the [CInteropCommonizerArtifactTypeAttribute.KLIB]. + * If artifacts are added using [CInteropCommonizerArtifactTypeAttribute.KLIB_COLLECTION_DIR] then a transformation + * has to happen that will resolve the exact list of klibs. + */ + configuration.attributes.attribute(CInteropCommonizerArtifactTypeAttribute.attribute, CInteropCommonizerArtifactTypeAttribute.KLIB) + description = "Commonized CInterop dependencies for targets: '$commonizerTarget'." + } + + return configuration +} + +private fun Project.setupBasicCommonizedCInteropConfigurationAttributes( + configuration: Configuration, + commonizerTarget: SharedCommonizerTarget, +) { + configuration.attributes.attribute(KotlinPlatformType.attribute, KotlinPlatformType.native) + configuration.attributes.attribute(CommonizerTargetAttribute.attribute, commonizerTarget.identityString) + configuration.attributes.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, cinteropKlibLibraryElements()) + configuration.attributes.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage::class.java, KotlinUsages.KOTLIN_CINTEROP)) + configuration.attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.categoryByName(Category.LIBRARY)) +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropCommonizerGroup.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropCommonizerGroup.kt index 1fa78af5d37..950611993f6 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropCommonizerGroup.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropCommonizerGroup.kt @@ -5,13 +5,24 @@ package org.jetbrains.kotlin.gradle.targets.native.internal +import org.gradle.api.Project import org.gradle.api.tasks.Input import org.jetbrains.kotlin.commonizer.SharedCommonizerTarget +import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinSharedNativeCompilation +import org.jetbrains.kotlin.gradle.utils.Future import org.jetbrains.kotlin.gradle.utils.appendLine +import org.jetbrains.kotlin.gradle.utils.getOrPut +import org.jetbrains.kotlin.gradle.utils.lazyFuture +/** + * Represents a group of cinterops and targets that can be passed to a single invocation to the commonizer. + * CInterops are bundled into these groups to reduce the amount of commonizer invocations and therefore ensuring no duplicate work + * has to be done. + */ internal data class CInteropCommonizerGroup( @get:Input val targets: Set, - @get:Input val interops: Set + @get:Input val interops: Set, ) { override fun toString(): String { return buildString { @@ -28,4 +39,70 @@ internal data class CInteropCommonizerGroup( appendLine("}") } } -} \ No newline at end of file +} + +/* Collection of cinterop groups present in the given project */ + +/** + * Represents all collected [CInteropCommonizerGroup] gruops for the given project + */ +internal val Project.kotlinCInteropGroups: Future> + get() = extensions.extraProperties.getOrPut("org.jetbrains.kotlin.gradle.targets.native.internal.kotlinCInteropGroups") { + lazyFuture { collectCInteropGroups() } + } + +private suspend fun Project.collectCInteropGroups(): Set { + val dependents = allCinteropCommonizerDependents() + + val allScopeSets = dependents.map { it.scopes }.toSet() + val rootScopeSets = allScopeSets.filter { scopeSet -> + allScopeSets.none { otherScopeSet -> otherScopeSet != scopeSet && otherScopeSet.containsAll(scopeSet) } + } + + return rootScopeSets.map { scopeSet -> + val dependentsForScopes = dependents.filter { dependent -> + scopeSet.containsAll(dependent.scopes) + } + + CInteropCommonizerGroup( + targets = dependentsForScopes.map { it.target }.toSet(), + interops = dependentsForScopes.flatMap { it.interops }.toSet() + ) + }.toSet() + +} + +private suspend fun Project.allCinteropCommonizerDependents(): Set { + val multiplatformExtension = project.multiplatformExtensionOrNull ?: return emptySet() + + val fromSharedNativeCompilations = multiplatformExtension + .targets.flatMap { target -> target.compilations } + .filterIsInstance() + .mapNotNull { compilation -> CInteropCommonizerDependent.from(compilation) } + .toSet() + + val fromSourceSets = multiplatformExtension.awaitSourceSets() + .mapNotNull { sourceSet -> CInteropCommonizerDependent.from(sourceSet) } + .toSet() + + val fromSourceSetsAssociateCompilations = multiplatformExtension.awaitSourceSets() + .mapNotNull { sourceSet -> CInteropCommonizerDependent.fromAssociateCompilations(sourceSet) } + .toSet() + + return (fromSharedNativeCompilations + fromSourceSets + fromSourceSetsAssociateCompilations) +} + +/** + * Utility function that allows to find the corresponding [CInteropCommonizerGroup] for a given [CInteropCommonizerDependent] + */ +internal suspend fun Project.findCInteropCommonizerGroup(dependent: CInteropCommonizerDependent): CInteropCommonizerGroup? { + val suitableGroups = kotlinCInteropGroups.await().filter { group -> + group.interops.containsAll(dependent.interops) && group.targets.contains(dependent.target) + } + + assert(suitableGroups.size <= 1) { + "CInteropCommonizerTask: Unnecessary work detected: More than one suitable group found for cinterop dependent." + } + + return suitableGroups.firstOrNull() +} 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 59d26a0dd9c..9a21df40169 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 @@ -42,7 +42,7 @@ internal abstract class CInteropCommonizerTask @Inject constructor( private val objectFactory: ObjectFactory, private val execOperations: ExecOperations, - private val projectLayout: ProjectLayout + private val projectLayout: ProjectLayout, ) : AbstractCInteropCommonizerTask() { internal class CInteropGist( @@ -54,7 +54,7 @@ internal abstract class CInteropCommonizerTask val libraryFile: Provider, @get:Classpath - val dependencies: FileCollection + val dependencies: FileCollection, ) { @Suppress("unused") // Used for UP-TO-DATE check @get:Input @@ -91,10 +91,6 @@ internal abstract class CInteropCommonizerTask override val outputDirectory: File get() = projectLayout.buildDirectory.get().asFile.resolve("classes/kotlin/commonizer") - internal fun outputDirectoriesOfCommonizerGroup(group: CInteropCommonizerGroup): Map = group - .targets - .associateWith { target -> CommonizerOutputFileLayout.resolveCommonizedDirectory(outputDirectory, target) } - @get:Internal internal val kotlinPluginVersion: Property = objectFactory .property() @@ -123,7 +119,7 @@ internal abstract class CInteropCommonizerTask data class CInteropCommonizerDependencies( val commonizerTarget: CommonizerTarget, - val dependencies: FileCollection + val dependencies: FileCollection, ) /** @@ -135,7 +131,7 @@ internal abstract class CInteropCommonizerTask val sourceSetsByTarget = multiplatformExtension.sourceSets.groupBy { sourceSet -> sourceSet.commonizerTarget.getOrThrow() } val sourceSetsByGroup = multiplatformExtension.sourceSets.groupBy { sourceSet -> - CInteropCommonizerDependent.from(sourceSet)?.let { findInteropsGroup(it) } + CInteropCommonizerDependent.from(sourceSet)?.let { findCInteropCommonizerGroup(it) } } allInteropGroups.await().associateWith { group -> @@ -158,19 +154,7 @@ internal abstract class CInteropCommonizerTask will provide the same dependencies (since cinterops are just based upon KonanTarget) */ .take(1) - .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() - } - } + .map { sourceSet -> project.createCommonizedCInteropDependencyConfigurationView(sourceSet) } } } @@ -214,7 +198,8 @@ internal abstract class CInteropCommonizerTask private fun commonize(group: CInteropCommonizerGroup) { val cinteropsForTarget = cinterops.get().filter { cinterop -> cinterop.identifier in group.interops } - outputDirectory(group).deleteRecursively() + val outputDirectory = outputDirectory(group) + outputDirectory.deleteRecursively() if (cinteropsForTarget.isEmpty()) return val commonizerRunner = KotlinNativeCommonizerToolRunner( @@ -228,7 +213,7 @@ internal abstract class CInteropCommonizerTask outputTargets = group.targets, inputLibraries = cinteropsForTarget.map { it.libraryFile.get() }.filter { it.exists() }.toSet(), dependencyLibraries = getCInteropCommonizerGroupDependencies(group), - outputDirectory = outputDirectory(group), + outputDirectory = outputDirectory, logLevel = commonizerLogLevel, additionalSettings = additionalCommonizerSettings, ) @@ -239,14 +224,6 @@ 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() @@ -256,23 +233,11 @@ internal abstract class CInteropCommonizerTask } @get:Internal - internal val allInteropGroups: CompletableFuture> = CompletableFuture() + internal val allInteropGroups: Future> = project.kotlinCInteropGroups - @get:Nested + @Nested @Suppress("unused") // UP-TO-DATE check - protected val allInteropGroupsOrThrow get() = allInteropGroups.getOrThrow() - - override suspend fun findInteropsGroup(dependent: CInteropCommonizerDependent): CInteropCommonizerGroup? { - val suitableGroups = allInteropGroups.await().filter { group -> - group.interops.containsAll(dependent.interops) && group.targets.contains(dependent.target) - } - - assert(suitableGroups.size <= 1) { - "CInteropCommonizerTask: Unnecessary work detected: More than one suitable group found for cinterop dependent." - } - - return suitableGroups.firstOrNull() - } + protected fun getAllInteropGroupsForUpToDateCheck() = allInteropGroups.getOrThrow() } private fun CInteropProcess.toGist(): CInteropGist { 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 9debe525057..2858ea52994 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,17 @@ 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.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.sources.internal +import org.jetbrains.kotlin.gradle.plugin.usesPlatformOf 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( @@ -63,39 +63,9 @@ internal fun Project.locateOrCreateCInteropDependencyConfiguration( } } -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 } @@ -116,42 +86,10 @@ 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 - */ -internal fun SharedCommonizerTarget.dashedIdentityString() = targets.map { it.name }.sorted().joinToString("-") -private fun commonizedCInteropApiElementsConfigurationName(commonizerTarget: SharedCommonizerTarget): String { - return commonizerTarget.dashedIdentityString() + "CInteropApiElements" -} - -private fun Configuration.applyAttributesForCommonizerTarget(commonizerTarget: SharedCommonizerTarget) { - attributes.attribute(KotlinPlatformType.attribute, KotlinPlatformType.native) - CommonizerTargetAttribute.apply(attributes, commonizerTarget) -} - internal object CInteropKlibLibraryElements { const val CINTEROP_KLIB = "cinterop-klib" @@ -173,13 +111,3 @@ private class CInteropLibraryElementsCompatibilityRule : AttributeCompatibilityR } } } - -private object CommonizerTargetAttribute { - private val attribute = Attribute.of("org.jetbrains.kotlin.native.commonizerTarget", String::class.java) - - private val SharedCommonizerTarget.attributeValue get() = dashedIdentityString() - - fun apply(attributeContainer: AttributeContainer, sharedCommonizerTarget: SharedCommonizerTarget) { - attributeContainer.attribute(attribute, sharedCommonizerTarget.attributeValue) - } -} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropMetadataDependencyClasspath.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropMetadataDependencyClasspath.kt index f942303fd1b..f17ca980f6e 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropMetadataDependencyClasspath.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropMetadataDependencyClasspath.kt @@ -8,13 +8,7 @@ package org.jetbrains.kotlin.gradle.targets.native.internal import org.gradle.api.Project import org.gradle.api.file.FileCollection import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets -import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets.MetadataProvider.ArtifactMetadataProvider -import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets.MetadataProvider.ProjectMetadataProvider -import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets.MetadataProvider.ProjectMetadataProvider.MetadataConsumer.Cli -import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets.MetadataProvider.ProjectMetadataProvider.MetadataConsumer.Ide -import org.jetbrains.kotlin.gradle.plugin.mpp.metadataDependencyResolutionsOrEmpty import org.jetbrains.kotlin.gradle.plugin.sources.DefaultKotlinSourceSet -import org.jetbrains.kotlin.gradle.plugin.sources.metadataTransformation import org.jetbrains.kotlin.gradle.utils.filesProvider import org.jetbrains.kotlin.gradle.utils.future import java.io.File @@ -37,54 +31,15 @@ internal suspend fun Project.createCInteropMetadataDependencyClasspath(sourceSet if (dependencyTransformationTask == null) return project.files() val dependencyTransformationTaskOutputs = project.files(dependencyTransformationTask.map { it.outputLibraryFiles }) - return if (forIde) { - /* - For IDE Import the classpath will be assembled by three independent parts: - 1) C-Interop Metadata which will be downloaded Jar files that get transformed by the transformation task - 2) C-Interop Metadata directly provided by dependency projects (in the same build) - 3) C-Interop Metadata from 'associated compilations' / additionalVisible source sets - (e.g. 'nativeTest' will be able to access the classpath from 'nativeMain') - */ - dependencyTransformationTaskOutputs + - createCInteropMetadataDependencyClasspathFromProjectDependenciesForIde(sourceSet) + - createCInteropMetadataDependencyClasspathFromAssociatedCompilations(sourceSet, true) - } else { - /* - For CLI execution the classpath will be assembled from two parts: - 1) C-Interop metadata from the transformation task which transforms - Project Dependencies and External Module Dependencies (e.g. the ones that downloaded from maven repo) - 2) C-Interop Metadata from 'associated compilations' / additionalVisible source sets - (e.g. 'nativeTest' will be able to access the classpath from 'nativeMain') - */ - dependencyTransformationTaskOutputs + - createCInteropMetadataDependencyClasspathFromAssociatedCompilations(sourceSet, false) - } -} -private fun Project.createCInteropMetadataDependencyClasspathFromProjectDependenciesForIde( - sourceSet: DefaultKotlinSourceSet -): FileCollection { - return filesProvider { - sourceSet.metadataTransformation - .metadataDependencyResolutionsOrEmpty - .filterIsInstance() - .mapNotNull { chooseVisibleSourceSets -> - /* We only want to access resolutions that provide metadata from dependency projects */ - val projectMetadataProvider = when (chooseVisibleSourceSets.metadataProvider) { - is ProjectMetadataProvider -> chooseVisibleSourceSets.metadataProvider - is ArtifactMetadataProvider -> return@mapNotNull null - } - - chooseVisibleSourceSets.visibleSourceSetProvidingCInterops?.let { visibleSourceSetName -> - projectMetadataProvider.getSourceSetCInteropMetadata(visibleSourceSetName, Ide) - } - } - } + return dependencyTransformationTaskOutputs + + createCInteropMetadataDependencyClasspathFromAssociatedCompilations(sourceSet, forIde) + + createCommonizedCInteropDependencyConfigurationView(sourceSet) } private fun Project.createCInteropMetadataDependencyClasspathFromAssociatedCompilations( sourceSet: DefaultKotlinSourceSet, - forIde: Boolean + forIde: Boolean, ): FileCollection { return filesProvider files@{ val commonizerTarget = sourceSet.sharedCommonizerTarget.getOrThrow() ?: return@files emptySet() @@ -96,7 +51,7 @@ private fun Project.createCInteropMetadataDependencyClasspathFromAssociatedCompi */ val (associatedSourceSet, _) = sourceSet.getAdditionalVisibleSourceSets() .filterIsInstance() - .mapNotNull { otherSourceSet -> otherSourceSet to (otherSourceSet.sharedCommonizerTarget.getOrThrow() ?: return@mapNotNull null) } + .mapNotNull { other -> other to (other.sharedCommonizerTarget.getOrThrow() ?: return@mapNotNull null) } .filter { (_, otherCommonizerTarget) -> otherCommonizerTarget.targets.containsAll(commonizerTarget.targets) } .minByOrNull { (_, otherCommonizerTarget) -> otherCommonizerTarget.targets.size } ?: return@files emptySet() diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropMetadataDependencyTransformationTask.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropMetadataDependencyTransformationTask.kt index 390b51c224c..a429680ef15 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropMetadataDependencyTransformationTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CInteropMetadataDependencyTransformationTask.kt @@ -55,8 +55,6 @@ internal suspend fun Project.locateOrRegisterCInteropMetadataDependencyTransform project.layout.kotlinTransformedCInteropMetadataLibraryDirectoryForBuild(sourceSet.name), /* cleaning = */ CInteropMetadataDependencyTransformationTask.Cleaning.DeleteOutputDirectory, - /* transformProjectDependencies = */ - true, ), configureTask = { configureTaskOrder() } ) @@ -84,8 +82,6 @@ internal suspend fun Project.locateOrRegisterCInteropMetadataDependencyTransform project.kotlinTransformedCInteropMetadataLibraryDirectoryForIde, /* cleaning = */ CInteropMetadataDependencyTransformationTask.Cleaning.None, - /* transformProjectDependencies = */ - false, // For IDE Project Dependencies will be transformed during configuration, see [createCInteropMetadataDependencyClasspath] ), configureTask = { configureTaskOrder() } ) @@ -114,9 +110,6 @@ internal open class CInteropMetadataDependencyTransformationTask @Inject constru @Transient @get:Internal val sourceSet: DefaultKotlinSourceSet, @get:OutputDirectory val outputDirectory: File, @get:Internal val cleaning: Cleaning, - /** when false, project-to-project dependencies will not be transformed and listed in [outputLibraryFiles], - * assuming they are added during gradle configuration, see [createCInteropMetadataDependencyClasspath] for details */ - private val transformProjectDependencies: Boolean, objectFactory: ObjectFactory, ) : DefaultTask() { @@ -139,7 +132,7 @@ internal open class CInteropMetadataDependencyTransformationTask @Inject constru } @get:Nested - internal val inputs = MetadataDependencyTransformationTaskInputs(project, sourceSet, transformProjectDependencies) + internal val inputs = MetadataDependencyTransformationTaskInputs(project, sourceSet, keepProjectDependencies = false) @get:OutputFile protected val outputLibrariesFileIndex: RegularFileProperty = objectFactory @@ -171,16 +164,9 @@ internal open class CInteropMetadataDependencyTransformationTask @Inject constru private fun materializeMetadata( chooseVisibleSourceSets: ChooseVisibleSourceSets, ): Iterable { - val metadataProvider = chooseVisibleSourceSets.metadataProvider - return when (metadataProvider) { - is ProjectMetadataProvider -> { - if (!transformProjectDependencies) return emptyList() - val visibleSourceSetName = chooseVisibleSourceSets.visibleSourceSetProvidingCInterops ?: return emptyList() - metadataProvider - .getSourceSetCInteropMetadata(visibleSourceSetName, ProjectMetadataProvider.MetadataConsumer.Cli) - ?.files - .orEmpty() - } + return when (val metadataProvider = chooseVisibleSourceSets.metadataProvider) { + /* Project to Project commonized cinterops are shared using configurations */ + is ProjectMetadataProvider -> emptyList() /* Extract/Materialize all cinterop files from composite jar file */ is ArtifactMetadataProvider -> metadataProvider.read { artifactContent -> @@ -195,9 +181,7 @@ internal open class CInteropMetadataDependencyTransformationTask @Inject constru private fun Iterable.resolutionsToTransform(): List { return filterIsInstance() - .applyIf(!transformProjectDependencies) { - filterNot { it.dependency in currentBuild } - } + .filterNot { it.dependency in currentBuild } } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CommonizerTargetAttribute.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CommonizerTargetAttribute.kt new file mode 100644 index 00000000000..130f3e6e8ff --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CommonizerTargetAttribute.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.targets.native.internal + +import org.gradle.api.attributes.* +import org.jetbrains.kotlin.commonizer.SharedCommonizerTarget +import org.jetbrains.kotlin.commonizer.parseCommonizerTarget +import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution + +internal object CommonizerTargetAttribute { + val attribute: Attribute = Attribute.of("org.jetbrains.kotlin.native.commonizerTarget", String::class.java) + + fun setupAttributesMatchingStrategy(attributesSchema: AttributesSchema) { + attributesSchema.attribute(attribute) { strategy -> + strategy.compatibilityRules.add(CommonizerTargetCompatibilityRule::class.java) + strategy.disambiguationRules.add(CommonizerTargetDisambiguationRule::class.java) + } + } + + /** + * Generally all producers providing a superset of targets can be consumed! + * e.g. a producer providing a commonizer target for ios and linux targets can be considered compatible + * for consumers of just the ios targets + */ + class CommonizerTargetCompatibilityRule : AttributeCompatibilityRule { + override fun execute(details: CompatibilityCheckDetails) { + val producerValue = parseCommonizerTarget(details.producerValue ?: return) as? SharedCommonizerTarget ?: return + val consumerValue = parseCommonizerTarget(details.consumerValue ?: return) as? SharedCommonizerTarget ?: return + + if (producerValue.targets.containsAll(consumerValue.targets)) { + details.compatible() + } + } + } + + /** + * Given the above [CommonizerTargetCompatibilityRule] we see that it is expected that every 'more common' cinterops + * will be marked as compatible. However, in the commonizer output model we always just want to use a single + * artifact to compile/analyze against. In the case of cinterops we always want the most specific cinterop + * See [MetadataDependencyResolution.ChooseVisibleSourceSets.visibleSourceSetProvidingCInterops] + * + * e.g. + * - given the consumer requests 'ios + linux' + * - given the producer offers 'ios + macos + linux' and 'ios + macos + linux + windows' + * + * then: the more specific 'ios + macos + linux' (closer) shall be chosen + */ + class CommonizerTargetDisambiguationRule : AttributeDisambiguationRule { + override fun execute(details: MultipleCandidatesDetails) { + details.closestMatch(details.candidateValues.sorted().minByOrNull { it.length } ?: return) + } + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CopyCInteropCommonizerTaskOutputForIdeTask.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CopyCInteropCommonizerTaskOutputForIdeTask.kt index 6a052c68313..23db342742c 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CopyCInteropCommonizerTaskOutputForIdeTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/CopyCInteropCommonizerTaskOutputForIdeTask.kt @@ -35,10 +35,6 @@ internal abstract class CopyCommonizeCInteropForIdeTask @Inject constructor( override val outputDirectory: File = project.rootDir.resolve(".gradle/kotlin/commonizer") .resolve(project.path.removePrefix(":").replace(":", "/")) - override suspend fun findInteropsGroup(dependent: CInteropCommonizerDependent): CInteropCommonizerGroup? { - return commonizeCInteropTask.get().findInteropsGroup(dependent) - } - @get:Internal val metrics: Property> = project.objects .property(GradleBuildMetricsReporter()) diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/configureCInteropCommonizer.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/configureCInteropCommonizer.kt index e50409d2ad3..7e8ffe9efd4 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/configureCInteropCommonizer.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/native/internal/configureCInteropCommonizer.kt @@ -7,18 +7,11 @@ package org.jetbrains.kotlin.gradle.targets.native.internal import org.gradle.api.Project import org.gradle.api.tasks.TaskProvider -import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull -import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinSharedNativeCompilation +import org.jetbrains.kotlin.commonizer.CommonizerOutputFileLayout internal suspend fun Project.configureCInteropCommonizer() { val interopTask = commonizeCInteropTask() ?: return - - val cinteropGroups = getCInteropGroups() - configureCInteropCommonizerConsumableConfigurations(cinteropGroups, interopTask) - - interopTask.configure { - it.allInteropGroups.complete(cinteropGroups) - } + configureCInteropCommonizerConsumableConfigurations(kotlinCInteropGroups.await(), interopTask) } private fun Project.configureCInteropCommonizerConsumableConfigurations( @@ -29,57 +22,14 @@ private fun Project.configureCInteropCommonizerConsumableConfigurations( for (sharedCommonizerTargets in commonizerGroup.targets) { val configuration = locateOrCreateCommonizedCInteropApiElementsConfiguration(sharedCommonizerTargets) val commonizerTargetOutputDir = interopTask.map { task -> - task.outputDirectoriesOfCommonizerGroup(commonizerGroup)[sharedCommonizerTargets] - ?: error("Can't find output of Shared Commonizer Target $sharedCommonizerTargets") + CommonizerOutputFileLayout.resolveCommonizedDirectory(task.outputDirectory(commonizerGroup), sharedCommonizerTargets) } + project.artifacts.add(configuration.name, commonizerTargetOutputDir) { artifact -> - artifact.extension = "klib" - artifact.type = "klib" - artifact.classifier = "cinterop-" + sharedCommonizerTargets.dashedIdentityString() + artifact.extension = CInteropCommonizerArtifactTypeAttribute.KLIB_COLLECTION_DIR + artifact.type = CInteropCommonizerArtifactTypeAttribute.KLIB_COLLECTION_DIR artifact.builtBy(interopTask) } } } } - -private suspend fun Project.allCinteropCommonizerDependents(): Set { - val multiplatformExtension = project.multiplatformExtensionOrNull ?: return emptySet() - - val fromSharedNativeCompilations = multiplatformExtension - .targets.flatMap { target -> target.compilations } - .filterIsInstance() - .mapNotNull { compilation -> CInteropCommonizerDependent.from(compilation) } - .toSet() - - val fromSourceSets = multiplatformExtension.awaitSourceSets() - .mapNotNull { sourceSet -> CInteropCommonizerDependent.from(sourceSet) } - .toSet() - - val fromSourceSetsAssociateCompilations = multiplatformExtension.awaitSourceSets() - .mapNotNull { sourceSet -> CInteropCommonizerDependent.fromAssociateCompilations(sourceSet) } - .toSet() - - return (fromSharedNativeCompilations + fromSourceSets + fromSourceSetsAssociateCompilations) -} - -private suspend fun Project.getCInteropGroups(): Set { - val dependents = allCinteropCommonizerDependents() - - val allScopeSets = dependents.map { it.scopes }.toSet() - val rootScopeSets = allScopeSets.filter { scopeSet -> - allScopeSets.none { otherScopeSet -> otherScopeSet != scopeSet && otherScopeSet.containsAll(scopeSet) } - } - - val result = rootScopeSets.map { scopeSet -> - val dependentsForScopes = dependents.filter { dependent -> - scopeSet.containsAll(dependent.scopes) - } - - CInteropCommonizerGroup( - targets = dependentsForScopes.map { it.target }.toSet(), - interops = dependentsForScopes.flatMap { it.interops }.toSet() - ) - }.toSet() - - return result -} diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/CInteropCommonizerArtifactTypeAttributeTest.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/CInteropCommonizerArtifactTypeAttributeTest.kt new file mode 100644 index 00000000000..74113a699bb --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/CInteropCommonizerArtifactTypeAttributeTest.kt @@ -0,0 +1,60 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +@file:Suppress("FunctionName") + +package org.jetbrains.kotlin.gradle.unitTests + +import org.gradle.api.attributes.Usage +import org.gradle.kotlin.dsl.dependencies +import org.jetbrains.kotlin.gradle.targets.native.internal.CInteropCommonizerArtifactTypeAttribute +import org.jetbrains.kotlin.gradle.util.buildProjectWithMPP +import org.jetbrains.kotlin.gradle.utils.markConsumable +import org.jetbrains.kotlin.gradle.utils.markResolvable +import org.jetbrains.kotlin.gradle.utils.named +import kotlin.test.Test +import kotlin.test.assertEquals + +class CInteropCommonizerArtifactTypeAttributeTest { + + @Test + fun `test - transformation from klib-collection-dir to klibs`() { + val project = buildProjectWithMPP() + + /* Create stub klibs collection directory */ + val klibCollectionDir = project.buildDir.resolve("testOutputDir") + klibCollectionDir.mkdirs() + + val klibs = listOf( + klibCollectionDir.resolve("foo.klib").also { file -> file.writeText("stub") }, + klibCollectionDir.resolve("bar").also { file -> file.mkdirs() } + ) + + /* Create consumable elements configuration */ + project.configurations.create("testElements") { configuration -> + configuration.markConsumable() + configuration.attributes.attribute(Usage.USAGE_ATTRIBUTE, project.objects.named("test")) + + /* Add klibCollectionDir as artifact */ + configuration.outgoing.artifact(klibCollectionDir) { artifact -> + artifact.type = CInteropCommonizerArtifactTypeAttribute.KLIB_COLLECTION_DIR + artifact.extension = CInteropCommonizerArtifactTypeAttribute.KLIB_COLLECTION_DIR + } + } + + /* Create resolvable configuration */ + val resolvable = project.configurations.create("testDependencies") { configuration -> + configuration.markResolvable() + configuration.attributes.attribute(Usage.USAGE_ATTRIBUTE, project.objects.named("test")) + configuration.attributes.attribute( + CInteropCommonizerArtifactTypeAttribute.attribute, CInteropCommonizerArtifactTypeAttribute.KLIB + ) + } + + project.dependencies { resolvable(project) } + + assertEquals(klibs.toSet(), resolvable.incoming.artifactView { }.files.toSet()) + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/CInteropCommonizerConfigurationTests.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/CInteropCommonizerConfigurationTests.kt new file mode 100644 index 00000000000..ca50f9fbe1d --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/CInteropCommonizerConfigurationTests.kt @@ -0,0 +1,117 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +@file:Suppress("FunctionName", "DuplicatedCode") + +package org.jetbrains.kotlin.gradle.unitTests + +import org.gradle.kotlin.dsl.dependencies +import org.jetbrains.kotlin.commonizer.CommonizerTarget +import org.jetbrains.kotlin.commonizer.identityString +import org.jetbrains.kotlin.gradle.targets.native.internal.CommonizerTargetAttribute +import org.jetbrains.kotlin.gradle.util.buildProjectWithMPP +import org.jetbrains.kotlin.gradle.utils.markConsumable +import org.jetbrains.kotlin.gradle.utils.markResolvable +import org.jetbrains.kotlin.konan.target.KonanTarget +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.fail + +class CInteropCommonizerConfigurationTests { + + @Test + fun `test - compatibility rule - superset is compatible`() { + val project = buildProjectWithMPP() + + val consumable = project.configurations.create("testElements") { configuration -> + configuration.markConsumable() + configuration.attributes.attribute( + CommonizerTargetAttribute.attribute, + CommonizerTarget( + KonanTarget.LINUX_X64, + KonanTarget.LINUX_ARM64, + KonanTarget.IOS_ARM64, + KonanTarget.IOS_X64 + ).identityString + ) + } + + val resolvable = project.configurations.create("testDependencies") { configuration -> + configuration.markResolvable() + configuration.attributes.attribute( + CommonizerTargetAttribute.attribute, + CommonizerTarget( + KonanTarget.IOS_ARM64, + KonanTarget.IOS_X64 + ).identityString + ) + } + + project.dependencies { + resolvable(project) + } + + val resolvedDependencies = resolvable.resolvedConfiguration.lenientConfiguration.allModuleDependencies + if (resolvedDependencies.isEmpty()) fail("Expected at least one dependency") + if (resolvedDependencies.size > 1) fail("Expected exactly one dependency. Found: $resolvedDependencies") + val resolved = resolvedDependencies.single() + assertEquals(consumable.name, resolved.configuration) + } + + @Test + fun `test - disambiguation rule - chooses most specific variant`() { + val project = buildProjectWithMPP() + + project.configurations.create("testConsumableAll") { configuration -> + configuration.markConsumable() + configuration.attributes.attribute( + CommonizerTargetAttribute.attribute, + CommonizerTarget( + KonanTarget.LINUX_X64, + KonanTarget.LINUX_ARM64, + KonanTarget.IOS_ARM64, + KonanTarget.IOS_X64, + KonanTarget.MACOS_X64, + KonanTarget.MACOS_ARM64 + ).identityString + ) + } + + /* More specific as it does not offer macos parts */ + val consumableSpecific = project.configurations.create("testConsumableSpecific") { configuration -> + configuration.markConsumable() + configuration.attributes.attribute( + CommonizerTargetAttribute.attribute, + CommonizerTarget( + KonanTarget.IOS_ARM64, + KonanTarget.IOS_X64, + KonanTarget.LINUX_X64, + KonanTarget.LINUX_ARM64, + ).identityString + ) + } + + val resolvable = project.configurations.create("testDependencies") { configuration -> + configuration.markResolvable() + configuration.attributes.attribute( + CommonizerTargetAttribute.attribute, + CommonizerTarget( + KonanTarget.IOS_ARM64, + KonanTarget.IOS_X64 + ).identityString + ) + } + + project.dependencies { + resolvable(project) + } + + val resolvedDependencies = resolvable.resolvedConfiguration.lenientConfiguration.allModuleDependencies + if (resolvedDependencies.isEmpty()) fail("Expected at least one dependency") + if (resolvedDependencies.size > 1) fail("Expected exactly one dependency. Found: $resolvedDependencies") + val resolved = resolvedDependencies.single() + assertEquals(consumableSpecific.name, resolved.configuration) + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/CInteropCommonizerTaskTest.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/CInteropCommonizerTaskTest.kt index 20e369973fe..ac635a726d8 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/CInteropCommonizerTaskTest.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/CInteropCommonizerTaskTest.kt @@ -15,6 +15,7 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet import org.jetbrains.kotlin.gradle.targets.native.internal.CInteropCommonizerTask import org.jetbrains.kotlin.gradle.targets.native.internal.CInteropCommonizerGroup import org.jetbrains.kotlin.gradle.targets.native.internal.commonizeCInteropTask +import org.jetbrains.kotlin.gradle.targets.native.internal.findCInteropCommonizerGroup import org.jetbrains.kotlin.gradle.util.enableCInteropCommonization import org.jetbrains.kotlin.gradle.util.main import org.jetbrains.kotlin.gradle.util.runLifecycleAwareTest @@ -78,7 +79,7 @@ class CInteropCommonizerTaskTest : MultiplatformExtensionTest() { setOf(CommonizerTarget(LINUX_X64, MACOS_X64)), setOf(linuxInterop.identifier, macosInterop.identifier) ), - task.findInteropsGroup(expectCInteropCommonizerDependent(nativeMain)) + project.findCInteropCommonizerGroup(expectCInteropCommonizerDependent(nativeMain)) ) } @@ -148,8 +149,8 @@ class CInteropCommonizerTaskTest : MultiplatformExtensionTest() { assertCInteropDependentEqualsForSourceSetAndCompilation(nativeMain) assertCInteropDependentEqualsForSourceSetAndCompilation(iosMain) - assertEquals(group, task.findInteropsGroup(expectCInteropCommonizerDependent(nativeMain))) - assertEquals(group, task.findInteropsGroup(expectCInteropCommonizerDependent(iosMain))) + assertEquals(group, findCInteropCommonizerGroup(expectCInteropCommonizerDependent(nativeMain))) + assertEquals(group, findCInteropCommonizerGroup(expectCInteropCommonizerDependent(iosMain))) } @Test @@ -207,7 +208,7 @@ class CInteropCommonizerTaskTest : MultiplatformExtensionTest() { ) assertEquals( - group, task.findInteropsGroup(expectCInteropCommonizerDependent(nativeMain)) + group, findCInteropCommonizerGroup(expectCInteropCommonizerDependent(nativeMain)) ) assertNull( @@ -267,11 +268,11 @@ class CInteropCommonizerTaskTest : MultiplatformExtensionTest() { ) assertEquals( - group, task.findInteropsGroup(expectCInteropCommonizerDependent(nativeMain)) + group, findCInteropCommonizerGroup(expectCInteropCommonizerDependent(nativeMain)) ) assertEquals( - group, task.findInteropsGroup(expectCInteropCommonizerDependent(expectSharedNativeCompilation(nativeMain))) + group, findCInteropCommonizerGroup(expectCInteropCommonizerDependent(expectSharedNativeCompilation(nativeMain))) ) assertNull( @@ -436,37 +437,37 @@ class CInteropCommonizerTaskTest : MultiplatformExtensionTest() { ) assertEquals( - mainGroup, task.findInteropsGroup(expectCInteropCommonizerDependent(nativeMain)), + mainGroup, findCInteropCommonizerGroup(expectCInteropCommonizerDependent(nativeMain)), "Expected nativeMain being part of the mainGroup" ) assertEquals( - mainGroup, task.findInteropsGroup(expectCInteropCommonizerDependent(unixMain)), + mainGroup, findCInteropCommonizerGroup(expectCInteropCommonizerDependent(unixMain)), "Expected unixMain being part of the mainGroup" ) assertEquals( - mainGroup, task.findInteropsGroup(expectCInteropCommonizerDependent(appleMain)), + mainGroup, findCInteropCommonizerGroup(expectCInteropCommonizerDependent(appleMain)), "Expected appleMain being part of the mainGroup" ) assertEquals( - mainGroup, task.findInteropsGroup(expectCInteropCommonizerDependent(iosMain)), + mainGroup, findCInteropCommonizerGroup(expectCInteropCommonizerDependent(iosMain)), "Expected iosMain being part of the mainGroup" ) assertEquals( - testGroup, task.findInteropsGroup(expectCInteropCommonizerDependent(nativeTest)), + testGroup, findCInteropCommonizerGroup(expectCInteropCommonizerDependent(nativeTest)), "Expected nativeTest being part of the testGroup" ) assertEquals( - testGroup, task.findInteropsGroup(expectCInteropCommonizerDependent(appleTest)), + testGroup, findCInteropCommonizerGroup(expectCInteropCommonizerDependent(appleTest)), "Expected appleTest being part of the testGroup" ) assertEquals( - testGroup, task.findInteropsGroup(expectCInteropCommonizerDependent(iosTest)), + testGroup, findCInteropCommonizerGroup(expectCInteropCommonizerDependent(iosTest)), "Expected iosTest being part of the testGroup" )