[Gradle] Use 'commonized cinterop configurations' as single way of sharing cinterop commonizer output

^KT-59596 Verification Pending
This commit is contained in:
Anton Lakotka
2023-08-14 17:16:34 +02:00
committed by Space Team
parent b2bef4ad41
commit 9866336a40
17 changed files with 534 additions and 310 deletions
@@ -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) {
@@ -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?
}
}
@@ -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<SourceSetName, SourceSetMetadataOutputs>,
@@ -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<SourceSetName, SourceSetMetadataOutputs> {
@@ -72,14 +57,9 @@ internal suspend fun Project.collectSourceSetMetadataOutputs(): Map<SourceSetNam
val multiplatformExtension = multiplatformExtensionOrNull ?: return emptyMap()
val sourceSetMetadata = multiplatformExtension.sourceSetsMetadataOutputs()
val sourceSetCInteropMetadata = multiplatformExtension.cInteropMetadataOfSourceSets(sourceSetMetadata.keys)
return sourceSetMetadata.mapValues { (sourceSet, metadata) ->
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<KotlinSourceSet>,
): Map<KotlinSourceSet, SourceSetMetadataOutputs.CInterop?> {
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)
)
}
}
@@ -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)
}
@@ -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<String> = 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<TransformParameters.None> {
@get:InputArtifact
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val inputArtifact: Provider<FileSystemLocation>
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)
}
}
}
}
@@ -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))
}
@@ -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<SharedCommonizerTarget>,
@get:Input val interops: Set<CInteropIdentifier>
@get:Input val interops: Set<CInteropIdentifier>,
) {
override fun toString(): String {
return buildString {
@@ -28,4 +39,70 @@ internal data class CInteropCommonizerGroup(
appendLine("}")
}
}
}
}
/* Collection of cinterop groups present in the given project */
/**
* Represents all collected [CInteropCommonizerGroup] gruops for the given project
*/
internal val Project.kotlinCInteropGroups: Future<Set<CInteropCommonizerGroup>>
get() = extensions.extraProperties.getOrPut("org.jetbrains.kotlin.gradle.targets.native.internal.kotlinCInteropGroups") {
lazyFuture { collectCInteropGroups() }
}
private suspend fun Project.collectCInteropGroups(): Set<CInteropCommonizerGroup> {
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<CInteropCommonizerDependent> {
val multiplatformExtension = project.multiplatformExtensionOrNull ?: return emptySet()
val fromSharedNativeCompilations = multiplatformExtension
.targets.flatMap { target -> target.compilations }
.filterIsInstance<KotlinSharedNativeCompilation>()
.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()
}
@@ -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<File>,
@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<SharedCommonizerTarget, File> = group
.targets
.associateWith { target -> CommonizerOutputFileLayout.resolveCommonizedDirectory(outputDirectory, target) }
@get:Internal
internal val kotlinPluginVersion: Property<String> = objectFactory
.property<String>()
@@ -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<File>.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<Set<CInteropCommonizerGroup>> = CompletableFuture()
internal val allInteropGroups: Future<Set<CInteropCommonizerGroup>> = 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 {
@@ -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)
}
}
@@ -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<ChooseVisibleSourceSets>()
.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<File>()
@@ -96,7 +51,7 @@ private fun Project.createCInteropMetadataDependencyClasspathFromAssociatedCompi
*/
val (associatedSourceSet, _) = sourceSet.getAdditionalVisibleSourceSets()
.filterIsInstance<DefaultKotlinSourceSet>()
.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<File>()
@@ -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<File> {
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<MetadataDependencyResolution>.resolutionsToTransform(): List<ChooseVisibleSourceSets> {
return filterIsInstance<ChooseVisibleSourceSets>()
.applyIf(!transformProjectDependencies) {
filterNot { it.dependency in currentBuild }
}
.filterNot { it.dependency in currentBuild }
}
}
@@ -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<String> = 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<String> {
override fun execute(details: CompatibilityCheckDetails<String>) {
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<String> {
override fun execute(details: MultipleCandidatesDetails<String>) {
details.closestMatch(details.candidateValues.sorted().minByOrNull { it.length } ?: return)
}
}
}
@@ -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<BuildMetricsReporter<GradleBuildTime, GradleBuildPerformanceMetric>> = project.objects
.property(GradleBuildMetricsReporter())
@@ -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<CInteropCommonizerDependent> {
val multiplatformExtension = project.multiplatformExtensionOrNull ?: return emptySet()
val fromSharedNativeCompilations = multiplatformExtension
.targets.flatMap { target -> target.compilations }
.filterIsInstance<KotlinSharedNativeCompilation>()
.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<CInteropCommonizerGroup> {
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
}
@@ -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())
}
}
@@ -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)
}
}
@@ -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"
)