[Gradle] CInterop commonization: Improve grouping and dependency management

^KT-47775 Verification Pending
This implementation will be lenient toward the common
*Test.dependsOn(*Main) mistake.

^KT-47053 Verification Pending
Support source sets that do not have a dedicated
shared native compilation. Also support additional visible source sets
coming from associate compilations.
This commit is contained in:
sebastian.sellmair
2021-07-19 15:08:21 +02:00
committed by Space
parent a7e81d5154
commit 02d4c866ca
14 changed files with 777 additions and 228 deletions
@@ -135,10 +135,10 @@ class CommonizerIT : BaseGradleIT() {
val expectedOutputDirectoryForBuild = projectDir.resolve("build/classes/kotlin/commonizer")
build(":copyCommonizeCInteropForIde") {
assertSuccessful()
assertTasksExecuted(":cinteropCurlTargetA")
assertTasksExecuted(":cinteropCurlTargetB")
assertTasksExecuted(":commonizeCInterop")
assertSuccessful()
assertTrue(expectedOutputDirectoryForIde.isDirectory, "Missing output directory for IDE")
assertTrue(expectedOutputDirectoryForBuild.isDirectory, "Missing output directory for build")
@@ -8,36 +8,27 @@
package org.jetbrains.kotlin.gradle
import org.gradle.api.internal.project.ProjectInternal
import org.gradle.api.plugins.ExtraPropertiesExtension
import org.gradle.testfixtures.ProjectBuilder
import org.jetbrains.kotlin.commonizer.CommonizerTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.commonizer.SharedCommonizerTarget
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinSharedNativeCompilation
import org.jetbrains.kotlin.gradle.targets.native.internal.CInteropCommonizationParameters
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.supports
import org.jetbrains.kotlin.konan.target.KonanTarget.*
import kotlin.test.*
class CInteropCommonizerTaskTest {
class CInteropCommonizerTaskTest : MultiplatformExtensionTest() {
private lateinit var project: ProjectInternal
private lateinit var kotlin: KotlinMultiplatformExtension
private val task: CInteropCommonizerTask get() = project.commonizeCInteropTask?.get() ?: fail("Missing commonizeCInteropTask")
@BeforeTest
fun setup() {
project = ProjectBuilder.builder().build() as ProjectInternal
project.extensions.getByType(ExtraPropertiesExtension::class.java).set("kotlin.mpp.enableGranularSourceSetsMetadata", "true")
project.extensions.getByType(ExtraPropertiesExtension::class.java).set("kotlin.mpp.enableCInteropCommonization", "true")
project.plugins.apply("kotlin-multiplatform")
kotlin = project.extensions.getByName("kotlin") as KotlinMultiplatformExtension
override fun setup() {
enableGranularSourceSetsMetadata()
enableCInteropCommonization()
super.setup()
}
@Test
fun `nativeMain linux macos`() {
val linuxInterop = kotlin.linuxX64("linux").compilations.getByName("main").cinterops.create("anyInteropName")
@@ -53,15 +44,18 @@ class CInteropCommonizerTaskTest {
macosMain.dependsOn(nativeMain)
project.evaluate()
val nativeMainCompilation = kotlin.targets.flatMap { it.compilations }
.filterIsInstance<KotlinSharedNativeCompilation>()
.single { it.defaultSourceSet == nativeMain }
val groups = task.getAllInteropsGroups()
assertEquals(1, groups.size, "Expected only one InteropsGroup")
assertCInteropDependentEqualsForSourceSetAndCompilation(nativeMain)
assertEquals(
CInteropCommonizationParameters(
setOf(CommonizerTarget(LINUX_X64, MACOS_X64)), setOf(linuxInterop.identifier, macosInterop.identifier)
CInteropCommonizerGroup(
setOf(CommonizerTarget(LINUX_X64, MACOS_X64)),
setOf(linuxInterop.identifier, macosInterop.identifier)
),
task.getCommonizationParameters(nativeMainCompilation)
task.findInteropsGroup(expectCInteropCommonizerDependent(nativeMain))
)
}
@@ -80,17 +74,18 @@ class CInteropCommonizerTaskTest {
macosMain.dependsOn(nativeMain)
project.evaluate()
val nativeMainCompilation = kotlin.targets.flatMap { it.compilations }
.filterIsInstance<KotlinSharedNativeCompilation>()
.single { it.defaultSourceSet == nativeMain }
assertNull(
task.getCommonizationParameters(nativeMainCompilation),
findCInteropCommonizerDependent(nativeMain),
"Expected no CInteropCommonizerTarget from nativeMain, since one target has not defined any cinterop"
)
assertNull(
findCInteropCommonizerDependent(expectSharedNativeCompilation(nativeMain)),
"Expected no CInteropCommonizerTarget from nativeMain, since one target has not defined any cinterop"
)
}
@Test
fun `nativeMain iosMain linux macos iosX64 iosArm64`() {
val linuxInterop = kotlin.linuxX64("linux").compilations.getByName("main").cinterops.create("anyInteropName").identifier
@@ -116,33 +111,370 @@ class CInteropCommonizerTaskTest {
project.evaluate()
assertEquals(
CInteropCommonizationParameters(
setOf(
CommonizerTarget(IOS_X64, IOS_ARM64),
CommonizerTarget(IOS_X64, IOS_ARM64, MACOS_X64, LINUX_X64)
),
setOf(linuxInterop, macosInterop, iosX64Interop, iosArm64Interop)
), task.getCommonizationParameters(sharedNativeCompilation(nativeMain))
1, task.getAllInteropsGroups().size,
"Expected exactly one InteropsGroup for task"
)
val group = CInteropCommonizerGroup(
setOf(
CommonizerTarget(IOS_X64, IOS_ARM64, MACOS_X64, LINUX_X64),
CommonizerTarget(IOS_X64, IOS_ARM64)
),
setOf(
linuxInterop, macosInterop, iosX64Interop, iosArm64Interop
)
)
assertCInteropDependentEqualsForSourceSetAndCompilation(nativeMain)
assertCInteropDependentEqualsForSourceSetAndCompilation(iosMain)
assertEquals(group, task.findInteropsGroup(expectCInteropCommonizerDependent(nativeMain)))
assertEquals(group, task.findInteropsGroup(expectCInteropCommonizerDependent(iosMain)))
}
@Test
fun `nativeTest nativeMain linux macos`() {
`nativeTest nativeMain linux macos`(false)
}
@Test
fun `nativeTest nativeMain linux macos - nativeTest dependsOn nativeMain`() {
`nativeTest nativeMain linux macos`(true)
}
private fun `nativeTest nativeMain linux macos`(
nativeTestDependsOnNativeMain: Boolean
) {
val linuxInterop = kotlin.linuxX64("linux").compilations.getByName("main").cinterops.create("anyInteropName").identifier
val macosInterop = kotlin.macosX64("macos").compilations.getByName("main").cinterops.create("anyInteropName").identifier
val commonMain = kotlin.sourceSets.getByName("commonMain")
val nativeMain = kotlin.sourceSets.create("nativeMain")
val linuxMain = kotlin.sourceSets.getByName("linuxMain")
val macosMain = kotlin.sourceSets.getByName("macosMain")
val nativeTest = kotlin.sourceSets.create("nativeTest")
val linuxTest = kotlin.sourceSets.getByName("linuxTest")
val macosTest = kotlin.sourceSets.getByName("macosTest")
linuxTest.dependsOn(nativeTest)
macosTest.dependsOn(nativeTest)
linuxMain.dependsOn(nativeMain)
macosMain.dependsOn(nativeMain)
nativeMain.dependsOn(commonMain)
if (nativeTestDependsOnNativeMain) {
nativeTest.dependsOn(nativeMain)
}
project.evaluate()
assertEquals(
1, task.getAllInteropsGroups().size,
"Expected exactly 1 'SharedInteropsGroup' for task"
)
val group = CInteropCommonizerGroup(
setOf(CommonizerTarget(LINUX_X64, MACOS_X64)),
setOf(linuxInterop, macosInterop)
)
assertEquals(
CInteropCommonizationParameters(
setOf(
CommonizerTarget(IOS_X64, IOS_ARM64),
CommonizerTarget(IOS_X64, IOS_ARM64, MACOS_X64, LINUX_X64)
),
setOf(linuxInterop, macosInterop, iosX64Interop, iosArm64Interop)
), task.getCommonizationParameters(sharedNativeCompilation(iosMain))
expectCInteropCommonizerDependent(nativeMain),
expectCInteropCommonizerDependent(expectSharedNativeCompilation(nativeMain)),
"Expected same dependent from 'nativeMain' source set and 'nativeMain' compilation"
)
assertTrue(
task.getCommonizationParameters(sharedNativeCompilation(nativeMain))!!.supports(sharedNativeCompilation(nativeMain)),
"Expected CInteropCommonizerTarget of nativeMain to support iosMain"
assertEquals(
group, task.findInteropsGroup(expectCInteropCommonizerDependent(nativeMain))
)
assertNull(
findCInteropCommonizerDependent(nativeTest),
"Expected nativeTest to not depend on CInteropCommonizer"
)
}
private fun sharedNativeCompilation(sourceSet: KotlinSourceSet): KotlinSharedNativeCompilation {
return kotlin.targets.flatMap { it.compilations }.filterIsInstance<KotlinSharedNativeCompilation>()
.single { it.defaultSourceSet == sourceSet }
@Test
fun `nativeTest nativeMain linux macos - test compilation defines custom cinterop`() {
`nativeTest nativeMain linux macos - test compilation defines custom cinterop`(false)
}
@Test
fun `nativeTest nativeMain linux macos - test compilation defines custom cinterop - nativeTest dependsOn nativeMain`() {
`nativeTest nativeMain linux macos - test compilation defines custom cinterop`(true)
}
private fun `nativeTest nativeMain linux macos - test compilation defines custom cinterop`(
nativeTestDependsOnNativeMain: Boolean
) {
val linuxInterop = kotlin.linuxX64("linux").compilations.getByName("main").cinterops.create("anyInteropName").identifier
val macosInterop = kotlin.macosX64("macos").compilations.getByName("main").cinterops.create("anyInteropName").identifier
kotlin.linuxX64("linux").compilations.getByName("test").cinterops.create("anyOtherName").identifier
val commonMain = kotlin.sourceSets.getByName("commonMain")
val nativeMain = kotlin.sourceSets.create("nativeMain")
val linuxMain = kotlin.sourceSets.getByName("linuxMain")
val macosMain = kotlin.sourceSets.getByName("macosMain")
val nativeTest = kotlin.sourceSets.create("nativeTest")
val linuxTest = kotlin.sourceSets.getByName("linuxTest")
val macosTest = kotlin.sourceSets.getByName("macosTest")
linuxTest.dependsOn(nativeTest)
macosTest.dependsOn(nativeTest)
linuxMain.dependsOn(nativeMain)
macosMain.dependsOn(nativeMain)
nativeMain.dependsOn(commonMain)
if (nativeTestDependsOnNativeMain) {
nativeTest.dependsOn(nativeMain)
}
project.evaluate()
assertEquals(
1, task.getAllInteropsGroups().size,
"Expected exactly 1 'SharedInteropsGroup' for task"
)
val group = CInteropCommonizerGroup(
setOf(CommonizerTarget(LINUX_X64, MACOS_X64)),
setOf(linuxInterop, macosInterop)
)
assertEquals(
group, task.findInteropsGroup(expectCInteropCommonizerDependent(nativeMain))
)
assertEquals(
group, task.findInteropsGroup(expectCInteropCommonizerDependent(expectSharedNativeCompilation(nativeMain)))
)
assertNull(
findCInteropCommonizerDependent(nativeTest),
"Expected 'nativeTest' to not be CInteropCommonizer dependent"
)
}
@Test
fun `hierarchical project - testSourceSetsDependOnMainSourceSets = true`() {
`hierarchical project`(testSourceSetsDependOnMainSourceSets = true)
}
@Test
fun `hierarchical project - testSourceSetsDependOnMainSourceSets = false`() {
`hierarchical project`(testSourceSetsDependOnMainSourceSets = false)
}
private fun `hierarchical project`(testSourceSetsDependOnMainSourceSets: Boolean) {
/* Define targets */
val linux = kotlin.linuxX64("linux")
val macos = kotlin.macosX64("macos")
val iosX64 = kotlin.iosX64()
val iosArm64 = kotlin.iosArm64()
val windows64 = kotlin.mingwX64("windows64")
val windows32 = kotlin.mingwX86("windows32")
kotlin.jvm()
kotlin.js().browser()
val nativeTargets = listOf(linux, macos, iosX64, iosArm64, windows64, windows32)
val windowsTargets = listOf(windows64, windows32)
val unixLikeTargets = listOf(linux, macos, iosX64, iosArm64)
val appleTargets = listOf(macos, iosX64, iosArm64)
val iosTargets = listOf(iosX64, iosArm64)
/* Define interops */
nativeTargets.map { target ->
target.compilations.getByName("main").cinterops.create("nativeHelper").identifier
}
nativeTargets.map { target ->
target.compilations.getByName("test").cinterops.create("nativeTestHelper").identifier
}
windowsTargets.map { target ->
target.compilations.getByName("main").cinterops.create("windowsHelper").identifier
}
unixLikeTargets.map { target ->
target.compilations.getByName("main").cinterops.create("unixHelper").identifier
}
appleTargets.map { target ->
target.compilations.getByName("main").cinterops.create("appleHelper").identifier
}
appleTargets.map { target ->
target.compilations.getByName("test").cinterops.create("appleTestHelper").identifier
}
iosTargets.map { target ->
target.compilations.getByName("main").cinterops.create("iosHelper").identifier
}
iosX64.compilations.getByName("main").cinterops.create("iosX64Helper").identifier
iosX64.compilations.getByName("test").cinterops.create("iosX64TestHelper").identifier
/* Define source set hierarchy */
val commonMain = kotlin.sourceSets.getByName("commonMain")
val commonTest = kotlin.sourceSets.getByName("commonTest")
val nativeMain = kotlin.sourceSets.create("nativeMain")
val nativeTest = kotlin.sourceSets.create("nativeTest")
val unixMain = kotlin.sourceSets.create("unixMain")
val appleMain = kotlin.sourceSets.create("appleMain")
val appleTest = kotlin.sourceSets.create("appleTest")
val windowsMain = kotlin.sourceSets.create("windowsMain")
val iosMain = kotlin.sourceSets.create("iosMain")
val iosTest = kotlin.sourceSets.create("iosTest")
nativeMain.dependsOn(commonMain)
nativeTest.dependsOn(commonTest)
unixMain.dependsOn(nativeMain)
appleMain.dependsOn(nativeMain)
appleTest.dependsOn(nativeTest)
windowsMain.dependsOn(nativeMain)
iosMain.dependsOn(appleMain)
if (testSourceSetsDependOnMainSourceSets) {
nativeTest.dependsOn(nativeMain)
appleTest.dependsOn(appleMain)
iosTest.dependsOn(iosMain)
}
windowsTargets.forEach { target ->
target.compilations.getByName("main").defaultSourceSet.dependsOn(windowsMain)
}
iosTargets.forEach { target ->
target.compilations.getByName("main").defaultSourceSet.dependsOn(iosMain)
target.compilations.getByName("test").defaultSourceSet.dependsOn(iosTest)
}
appleTargets.forEach { target ->
target.compilations.getByName("main").defaultSourceSet.dependsOn(appleMain)
target.compilations.getByName("test").defaultSourceSet.dependsOn(appleTest)
}
unixLikeTargets.forEach { target ->
target.compilations.getByName("main").defaultSourceSet.dependsOn(unixMain)
}
nativeTargets.forEach { target ->
target.compilations.getByName("main").defaultSourceSet.dependsOn(nativeMain)
target.compilations.getByName("test").defaultSourceSet.dependsOn(nativeTest)
}
project.evaluate()
assertCInteropDependentEqualsForSourceSetAndCompilation(nativeMain)
assertCInteropDependentEqualsForSourceSetAndCompilation(unixMain)
assertCInteropDependentEqualsForSourceSetAndCompilation(windowsMain)
assertCInteropDependentEqualsForSourceSetAndCompilation(appleMain)
assertCInteropDependentEqualsForSourceSetAndCompilation(iosMain)
val groups = task.getAllInteropsGroups()
assertEquals(2, groups.size, "Expected exactly two interop groups: main and test")
val nativeCommonizerTarget = SharedCommonizerTarget(nativeTargets.map { it.konanTarget })
val unixLikeCommonizerTarget = SharedCommonizerTarget(unixLikeTargets.map { it.konanTarget })
val windowsCommonizerTarget = SharedCommonizerTarget(windowsTargets.map { it.konanTarget })
val appleCommonizerTarget = SharedCommonizerTarget(appleTargets.map { it.konanTarget })
val iosCommonizerTarget = SharedCommonizerTarget(iosTargets.map { it.konanTarget })
val expectedMainGroup = CInteropCommonizerGroup(
targets = setOf(
nativeCommonizerTarget, unixLikeCommonizerTarget, windowsCommonizerTarget,
appleCommonizerTarget, iosCommonizerTarget
),
interops = nativeTargets.map { target -> target.mainCinteropIdentifier("nativeHelper") }.toSet() +
unixLikeTargets.map { target -> target.mainCinteropIdentifier("unixHelper") } +
windowsTargets.map { target -> target.mainCinteropIdentifier("windowsHelper") } +
appleTargets.map { target -> target.mainCinteropIdentifier("appleHelper") } +
iosTargets.map { target -> target.mainCinteropIdentifier("iosHelper") } +
iosX64.mainCinteropIdentifier("iosX64Helper")
)
val expectedTestGroup = CInteropCommonizerGroup(
targets = setOf(nativeCommonizerTarget, appleCommonizerTarget, iosCommonizerTarget),
interops = nativeTargets.map { target -> target.testCinteropIdentifier("nativeTestHelper") }.toSet() +
appleTargets.map { target -> target.testCinteropIdentifier("appleTestHelper") } +
iosX64.testCinteropIdentifier("iosX64TestHelper")
)
val mainGroup = groups.maxBy { it.targets.size }!!
val testGroup = groups.minBy { it.targets.size }!!
assertEquals(
expectedMainGroup, mainGroup,
"mainGroup does not match"
)
assertEquals(
expectedTestGroup, testGroup,
"testGroup does not match"
)
assertEquals(
mainGroup, task.findInteropsGroup(expectCInteropCommonizerDependent(nativeMain)),
"Expected nativeMain being part of the mainGroup"
)
assertEquals(
mainGroup, task.findInteropsGroup(expectCInteropCommonizerDependent(unixMain)),
"Expected unixMain being part of the mainGroup"
)
assertEquals(
mainGroup, task.findInteropsGroup(expectCInteropCommonizerDependent(windowsMain)),
"Expected windowsMain being part of the mainGroup"
)
assertEquals(
mainGroup, task.findInteropsGroup(expectCInteropCommonizerDependent(appleMain)),
"Expected appleMain being part of the mainGroup"
)
assertEquals(
mainGroup, task.findInteropsGroup(expectCInteropCommonizerDependent(iosMain)),
"Expected iosMain being part of the mainGroup"
)
assertEquals(
testGroup, task.findInteropsGroup(expectCInteropCommonizerDependent(nativeTest)),
"Expected nativeTest being part of the testGroup"
)
assertEquals(
testGroup, task.findInteropsGroup(expectCInteropCommonizerDependent(appleTest)),
"Expected appleTest being part of the testGroup"
)
assertEquals(
testGroup, task.findInteropsGroup(expectCInteropCommonizerDependent(iosTest)),
"Expected iosTest being part of the testGroup"
)
kotlin.targets
/* Shared K/N targets still are considered type common */
.filter { it.platformType != KotlinPlatformType.common }
.flatMap { it.compilations }.map { it.defaultSourceSet }.forEach { targetDefaultSourceSet ->
assertNull(
findCInteropCommonizerDependent(targetDefaultSourceSet),
"Expected target source set ${targetDefaultSourceSet.name} not be CInteropCommonizerDependent"
)
}
}
private fun assertCInteropDependentEqualsForSourceSetAndCompilation(sourceSet: KotlinSourceSet) {
assertEquals(
expectCInteropCommonizerDependent(sourceSet),
expectCInteropCommonizerDependent(expectSharedNativeCompilation(sourceSet)),
"Expected found CInteropCommonizerDependent for source set '$sourceSet and it's shared native compilation to be equal"
)
}
}
@@ -0,0 +1,79 @@
/*
* Copyright 2010-2021 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.
*/
/* Associate compilations are not yet supported by the IDE. KT-34102 */
@file:Suppress("invisible_reference", "invisible_member", "FunctionName", "DuplicatedCode")
package org.jetbrains.kotlin.gradle
import org.gradle.api.internal.project.ProjectInternal
import org.gradle.api.plugins.ExtraPropertiesExtension
import org.gradle.testfixtures.ProjectBuilder
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinSharedNativeCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinCompilationData
import org.jetbrains.kotlin.gradle.targets.native.internal.CInteropCommonizerDependent
import org.jetbrains.kotlin.gradle.targets.native.internal.CInteropIdentifier
import org.jetbrains.kotlin.gradle.targets.native.internal.from
import kotlin.test.BeforeTest
import kotlin.test.assertNotNull
abstract class MultiplatformExtensionTest {
protected val project: ProjectInternal = ProjectBuilder.builder().build() as ProjectInternal
protected lateinit var kotlin: KotlinMultiplatformExtension
@BeforeTest
open fun setup() {
project.plugins.apply("kotlin-multiplatform")
kotlin = project.extensions.getByName("kotlin") as KotlinMultiplatformExtension
}
protected fun enableGranularSourceSetsMetadata() {
project.extensions.getByType(ExtraPropertiesExtension::class.java).set("kotlin.mpp.enableGranularSourceSetsMetadata", "true")
}
protected fun enableCInteropCommonization() {
project.extensions.getByType(ExtraPropertiesExtension::class.java).set("kotlin.mpp.enableCInteropCommonization", "true")
}
internal fun expectCInteropCommonizerDependent(compilation: KotlinSharedNativeCompilation): CInteropCommonizerDependent {
return assertNotNull(
CInteropCommonizerDependent.from(compilation), "Can't find SharedInterops for ${compilation.name} compilation"
)
}
internal fun expectCInteropCommonizerDependent(sourceSet: KotlinSourceSet): CInteropCommonizerDependent {
return assertNotNull(
CInteropCommonizerDependent.from(project, sourceSet), "Can't find SharedInterops for ${sourceSet.name} source set"
)
}
internal fun findCInteropCommonizerDependent(compilation: KotlinSharedNativeCompilation): CInteropCommonizerDependent? {
return CInteropCommonizerDependent.from(compilation)
}
internal fun findCInteropCommonizerDependent(sourceSet: KotlinSourceSet): CInteropCommonizerDependent? {
return CInteropCommonizerDependent.from(project, sourceSet)
}
internal fun expectSharedNativeCompilation(sourceSet: KotlinSourceSet): KotlinSharedNativeCompilation {
return kotlin.targets.flatMap { it.compilations }.filterIsInstance<KotlinSharedNativeCompilation>()
.single { it.defaultSourceSet == sourceSet }
}
internal fun KotlinNativeTarget.mainCinteropIdentifier(name: String): CInteropIdentifier {
return compilations.getByName("main").cinteropIdentifier(name)
}
internal fun KotlinNativeTarget.testCinteropIdentifier(name: String): CInteropIdentifier {
return compilations.getByName("test").cinteropIdentifier(name)
}
internal fun KotlinCompilationData<*>.cinteropIdentifier(name: String): CInteropIdentifier {
return CInteropIdentifier(CInteropIdentifier.Scope.create(this), name)
}
}
@@ -27,9 +27,6 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinPm20ProjectExtension
import org.jetbrains.kotlin.gradle.plugin.sources.*
import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService
import org.jetbrains.kotlin.gradle.targets.native.internal.*
import org.jetbrains.kotlin.gradle.targets.native.internal.commonizeCInteropTask
import org.jetbrains.kotlin.gradle.targets.native.internal.copyCommonizeCInteropForIdeTask
import org.jetbrains.kotlin.gradle.targets.native.internal.getLibraries
import org.jetbrains.kotlin.gradle.tasks.*
import org.jetbrains.kotlin.gradle.utils.addExtendsFromRelation
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
@@ -318,21 +315,6 @@ class KotlinMetadataTargetConfigurator :
compileDependencyFiles = project.files()
}
}
if (this is KotlinSharedNativeCompilation) {
project.commonizeCInteropTask?.let { task ->
compileDependencyFiles = compileDependencyFiles.plus(task.getLibraries(this))
}
project.copyCommonizeCInteropForIdeTask?.let { task ->
val libraries = task.getLibraries(this)
kotlinSourceSetsIncludingDefault.filterIsInstance<DefaultKotlinSourceSet>().forEach { sourceSet ->
val dependencyConfigurationName =
if (project.isIntransitiveMetadataConfigurationEnabled) sourceSet.intransitiveMetadataConfigurationName
else sourceSet.implementationMetadataConfigurationName
project.dependencies.add(dependencyConfigurationName, libraries)
}
}
}
}
}
@@ -16,6 +16,7 @@ import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider
import org.jetbrains.kotlin.gradle.targets.native.DisabledNativeTargetsReporter
import org.jetbrains.kotlin.gradle.targets.native.internal.NativeDistributionTypeProvider
import org.jetbrains.kotlin.gradle.targets.native.internal.PlatformLibrariesGenerator
import org.jetbrains.kotlin.gradle.targets.native.internal.setupCInteropCommonizerDependencies
import org.jetbrains.kotlin.gradle.targets.native.internal.setupKotlinNativePlatformDependencies
import org.jetbrains.kotlin.gradle.utils.NativeCompilerDownloader
import org.jetbrains.kotlin.gradle.utils.SingleActionPerProject
@@ -91,6 +92,10 @@ abstract class AbstractKotlinNativeTargetPreset<T : KotlinNativeTarget>(
}
}
SingleActionPerProject.run(project, "setupCInteropCommonizerDependencies") {
project.setupCInteropCommonizerDependencies()
}
if (!konanTarget.enabledOnCurrentHost) {
with(HostManager()) {
val supportedHosts = enabledByHost.filterValues { konanTarget in it }.keys
@@ -8,10 +8,10 @@ package org.jetbrains.kotlin.gradle.targets.native.internal
import org.gradle.api.DefaultTask
import org.gradle.api.file.FileCollection
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskProvider
import org.jetbrains.kotlin.commonizer.CommonizerOutputFileLayout
import org.jetbrains.kotlin.commonizer.CommonizerOutputFileLayout.fileName
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinSharedNativeCompilation
import org.jetbrains.kotlin.commonizer.CommonizerOutputFileLayout.base64Hash
import org.jetbrains.kotlin.commonizer.CommonizerOutputFileLayout.ensureMaxFileNameLength
import org.jetbrains.kotlin.commonizer.identityString
import org.jetbrains.kotlin.gradle.utils.filesProvider
import java.io.File
@@ -19,27 +19,26 @@ internal abstract class AbstractCInteropCommonizerTask : DefaultTask() {
@get:OutputDirectory
abstract val outputDirectory: File
internal fun outputDirectory(parameters: CInteropCommonizationParameters): File {
internal fun outputDirectory(group: CInteropCommonizerGroup): File {
val interopsDirectoryName = group.interops.map { it.interopName }.toSet().joinToString(";")
val groupDisambiguation = group.targets.joinToString { it.identityString } +
group.interops.joinToString { it.uniqueName }
return outputDirectory
.resolve(parameters.targets.fileName)
.resolve(parameters.interops.map { it.interopName }.distinct().joinToString("-"))
.resolve(ensureMaxFileNameLength(interopsDirectoryName))
.resolve(base64Hash(groupDisambiguation))
}
internal abstract fun getCommonizationParameters(compilation: KotlinSharedNativeCompilation): CInteropCommonizationParameters?
internal abstract fun findInteropsGroup(dependent: CInteropCommonizerDependent): CInteropCommonizerGroup?
internal fun getLibraries(compilation: KotlinSharedNativeCompilation): FileCollection {
val compilationCommonizerTarget = project.getCommonizerTarget(compilation) ?: return project.files()
internal fun commonizedOutputLibraries(dependent: CInteropCommonizerDependent): FileCollection {
val fileProvider = project.filesProvider {
val parameters = getCommonizationParameters(compilation) ?: return@filesProvider emptySet<File>()
val group = findInteropsGroup(dependent) ?: return@filesProvider emptySet<File>()
CommonizerOutputFileLayout
.resolveCommonizedDirectory(outputDirectory(parameters), compilationCommonizerTarget)
.resolveCommonizedDirectory(outputDirectory(group), dependent.target)
.listFiles().orEmpty().toSet()
}
return fileProvider.builtBy(this)
}
}
internal fun TaskProvider<out AbstractCInteropCommonizerTask>.getLibraries(compilation: KotlinSharedNativeCompilation): FileCollection {
return compilation.target.project.files(map { it.getLibraries(compilation) })
}
@@ -0,0 +1,77 @@
/*
* Copyright 2010-2021 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.file.FileCollection
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMetadataTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinSharedNativeCompilation
import org.jetbrains.kotlin.gradle.plugin.sources.DefaultKotlinSourceSet
import org.jetbrains.kotlin.gradle.utils.filesProvider
import java.io.File
// TODO NOW: Integration tests!
internal fun Project.setupCInteropCommonizerDependencies() {
val kotlin = this.multiplatformExtensionOrNull ?: return
kotlin.targets.withType(KotlinMetadataTarget::class.java).all { target ->
target.compilations.withType(KotlinSharedNativeCompilation::class.java).all { compilation ->
setupCInteropCommonizerDependenciesForCompilation(compilation)
}
}
kotlin.sourceSets.withType(DefaultKotlinSourceSet::class.java).all { sourceSet ->
setupCInteropCommonizerDependenciesForIde(sourceSet)
}
}
private fun Project.setupCInteropCommonizerDependenciesForCompilation(compilation: KotlinSharedNativeCompilation) {
val cinteropCommonizerTask = project.commonizeCInteropTask ?: return
compilation.compileDependencyFiles += filesProvider {
val cinteropCommonizerDependent = CInteropCommonizerDependent.from(compilation) ?: return@filesProvider emptySet<File>()
cinteropCommonizerTask.get().commonizedOutputLibraries(cinteropCommonizerDependent)
}
}
/**
* IDE will resolve the dependencies provided on source sets.
* This will use the [Project.copyCommonizeCInteropForIdeTask] over the regular cinterop commonization task.
* The copying task prevent red code within the IDE after cleaning the build output.
*/
private fun Project.setupCInteropCommonizerDependenciesForIde(sourceSet: DefaultKotlinSourceSet) {
val cinteropCommonizerTask = project.copyCommonizeCInteropForIdeTask ?: return
addDependency(sourceSet, filesProvider files@{
val sourceSetCommonizerTarget = getCommonizerTarget(sourceSet)
val additionalVisibleSourceSets = sourceSet.getAdditionalVisibleSourceSets()
.filter { sourceSet -> getCommonizerTarget(sourceSet) == sourceSetCommonizerTarget }
val cinteropCommonizerDependents = (additionalVisibleSourceSets + sourceSet).toSet()
.mapNotNull { sourceSet -> CInteropCommonizerDependent.from(this, sourceSet) }
.toSet()
cinteropCommonizerDependents.map { cinteropCommonizerDependent ->
cinteropCommonizerTask.get().commonizedOutputLibraries(cinteropCommonizerDependent)
}
})
}
/**
* Dependencies here are using a special configuration called 'intransitiveMetadataConfiguration'.
* This special configuration can tell the IDE that this dependencies shall *not* be transitively be visible
* to dependsOn edges. This is necessary for the way the commonizer handles it's "expect refinement" approach.
* In this mode, every source set will receive exactly one commonized library to analyze its source code with.
*/
private fun Project.addDependency(sourceSet: DefaultKotlinSourceSet, dependency: FileCollection) {
val dependencyConfigurationName =
if (project.isIntransitiveMetadataConfigurationEnabled) sourceSet.intransitiveMetadataConfigurationName
else sourceSet.implementationMetadataConfigurationName
project.dependencies.add(dependencyConfigurationName, dependency)
}
@@ -0,0 +1,117 @@
/*
* Copyright 2010-2021 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.jetbrains.kotlin.commonizer.SharedCommonizerTarget
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.plugin.mpp.CompilationSourceSetUtil.compilationsBySourceSets
import org.jetbrains.kotlin.gradle.plugin.sources.resolveAllDependsOnSourceSets
import org.jetbrains.kotlin.gradle.targets.native.internal.CInteropIdentifier.Scope
import org.jetbrains.kotlin.gradle.utils.UnsafeApi
internal class CInteropCommonizerDependent @UnsafeApi constructor(
val target: SharedCommonizerTarget,
val scopes: Set<Scope>,
val interops: Set<CInteropIdentifier>
) {
override fun equals(other: Any?): Boolean {
if (other !is CInteropCommonizerDependent) return false
if (this.target != other.target) return false
if (this.scopes != other.scopes) return false
if (this.interops != other.interops) return false
return true
}
override fun hashCode(): Int {
var result = target.hashCode()
result = 31 * result + scopes.hashCode()
result = 31 * result + interops.hashCode()
return result
}
init {
require(target.targets.isNotEmpty()) { "CInteropCommonizerDependent.target.targets.size can't be empty" }
require(scopes.isNotEmpty()) { "CInteropCommonizerDependent.scopes can't be empty" }
require(interops.isNotEmpty()) { "CInteropCommonizerDependent.interops can't be empty" }
}
companion object Factory
}
@OptIn(UnsafeApi::class)
internal fun CInteropCommonizerDependent.Factory.from(
target: SharedCommonizerTarget,
compilations: Set<KotlinNativeCompilation>
): CInteropCommonizerDependent? {
target.targets.ifEmpty { return null }
/*
Filter out compilations that have their associate also in the set of compilations
e.g. do not include '*test' if their main counterpart is also present.
*test and *main compilations will be included when build authors declare a *Test dependsOn *Main source set relationship.
This relationship should not be declared, but we try to be lenient towards it here.
*/
val filteredCompilations = compilations.filter { compilation ->
compilation.associateWithTransitiveClosure.none { associateCompilation -> associateCompilation in compilations }
}.ifEmpty { return null }.toSet()
val scopes: Set<Scope> = filteredCompilations
.map { compilation -> Scope.create(compilation) }.toSet()
.ifEmpty { return null }
val interops: Set<CInteropIdentifier> = filteredCompilations
.flatMap { compilation -> compilation.cinterops.ifEmpty { return null } }
.map { cinterop -> cinterop.identifier }.toSet()
return CInteropCommonizerDependent(target, scopes, interops)
}
internal fun CInteropCommonizerDependent.Factory.from(compilation: KotlinSharedNativeCompilation): CInteropCommonizerDependent? {
return from(
compilation.project.getCommonizerTarget(compilation) as? SharedCommonizerTarget ?: return null,
compilation.findDependingNativeCompilations()
)
}
internal fun CInteropCommonizerDependent.Factory.from(project: Project, sourceSet: KotlinSourceSet): CInteropCommonizerDependent? {
val target = project.getCommonizerTarget(sourceSet) as? SharedCommonizerTarget ?: return null
val compilations = compilationsBySourceSets(project)[sourceSet] ?: return null
/* Non-native or non 'shared native' source sets can return eagerly */
if (compilations.any { compilation -> compilation !is AbstractKotlinNativeCompilation }) {
return null
}
return from(
target = target,
compilations = compilations.filterIsInstance<KotlinNativeCompilation>().toSet()
)
}
private fun KotlinSharedNativeCompilation.findDependingNativeCompilations(): Set<KotlinNativeCompilation> {
val multiplatformExtension = project.multiplatformExtensionOrNull ?: return emptySet()
val allParticipatingSourceSetsOfCompilation = allParticipatingSourceSets()
return multiplatformExtension.targets
.flatMap { target -> target.compilations }
.filterIsInstance<KotlinNativeCompilation>()
.filter { nativeCompilation -> nativeCompilation.allParticipatingSourceSets().containsAll(allParticipatingSourceSetsOfCompilation) }
.toSet()
}
/**
* Some implementations of [KotlinCompilation] do not contain the default source set in
* [KotlinCompilation.kotlinSourceSets] or [KotlinCompilation.allKotlinSourceSets]
* see KT-45412
*/
private fun KotlinCompilation<*>.allParticipatingSourceSets(): Set<KotlinSourceSet> {
return kotlinSourceSetsIncludingDefault + kotlinSourceSetsIncludingDefault.resolveAllDependsOnSourceSets()
}
@@ -0,0 +1,32 @@
/*
* Copyright 2010-2021 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.tasks.Input
import org.jetbrains.kotlin.commonizer.SharedCommonizerTarget
import org.jetbrains.kotlin.gradle.utils.appendLine
internal data class CInteropCommonizerGroup(
@get:Input val targets: Set<SharedCommonizerTarget>,
@get:Input val interops: Set<CInteropIdentifier>
) {
@Suppress("deprecation")
override fun toString(): String {
return buildString {
appendln("InteropsGroup {")
appendln("targets: ")
targets.sortedBy { it.targets.size }.forEach { target ->
appendln(" $target")
}
appendln()
appendln("interops: ")
interops.sortedBy { it.toString() }.forEach { interop ->
appendln(" $interop")
}
appendLine("}")
}
}
}
@@ -5,19 +5,18 @@
package org.jetbrains.kotlin.gradle.targets.native.internal
import org.gradle.api.Project
import org.gradle.api.file.FileCollection
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.*
import org.jetbrains.kotlin.commonizer.*
import org.jetbrains.kotlin.commonizer.CommonizerDependency
import org.jetbrains.kotlin.commonizer.NonTargetedCommonizerDependency
import org.jetbrains.kotlin.commonizer.TargetedCommonizerDependency
import org.jetbrains.kotlin.commonizer.allLeaves
import org.jetbrains.kotlin.compilerRunner.GradleCliCommonizer
import org.jetbrains.kotlin.compilerRunner.konanHome
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull
import org.jetbrains.kotlin.gradle.plugin.CInteropSettings
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.mpp.DefaultCInteropSettings
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinSharedNativeCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.kotlinSourceSetsIncludingDefault
import org.jetbrains.kotlin.gradle.plugin.sources.resolveAllDependsOnSourceSets
@@ -48,12 +47,12 @@ internal open class CInteropCommonizerTask : AbstractCInteropCommonizerTask() {
@get:OutputDirectories
val allOutputDirectories: Set<File>
get() = getCommonizationParameters().map { outputDirectory(it) }.toSet()
get() = getAllInteropsGroups().map { outputDirectory(it) }.toSet()
@Suppress("unused") // Used for UP-TO-DATE check
@get:Classpath
val commonizedNativeDistributionDependencies: Set<File>
get() = getCommonizationParameters().flatMap { parameters -> parameters.targets }
get() = getAllInteropsGroups().flatMap { group -> group.targets }
.flatMap { target -> project.getNativeDistributionDependencies(target) }
.toSet()
@@ -71,102 +70,80 @@ internal open class CInteropCommonizerTask : AbstractCInteropCommonizerTask() {
this.cinterops += cinterops
}
fun exclude(vararg tasks: CInteropProcess) {
exclude(tasks.map { it.settings.identifier })
}
fun exclude(vararg settings: CInteropSettings) {
exclude(settings.mapNotNull { (it as? DefaultCInteropSettings)?.identifier })
}
internal fun exclude(interopIdentifiers: List<CInteropIdentifier>) {
this.cinterops = this.cinterops.filterTo(mutableSetOf()) { it.identifier !in interopIdentifiers }
}
@TaskAction
internal fun commonizeCInteropLibraries() {
getCommonizationParameters().forEach(::commonize)
getAllInteropsGroups().forEach(::commonize)
}
private fun commonize(parameters: CInteropCommonizationParameters) {
val cinteropsForTarget = cinterops.filter { cinterop -> cinterop.identifier in parameters.interops }
outputDirectory(parameters).deleteRecursively()
private fun commonize(group: CInteropCommonizerGroup) {
val cinteropsForTarget = cinterops.filter { cinterop -> cinterop.identifier in group.interops }
outputDirectory(group).deleteRecursively()
if (cinteropsForTarget.isEmpty()) return
GradleCliCommonizer(project).commonizeLibraries(
konanHome = project.file(project.konanHome),
outputTargets = parameters.targets,
outputTargets = group.targets,
inputLibraries = cinteropsForTarget.map { it.libraryFile.get() }.filter { it.exists() }.toSet(),
dependencyLibraries = getNativeDistributionDependencies(parameters),
outputDirectory = outputDirectory(parameters),
dependencyLibraries = getNativeDistributionDependencies(group),
outputDirectory = outputDirectory(group),
logLevel = project.commonizerLogLevel
)
}
private fun getNativeDistributionDependencies(parameters: CInteropCommonizationParameters): Set<CommonizerDependency> {
return (parameters.targets + parameters.targets.allLeaves()).flatMapTo(mutableSetOf()) { target ->
private fun getNativeDistributionDependencies(group: CInteropCommonizerGroup): Set<CommonizerDependency> {
return (group.targets + group.targets.allLeaves()).flatMapTo(mutableSetOf()) { target ->
project.getNativeDistributionDependencies(target).map { dependency -> TargetedCommonizerDependency(target, dependency) }
}
}
private fun getSharedNativeCInterops(): Set<SharedNativeCInterops> {
val sharedNativeCompilations = (project.multiplatformExtensionOrNull ?: return emptySet())
.targets.flatMap { it.compilations }
.filterIsInstance<KotlinSharedNativeCompilation>()
fun buildSharedNativeCInterops(compilation: KotlinSharedNativeCompilation): SharedNativeCInterops? {
return SharedNativeCInterops(
target = project.getCommonizerTarget(compilation) as? SharedCommonizerTarget ?: return null,
interops = project.getDependingNativeCompilations(compilation)
/* If any dependee native compilation has no interop, then commonization is useless */
.flatMap { nativeCompilation -> nativeCompilation.cinterops.ifEmpty { return null } }
.map { interop -> interop.identifier }
.toSet()
)
}
return sharedNativeCompilations.mapNotNull(::buildSharedNativeCInterops).toSet()
.run(::removeNotRegisteredInterops)
.run(::removeEmptyInterops)
}
@Nested
internal fun getCommonizationParameters(): Set<CInteropCommonizationParameters> {
val sharedNativeCInterops = getSharedNativeCInterops()
if (sharedNativeCInterops.isEmpty()) return emptySet()
return sharedNativeCInterops.distinct()
.filter { potentialRoot -> sharedNativeCInterops.none { other -> potentialRoot isProperSubsetOf other } }
.map { root -> root to sharedNativeCInterops.filter { other -> other isProperSubsetOf root } }
.mapTo(mutableSetOf()) { (root, subsets) ->
CInteropCommonizationParameters(
targets = subsets.mapTo(mutableSetOf()) { it.target } + root.target,
interops = root.interops
)
}
}
override fun getCommonizationParameters(compilation: KotlinSharedNativeCompilation): CInteropCommonizationParameters? {
val supportedParameters = getCommonizationParameters().filter { parameters -> parameters.supports(compilation) }
if (supportedParameters.isEmpty()) return null
assert(supportedParameters.size == 1) {
"Unnecessary work detected: Multiple commonization parameters seem to be doing redundant work"
internal fun getAllInteropsGroups(): Set<CInteropCommonizerGroup> {
val dependents = getAllDependents()
val allScopes = dependents.map { it.scopes }.toSet()
val rootScopes = allScopes.filter { scopes ->
allScopes.none { otherScopes -> otherScopes != scopes && otherScopes.containsAll(scopes) }
}
return supportedParameters.first()
return rootScopes.map { scopes ->
val dependentsForScopes = dependents.filter { dependent ->
scopes.containsAll(dependent.scopes)
}
CInteropCommonizerGroup(
targets = dependentsForScopes.map { it.target }.toSet(),
interops = dependentsForScopes.flatMap { it.interops }.toSet()
)
}.toSet()
}
}
override fun findInteropsGroup(dependent: CInteropCommonizerDependent): CInteropCommonizerGroup? {
val suitableGroups = getAllInteropsGroups().filter { group ->
group.interops.containsAll(dependent.interops) && group.targets.contains(dependent.target)
}
internal fun CInteropCommonizationParameters.supports(
compilation: KotlinSharedNativeCompilation
): Boolean {
val project = compilation.project
val commonizerTargetOfCompilation = project.getCommonizerTarget(compilation) ?: return false
val interopsOfCompilation = project.getDependingNativeCompilations(compilation)
.flatMap { it.cinterops }.map { it.identifier }
assert(suitableGroups.size <= 1) {
"CInteropCommonizerTask: Unnecessary work detected: More than one suitable group found for cinterop dependent."
}
return targets.contains(commonizerTargetOfCompilation) && interops.containsAll(interopsOfCompilation)
return suitableGroups.firstOrNull()
}
@Internal
internal fun getAllDependents(): 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.sourceSets
.mapNotNull { sourceSet -> CInteropCommonizerDependent.from(project, sourceSet) }
.toSet()
return (fromSharedNativeCompilations + fromSourceSets)
}
}
private fun CInteropProcess.toGist(): CInteropGist {
@@ -178,58 +155,3 @@ private fun CInteropProcess.toGist(): CInteropGist {
libraryFile = outputFileProvider
)
}
/**
* Represents a single shared native compilation / shared native source set
* that would rely on given [interops]
*/
internal data class SharedNativeCInterops(
val target: SharedCommonizerTarget,
val interops: Set<CInteropIdentifier>
)
internal infix fun SharedNativeCInterops.isProperSubsetOf(other: SharedNativeCInterops): Boolean {
return target.allLeaves() != other.target.allLeaves() && other.target.allLeaves().containsAll(target.allLeaves())
&& interops != other.interops && other.interops.containsAll(interops)
}
/**
* Represents a single invocation to the commonizer
*/
internal data class CInteropCommonizationParameters(
@get:Input val targets: Set<SharedCommonizerTarget>,
@get:Input val interops: Set<CInteropIdentifier>
)
private fun CInteropCommonizerTask.removeNotRegisteredInterops(
parameters: Set<SharedNativeCInterops>
): Set<SharedNativeCInterops> {
val registeredInterops = this.cinterops.map { it.identifier }
return parameters.mapTo(mutableSetOf()) { params ->
params.copy(interops = params.interops.filterTo(mutableSetOf()) { interop -> interop in registeredInterops })
}
}
private fun removeEmptyInterops(parameters: Set<SharedNativeCInterops>): Set<SharedNativeCInterops> {
return parameters.filterTo(mutableSetOf()) { it.interops.isNotEmpty() }
}
private fun Project.getDependingNativeCompilations(compilation: KotlinSharedNativeCompilation): Set<KotlinNativeCompilation> {
/**
* Some implementations of [KotlinCompilation] do not contain the default source set in
* [KotlinCompilation.kotlinSourceSets] or [KotlinCompilation.allKotlinSourceSets]
* see KT-45412
*/
fun KotlinCompilation<*>.allParticipatingSourceSets(): Set<KotlinSourceSet> {
return kotlinSourceSetsIncludingDefault + kotlinSourceSetsIncludingDefault.resolveAllDependsOnSourceSets()
}
val multiplatformExtension = multiplatformExtensionOrNull ?: return emptySet()
val allParticipatingSourceSetsOfCompilation = compilation.allParticipatingSourceSets()
return multiplatformExtension.targets
.flatMap { target -> target.compilations }
.filterIsInstance<KotlinNativeCompilation>()
.filter { nativeCompilation -> nativeCompilation.allParticipatingSourceSets().containsAll(allParticipatingSourceSetsOfCompilation) }
.toSet()
}
@@ -11,6 +11,7 @@ import org.gradle.api.tasks.Delete
import org.gradle.api.tasks.TaskProvider
import org.jetbrains.kotlin.gradle.internal.isInIdeaSync
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider
import org.jetbrains.kotlin.gradle.plugin.whenEvaluated
import org.jetbrains.kotlin.gradle.tasks.dependsOn
import org.jetbrains.kotlin.gradle.tasks.locateTask
import org.jetbrains.kotlin.gradle.tasks.registerTask
@@ -50,8 +51,11 @@ internal val Project.commonizeCInteropTask: TaskProvider<CInteropCommonizerTask>
return locateOrRegisterTask(
"commonizeCInterop",
invokeWhenRegistered = {
val task = this
commonizeTask.dependsOn(this)
commonizeNativeDistributionTask?.let(this::dependsOn)
whenEvaluated {
commonizeNativeDistributionTask?.let(task::dependsOn)
}
},
configureTask = {
group = "interop"
@@ -7,7 +7,6 @@ package org.jetbrains.kotlin.gradle.targets.native.internal
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.*
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinSharedNativeCompilation
import java.io.File
internal open class CopyCommonizeCInteropForIdeTask : AbstractCInteropCommonizerTask() {
@@ -25,17 +24,17 @@ internal open class CopyCommonizeCInteropForIdeTask : AbstractCInteropCommonizer
override val outputDirectory: File = project.rootDir.resolve(".gradle/kotlin/commonizer")
.resolve(project.path.removePrefix(":").replace(":", "/"))
override fun getCommonizationParameters(compilation: KotlinSharedNativeCompilation): CInteropCommonizationParameters? {
return commonizeCInteropTask.get().getCommonizationParameters(compilation)
override fun findInteropsGroup(dependent: CInteropCommonizerDependent): CInteropCommonizerGroup? {
return commonizeCInteropTask.get().findInteropsGroup(dependent)
}
@TaskAction
protected fun copy() {
outputDirectory.mkdirs()
for (parameters in commonizeCInteropTask.get().getCommonizationParameters()) {
val source = commonizeCInteropTask.get().outputDirectory(parameters)
for (group in commonizeCInteropTask.get().getAllInteropsGroups()) {
val source = commonizeCInteropTask.get().outputDirectory(group)
if (!source.exists()) continue
val target = outputDirectory(parameters)
val target = outputDirectory(group)
if (target.exists()) target.deleteRecursively()
source.copyRecursively(target, true)
}
@@ -85,6 +85,7 @@ private val Project.isNativeDependencyPropagationEnabled: Boolean
internal fun Project.isAllowCommonizer(
kotlinVersion: String = getKotlinPluginVersion()
): Boolean {
assert(state.executed) { "'isAllowCommonizer' can only be called after project evaluation" }
multiplatformExtensionOrNull ?: return false
//register commonizer only for 1.4+, only for HMPP projects
@@ -18,7 +18,7 @@ public inline fun <reified T> transitiveClosure(seed: T, edges: T.() -> Iterable
val initialEdges = seed.edges()
if (initialEdges is Collection && initialEdges.isEmpty()) return emptySet()
val queue = deque<T>()
val queue = deque<T>(initialEdges.count() * 2)
val results = mutableSetOf<T>()
queue.addAll(initialEdges)
while (queue.isNotEmpty()) {
@@ -34,7 +34,7 @@ public inline fun <reified T> transitiveClosure(seed: T, edges: T.() -> Iterable
@OptIn(ExperimentalStdlibApi::class)
@PublishedApi
internal inline fun <reified T> deque(): MutableList<T> {
return if (KotlinVersion.CURRENT.isAtLeast(1, 4)) ArrayDeque()
else mutableListOf()
internal inline fun <reified T> deque(initialSize: Int): MutableList<T> {
return if (KotlinVersion.CURRENT.isAtLeast(1, 4)) ArrayDeque(initialSize)
else ArrayList(initialSize)
}