Implement CInteropCommonizerTask to support commonization of user-defined cinterops

^KT-40975 Verification Pending
This commit is contained in:
sebastian.sellmair
2021-02-16 11:17:04 +01:00
committed by Space
parent 7cf8e9ec58
commit 6efd04edc0
15 changed files with 772 additions and 25 deletions
@@ -0,0 +1,143 @@
/*
* Copyright 2010-2020 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.descriptors.commonizer.CommonizerTarget
import org.jetbrains.kotlin.descriptors.commonizer.SharedCommonizerTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
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.commonizeCInteropTask
import org.jetbrains.kotlin.konan.target.KonanTarget.*
import kotlin.test.*
class CInteropCommonizerTaskTest {
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
}
@Test
fun `nativeMain linux macos`() {
val linuxInterop = kotlin.linuxX64("linux").compilations.getByName("main").cinterops.create("anyInteropName")
val macosInterop = kotlin.macosX64("macos").compilations.getByName("main").cinterops.create("anyInteropName")
val commonMain = kotlin.sourceSets.getByName("commonMain")
val nativeMain = kotlin.sourceSets.create("nativeMain")
val linuxMain = kotlin.sourceSets.getByName("linuxMain")
val macosMain = kotlin.sourceSets.getByName("macosMain")
nativeMain.dependsOn(commonMain)
linuxMain.dependsOn(nativeMain)
macosMain.dependsOn(nativeMain)
project.evaluate()
val nativeMainCompilation = kotlin.targets.flatMap { it.compilations }
.filterIsInstance<KotlinSharedNativeCompilation>()
.single { it.defaultSourceSet == nativeMain }
assertEquals(
CInteropCommonizationParameters(
CommonizerTarget(LINUX_X64, MACOS_X64), setOf(linuxInterop.identifier, macosInterop.identifier)
),
task.findSupportingCommonizationParameters(nativeMainCompilation)
)
}
@Test
fun `nativeMain linux macos (no macos interop defined)`() {
kotlin.linuxX64("linux").compilations.getByName("main").cinterops.create("anyInteropName")
kotlin.macosX64("macos")
val commonMain = kotlin.sourceSets.getByName("commonMain")
val nativeMain = kotlin.sourceSets.create("nativeMain")
val linuxMain = kotlin.sourceSets.getByName("linuxMain")
val macosMain = kotlin.sourceSets.getByName("macosMain")
nativeMain.dependsOn(commonMain)
linuxMain.dependsOn(nativeMain)
macosMain.dependsOn(nativeMain)
project.evaluate()
val nativeMainCompilation = kotlin.targets.flatMap { it.compilations }
.filterIsInstance<KotlinSharedNativeCompilation>()
.single { it.defaultSourceSet == nativeMain }
assertNull(
task.findSupportingCommonizationParameters(nativeMainCompilation),
"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
val macosInterop = kotlin.macosX64("macos").compilations.getByName("main").cinterops.create("anyInteropName").identifier
val iosX64Interop = kotlin.iosX64("iosX64").compilations.getByName("main").cinterops.create("anyInteropName").identifier
val iosArm64Interop = kotlin.iosArm64("iosArm64").compilations.getByName("main").cinterops.create("anyInteropName").identifier
val commonMain = kotlin.sourceSets.getByName("commonMain")
val nativeMain = kotlin.sourceSets.create("nativeMain")
val iosMain = kotlin.sourceSets.create("iosMain")
val linuxMain = kotlin.sourceSets.getByName("linuxMain")
val macosMain = kotlin.sourceSets.getByName("macosMain")
val iosX64Main = kotlin.sourceSets.getByName("iosX64Main")
val iosArm64Main = kotlin.sourceSets.getByName("iosArm64Main")
nativeMain.dependsOn(commonMain)
linuxMain.dependsOn(nativeMain)
macosMain.dependsOn(nativeMain)
iosMain.dependsOn(nativeMain)
iosX64Main.dependsOn(iosMain)
iosArm64Main.dependsOn(iosMain)
project.evaluate()
assertEquals(
CInteropCommonizationParameters(
SharedCommonizerTarget(CommonizerTarget(IOS_X64, IOS_ARM64), CommonizerTarget(MACOS_X64), CommonizerTarget(LINUX_X64)),
setOf(linuxInterop, macosInterop, iosX64Interop, iosArm64Interop)
), task.findSupportingCommonizationParameters(sharedNativeCompilation(nativeMain))
)
assertEquals(
CInteropCommonizationParameters(
SharedCommonizerTarget(CommonizerTarget(IOS_X64, IOS_ARM64), CommonizerTarget(MACOS_X64), CommonizerTarget(LINUX_X64)),
setOf(linuxInterop, macosInterop, iosX64Interop, iosArm64Interop)
), task.findSupportingCommonizationParameters(sharedNativeCompilation(iosMain))
)
assertTrue(
task.findSupportingCommonizationParameters(sharedNativeCompilation(iosMain))!! in
task.findSupportingCommonizationParameters(sharedNativeCompilation(nativeMain))!!,
"Expected CInteropCommonizerTarget of iosMain to be fully contained in nativeMain"
)
}
private fun sharedNativeCompilation(sourceSet: KotlinSourceSet): KotlinSharedNativeCompilation {
return kotlin.targets.flatMap { it.compilations }.filterIsInstance<KotlinSharedNativeCompilation>()
.single { it.defaultSourceSet == sourceSet }
}
}
@@ -0,0 +1,166 @@
/*
* Copyright 2010-2020 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.Project
import org.gradle.testfixtures.ProjectBuilder
import org.jetbrains.kotlin.descriptors.commonizer.CommonizerTarget
import org.jetbrains.kotlin.descriptors.commonizer.SharedCommonizerTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.targets.native.internal.getCommonizerTarget
import org.jetbrains.kotlin.konan.target.KonanTarget.*
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNull
class SourceSetCommonizerTargetTest {
private lateinit var project: Project
private lateinit var kotlin: KotlinMultiplatformExtension
@BeforeTest
fun setup() {
project = ProjectBuilder.builder().build()
project.plugins.apply("kotlin-multiplatform")
kotlin = project.extensions.getByName("kotlin") as KotlinMultiplatformExtension
}
@Test
fun `linux macos`() {
kotlin.linuxX64("linux")
kotlin.macosX64("macos")
val commonMain = kotlin.sourceSets.getByName("commonMain")
val commonTest = kotlin.sourceSets.getByName("commonTest")
val linuxMain = kotlin.sourceSets.getByName("linuxMain")
val macosMain = kotlin.sourceSets.getByName("macosMain")
val linuxTest = kotlin.sourceSets.getByName("linuxTest")
val macosTest = kotlin.sourceSets.getByName("macosTest")
assertEquals(CommonizerTarget(LINUX_X64), project.getCommonizerTarget(linuxMain))
assertEquals(CommonizerTarget(LINUX_X64), project.getCommonizerTarget(linuxTest))
assertEquals(CommonizerTarget(MACOS_X64), project.getCommonizerTarget(macosMain))
assertEquals(CommonizerTarget(MACOS_X64), project.getCommonizerTarget(macosTest))
assertEquals(CommonizerTarget(LINUX_X64, MACOS_X64), project.getCommonizerTarget(commonMain))
assertEquals(CommonizerTarget(LINUX_X64, MACOS_X64), project.getCommonizerTarget(commonTest))
}
@Test
fun `nativeMain linux macos`() {
kotlin.linuxX64("linux")
kotlin.macosX64("macos")
val commonMain = kotlin.sourceSets.getByName("commonMain")
val commonTest = kotlin.sourceSets.getByName("commonTest")
val nativeMain = kotlin.sourceSets.create("nativeMain")
val linuxMain = kotlin.sourceSets.getByName("linuxMain")
val macosMain = kotlin.sourceSets.getByName("macosMain")
val linuxTest = kotlin.sourceSets.getByName("linuxTest")
val macosTest = kotlin.sourceSets.getByName("macosTest")
nativeMain.dependsOn(commonMain)
linuxMain.dependsOn(nativeMain)
macosMain.dependsOn(nativeMain)
assertEquals(CommonizerTarget(LINUX_X64), project.getCommonizerTarget(linuxMain))
assertEquals(CommonizerTarget(LINUX_X64), project.getCommonizerTarget(linuxTest))
assertEquals(CommonizerTarget(MACOS_X64), project.getCommonizerTarget(macosMain))
assertEquals(CommonizerTarget(MACOS_X64), project.getCommonizerTarget(macosTest))
assertEquals(CommonizerTarget(LINUX_X64, MACOS_X64), project.getCommonizerTarget(nativeMain))
assertEquals(CommonizerTarget(LINUX_X64, MACOS_X64), project.getCommonizerTarget(commonMain))
assertEquals(CommonizerTarget(LINUX_X64, MACOS_X64), project.getCommonizerTarget(commonTest))
}
@Test
fun `nativeMain iosMain linux macos iosX64 iosArm64`() {
kotlin.linuxX64("linux")
kotlin.macosX64("macos")
kotlin.iosX64("iosX64")
kotlin.iosArm64("iosArm64")
val commonMain = kotlin.sourceSets.getByName("commonMain")
val commonTest = kotlin.sourceSets.getByName("commonTest")
val nativeMain = kotlin.sourceSets.create("nativeMain")
val iosMain = kotlin.sourceSets.create("iosMain")
val linuxMain = kotlin.sourceSets.getByName("linuxMain")
val macosMain = kotlin.sourceSets.getByName("macosMain")
val linuxTest = kotlin.sourceSets.getByName("linuxTest")
val macosTest = kotlin.sourceSets.getByName("macosTest")
val iosX64Main = kotlin.sourceSets.getByName("iosX64Main")
val iosX64Test = kotlin.sourceSets.getByName("iosX64Test")
val iosArm64Main = kotlin.sourceSets.getByName("iosArm64Main")
val iosArm64Test = kotlin.sourceSets.getByName("iosArm64Test")
nativeMain.dependsOn(commonMain)
linuxMain.dependsOn(nativeMain)
macosMain.dependsOn(nativeMain)
iosMain.dependsOn(nativeMain)
iosX64Main.dependsOn(iosMain)
iosArm64Main.dependsOn(iosMain)
assertEquals(CommonizerTarget(LINUX_X64), project.getCommonizerTarget(linuxMain))
assertEquals(CommonizerTarget(LINUX_X64), project.getCommonizerTarget(linuxTest))
assertEquals(CommonizerTarget(MACOS_X64), project.getCommonizerTarget(macosMain))
assertEquals(CommonizerTarget(MACOS_X64), project.getCommonizerTarget(macosTest))
assertEquals(CommonizerTarget(IOS_X64), project.getCommonizerTarget(iosX64Test))
assertEquals(CommonizerTarget(IOS_X64), project.getCommonizerTarget(iosX64Test))
assertEquals(CommonizerTarget(IOS_ARM64), project.getCommonizerTarget(iosArm64Main))
assertEquals(CommonizerTarget(IOS_ARM64), project.getCommonizerTarget(iosArm64Test))
assertEquals(CommonizerTarget(IOS_X64, IOS_ARM64), project.getCommonizerTarget(iosMain))
assertEquals(
SharedCommonizerTarget(CommonizerTarget(IOS_X64, IOS_ARM64), CommonizerTarget(MACOS_X64), CommonizerTarget(LINUX_X64)),
project.getCommonizerTarget(nativeMain)
)
assertEquals(
SharedCommonizerTarget(CommonizerTarget(IOS_X64, IOS_ARM64), CommonizerTarget(MACOS_X64), CommonizerTarget(LINUX_X64)),
project.getCommonizerTarget(commonMain)
)
/* No test hierarchy declared */
assertEquals(
CommonizerTarget(IOS_ARM64, IOS_X64, LINUX_X64, MACOS_X64),
project.getCommonizerTarget(commonTest)
)
}
@Test
fun `nativeMain linux macos jvm`() {
kotlin.linuxX64("linux")
kotlin.macosX64("macos")
kotlin.jvm("jvm")
val commonMain = kotlin.sourceSets.getByName("commonMain")
val commonTest = kotlin.sourceSets.getByName("commonTest")
val nativeMain = kotlin.sourceSets.create("nativeMain")
val linuxMain = kotlin.sourceSets.getByName("linuxMain")
val macosMain = kotlin.sourceSets.getByName("macosMain")
val linuxTest = kotlin.sourceSets.getByName("linuxTest")
val macosTest = kotlin.sourceSets.getByName("macosTest")
val jvmMain = kotlin.sourceSets.getByName("jvmMain")
val jvmTest = kotlin.sourceSets.getByName("jvmTest")
nativeMain.dependsOn(commonMain)
linuxMain.dependsOn(nativeMain)
macosMain.dependsOn(nativeMain)
assertEquals(CommonizerTarget(LINUX_X64), project.getCommonizerTarget(linuxMain))
assertEquals(CommonizerTarget(LINUX_X64), project.getCommonizerTarget(linuxTest))
assertEquals(CommonizerTarget(MACOS_X64), project.getCommonizerTarget(macosMain))
assertEquals(CommonizerTarget(MACOS_X64), project.getCommonizerTarget(macosTest))
assertNull(project.getCommonizerTarget(jvmMain), "Expected jvmMain to have no commonizer target")
assertNull(project.getCommonizerTarget(jvmTest), "Expected jvmTest to have no commonizer target")
assertEquals(CommonizerTarget(LINUX_X64, MACOS_X64), project.getCommonizerTarget(nativeMain))
assertNull(project.getCommonizerTarget(commonMain), "Expected commonMain to have no commonizer target")
assertNull(project.getCommonizerTarget(commonTest), "Expected commonTest to have no commonizer target")
}
}
@@ -206,6 +206,9 @@ internal class PropertiesProvider private constructor(private val project: Proje
val commonizerJvmArgs: String?
get() = property("kotlin.commonizer.jvmArgs")
val enableCInteropCommonization: Boolean
get() = booleanProperty("kotlin.mpp.enableCInteropCommonization") ?: false
/**
* Dependencies caching strategy for all targets that support caches.
*/
@@ -300,4 +303,4 @@ internal class PropertiesProvider private constructor(private val project: Proje
?: PropertiesProvider(project) // Fallback if multiple class loaders are involved
}
}
}
}
@@ -42,6 +42,14 @@ internal fun KotlinCompilation<*>.composeName(prefix: String? = null, suffix: St
internal fun KotlinCompilation<*>.isMain(): Boolean =
name == KotlinCompilation.MAIN_COMPILATION_NAME
/**
* see https://youtrack.jetbrains.com/issue/KT-45412
* Some implementations of [KotlinCompilation] are not including their [KotlinCompilation.defaultSourceSet] into [kotlinSourceSet]s
* This helper function might disappear in the future, once the behaviour of those [KotlinCompilation] implementations is streamlined.
* @return [KotlinCompilation.kotlinSourceSets] + [KotlinCompilation.defaultSourceSet]
*/
internal val KotlinCompilation<*>.kotlinSourceSetsIncludingDefault: Set<KotlinSourceSet> get() = kotlinSourceSets + defaultSourceSet
abstract class AbstractKotlinCompilation<T : KotlinCommonOptions>(
target: KotlinTarget,
override val compilationName: String
@@ -23,10 +23,8 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.plugin.mpp.CompilationSourceSetUtil.compilationsBySourceSets
import org.jetbrains.kotlin.gradle.plugin.sources.*
import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService
import org.jetbrains.kotlin.gradle.targets.native.internal.commonizeCInteropTask
import org.jetbrains.kotlin.gradle.tasks.*
import org.jetbrains.kotlin.gradle.tasks.KotlinTasksProvider
import org.jetbrains.kotlin.gradle.tasks.locateTask
import org.jetbrains.kotlin.gradle.tasks.registerTask
import org.jetbrains.kotlin.gradle.utils.addExtendsFromRelation
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import org.jetbrains.kotlin.statistics.metrics.BooleanMetrics
@@ -293,7 +291,9 @@ class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) :
val platformCompilations = compilationsBySourceSets(project).getValue(sourceSet)
val isNativeSourceSet = platformCompilations.all { compilation -> compilation.target is KotlinNativeTarget }
val isNativeSourceSet = platformCompilations.all { compilation ->
compilation.target is KotlinNativeTarget || compilation is KotlinSharedNativeCompilation
}
val compilationFactory: KotlinCompilationFactory<out AbstractKotlinCompilation<*>> = when {
isNativeSourceSet -> KotlinSharedNativeCompilationFactory(
@@ -320,6 +320,16 @@ class KotlinMetadataTargetConfigurator(kotlinPluginVersion: String) :
compileDependencyFiles = project.files()
}
}
if (this is KotlinSharedNativeCompilation) {
project.commonizeCInteropTask?.let { commonizeCInteropTask ->
val commonizerOutput = project.files(commonizeCInteropTask.map { it.getLibraries(this) })
compileDependencyFiles = compileDependencyFiles.plus(commonizerOutput)
kotlinSourceSetsIncludingDefault.forEach { sourceSet ->
project.dependencies.add(sourceSet.implementationMetadataConfigurationName, commonizerOutput)
}
}
}
}
}
@@ -15,6 +15,7 @@ import org.gradle.api.provider.Property
import org.gradle.util.ConfigureUtil
import org.jetbrains.kotlin.gradle.plugin.CInteropSettings
import org.jetbrains.kotlin.gradle.plugin.CInteropSettings.IncludeDirectories
import org.jetbrains.kotlin.gradle.targets.native.internal.CInteropIdentifier
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import java.io.File
import javax.inject.Inject
@@ -42,6 +43,9 @@ open class DefaultCInteropSettings @Inject constructor(
override fun getName(): String = name
internal val identifier: CInteropIdentifier
get() = CInteropIdentifier(compilation.compileKotlinTaskName, name)
val target: KotlinNativeTarget
get() = compilation.target
@@ -31,6 +31,7 @@ import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.Companion.KOTLIN_NA
import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.targets.metadata.isKotlinGranularMetadataEnabled
import org.jetbrains.kotlin.gradle.targets.native.*
import org.jetbrains.kotlin.gradle.targets.native.internal.commonizeCInteropTask
import org.jetbrains.kotlin.gradle.targets.native.tasks.KotlinNativeHostTest
import org.jetbrains.kotlin.gradle.targets.native.tasks.KotlinNativeSimulatorTest
import org.jetbrains.kotlin.gradle.targets.native.tasks.KotlinNativeTest
@@ -307,6 +308,10 @@ open class KotlinNativeTargetConfigurator<T : KotlinNativeTarget>(
it.enabled = compilation.konanTarget.enabledOnCurrentHost
}
project.commonizeCInteropTask?.configure { commonizeCInteropTask ->
commonizeCInteropTask.from((interopTask.get()))
}
val interopOutput = project.files(interopTask.map { it.outputFileProvider })
with(compilation) {
// Register the interop library as a dependency of the compilation to make IDE happy.
@@ -0,0 +1,230 @@
/*
* 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.DefaultTask
import org.gradle.api.Project
import org.gradle.api.file.FileCollection
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.*
import org.gradle.workers.WorkParameters
import org.jetbrains.kotlin.compilerRunner.GradleCliCommonizer
import org.jetbrains.kotlin.compilerRunner.konanHome
import org.jetbrains.kotlin.descriptors.commonizer.CommonizerTarget
import org.jetbrains.kotlin.descriptors.commonizer.HierarchicalCommonizerOutputLayout
import org.jetbrains.kotlin.descriptors.commonizer.SharedCommonizerTarget
import org.jetbrains.kotlin.descriptors.commonizer.prettyName
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
import org.jetbrains.kotlin.gradle.targets.native.internal.CInteropCommonizerTask.CInteropGist
import org.jetbrains.kotlin.gradle.tasks.CInteropProcess
import org.jetbrains.kotlin.konan.target.KonanTarget
import java.io.File
internal open class CInteropCommonizerTask : DefaultTask() {
internal data class CInteropGist(
@get:Input val identifier: CInteropIdentifier,
@get:Input val konanTarget: KonanTarget,
@get:Internal val sourceSets: Provider<Set<KotlinSourceSet>>,
@get:Classpath val libraryFile: Provider<File>,
@get:Classpath val dependencies: FileCollection
) {
@Suppress("unused") // Used for UP-TO-DATE check
@get:Input
val allSourceSetNames: Provider<List<String>> = sourceSets.map { it.resolveAllDependsOnSourceSets().map(Any::toString) }
}
@get:Nested
internal var cinterops = setOf<CInteropGist>()
private set
@OutputDirectories
fun getAllOutputDirectories(): Set<File> {
return getCommonizationParameters().map { outputDirectory(it) }.toSet()
}
fun from(vararg tasks: CInteropProcess) = from(
tasks.toList()
.onEach { task -> this.dependsOn(task) }
.map { task -> task.toGist() }
)
internal fun from(vararg cinterop: CInteropGist) {
from(cinterop.toList())
}
internal fun from(cinterops: List<CInteropGist>) {
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)
}
private fun commonize(parameters: CInteropCommonizationParameters) {
val cinteropsForTarget = cinterops.filter { cinterop -> cinterop.identifier in parameters.interops }
outputDirectory(parameters).deleteRecursively()
if (cinteropsForTarget.isEmpty()) return
GradleCliCommonizer(project).commonizeLibraries(
konanHome = project.file(project.konanHome),
outputCommonizerTarget = parameters.commonizerTarget,
inputLibraries = cinteropsForTarget.map { it.libraryFile.get() }.toSet(),
dependencyLibraries = emptySet(),/*cinteropsForTarget.flatMap { it.dependencies.files }.toSet() */ // TODO pass real dependencies
outputDirectory = outputDirectory(parameters)
)
}
private fun outputDirectory(parameters: CInteropCommonizationParameters): File {
return project.buildDir.resolve("classes/kotlin/commonizer")
.resolve(parameters.commonizerTarget.prettyName)
.resolve(parameters.interops.map { it.interopName }.distinct().joinToString("-"))
}
internal fun getLibraries(compilation: KotlinSharedNativeCompilation): FileCollection {
val fileProvider = project.provider<Set<File>> {
val parameters = findSupportingCommonizationParameters(compilation) ?: return@provider emptySet()
HierarchicalCommonizerOutputLayout
.getTargetDirectory(outputDirectory(parameters), parameters.commonizerTarget)
.listFiles().orEmpty().toSet()
}
return project.files(fileProvider) { fileCollection ->
fileCollection.builtBy(this)
}
}
@Nested
internal fun getCommonizationParameters(): Set<CInteropCommonizationParameters> {
val sharedNativeCompilations = (project.multiplatformExtensionOrNull ?: return emptySet())
.targets.flatMap { it.compilations }
.filterIsInstance<KotlinSharedNativeCompilation>()
fun getCommonizationParameters(compilation: KotlinSharedNativeCompilation): CInteropCommonizationParameters? {
return CInteropCommonizationParameters(
commonizerTarget = project.getCommonizerTarget(compilation) as? SharedCommonizerTarget ?: return null,
interops = project.getDependingNativeCompilations(compilation)
/* If a depending native compilation has no interop, then commonization is useless */
.flatMap { nativeCompilation -> nativeCompilation.cinterops.ifEmpty { return null } }
.map { interop -> interop.identifier }
.toSet()
)
}
return sharedNativeCompilations.mapNotNull(::getCommonizationParameters).toSet()
.run(::removeNotRegisteredInterops)
.run(::removeEmptyInterops)
.run(::removeRedundantParameters)
}
internal fun findSupportingCommonizationParameters(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"
}
return supportedParameters.first()
}
private fun CInteropCommonizationParameters.supports(
compilation: KotlinSharedNativeCompilation
): Boolean {
val registeredInterops = cinterops.map { it.identifier }
val commonizerTargetOfCompilation = project.getCommonizerTarget(compilation) ?: return false
val interopsOfCompilation = project.getDependingNativeCompilations(compilation)
.flatMap { it.cinterops }.map { it.identifier }
.filter { interop -> interop in registeredInterops }
return commonizerTarget.contains(commonizerTargetOfCompilation) && interops.containsAll(interopsOfCompilation)
}
}
private fun CInteropProcess.toGist(): CInteropGist {
return CInteropGist(
identifier = settings.identifier,
konanTarget = konanTarget,
sourceSets = project.provider { settings.compilation.kotlinSourceSetsIncludingDefault },
libraryFile = outputFileProvider,
dependencies = project.files(project.provider { settings.dependencyFiles })
)
}
internal data class CInteropCommonizationParameters(
@get:Input val commonizerTarget: SharedCommonizerTarget,
@get:Input val interops: Set<CInteropIdentifier>
) : WorkParameters {
operator fun contains(other: CInteropCommonizationParameters) =
this.interops.containsAll(other.interops) && this.commonizerTarget.contains(other.commonizerTarget)
}
private fun CInteropCommonizerTask.removeNotRegisteredInterops(
parameters: Set<CInteropCommonizationParameters>
): Set<CInteropCommonizationParameters> {
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<CInteropCommonizationParameters>): Set<CInteropCommonizationParameters> {
return parameters.filterTo(mutableSetOf()) { it.interops.isNotEmpty() }
}
private fun removeRedundantParameters(parameters: Set<CInteropCommonizationParameters>): Set<CInteropCommonizationParameters> {
return parameters.filterNotTo(mutableSetOf()) { current ->
parameters.any { other ->
other !== current && current in other
}
}
}
private operator fun CommonizerTarget.contains(other: CommonizerTarget): Boolean {
if (this == other) return true
if (this !is SharedCommonizerTarget) return false
return targets.any { child -> other in child }
}
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()
}
@@ -0,0 +1,20 @@
/*
* 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
/**
* Project unique identifier for all cinterops
*/
data class CInteropIdentifier internal constructor(
val compilationTaskName: String,
val cinteropName: String
) {
@get:Input
val uniqueName: String = "cinterop:$compilationTaskName/$cinteropName"
override fun toString(): String = uniqueName
}
@@ -0,0 +1,76 @@
/*
* 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.Task
import org.gradle.api.tasks.TaskProvider
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider
import org.jetbrains.kotlin.gradle.tasks.dependsOn
import org.jetbrains.kotlin.gradle.tasks.locateTask
import org.jetbrains.kotlin.gradle.tasks.registerTask
internal val Project.isCInteropCommonizationEnabled: Boolean get() = PropertiesProvider(this).enableCInteropCommonization
internal val Project.commonizeTask: TaskProvider<Task>
get() = locateOrRegisterTask(
"commonize",
invokeWhenRegistered = { @Suppress("deprecation") runCommonizerTask.dependsOn(this) },
configureTask = {
group = "interop"
description = "Aggregator task for all c-interop & Native distribution commonizer tasks"
}
)
/**
* Keeping this task/task name for IDE compatibility which is invoking 'runCommonizer' during sync
*/
@Deprecated("Use 'commonizeTask' instead. Keeping the task for IDE compatibility", replaceWith = ReplaceWith("commonizeTask"))
internal val Project.runCommonizerTask: TaskProvider<Task>
get() = locateOrRegisterTask(
"runCommonizer",
invokeWhenRegistered = { dependsOn(commonizeTask) },
configureTask = {
group = "interop"
description = "[Deprecated: Use 'commonize' instead]"
}
)
internal val Project.commonizeCInteropTask: TaskProvider<CInteropCommonizerTask>?
get() {
if (isCInteropCommonizationEnabled) {
return locateOrRegisterTask(
"commonizeCInterop",
invokeWhenRegistered = { commonizeTask.dependsOn(this) },
configureTask = {
group = "interop"
description = "Invokes the commonizer on c-interop bindings of the project"
}
)
}
return null
}
internal val Project.commonizeNativeDistributionTask: TaskProvider<NativeDistributionCommonizerTask>
get() = locateOrRegisterTask(
"commonizeNativeDistribution",
invokeWhenRegistered = { commonizeTask.dependsOn(this) },
configureTask = {
group = "interop"
description = "Invokes the commonizer on the platform libraries provided by the Kotlin/Native distribution"
}
)
private inline fun <reified T : Task> Project.locateOrRegisterTask(
name: String,
args: List<Any> = emptyList(),
invokeWhenRegistered: (TaskProvider<T>.() -> Unit) = {},
noinline configureTask: (T.() -> Unit)? = null
): TaskProvider<T> {
locateTask<T>(name)?.let { return it }
return registerTask(name, args, configureTask).also(invokeWhenRegistered)
}
@@ -0,0 +1,25 @@
/*
* Copyright 2010-2020 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.descriptors.commonizer.CommonizerTarget
import org.jetbrains.kotlin.descriptors.commonizer.LeafCommonizerTarget
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinSharedNativeCompilation
internal fun Project.getCommonizerTarget(compilation: KotlinCompilation<*>): CommonizerTarget? {
if (compilation is KotlinNativeCompilation) {
return LeafCommonizerTarget(compilation.konanTarget)
}
if (compilation is KotlinSharedNativeCompilation) {
return getCommonizerTarget(compilation.defaultSourceSet) ?: CommonizerTarget(compilation.konanTargets)
}
return null
}
@@ -0,0 +1,70 @@
/*
* Copyright 2010-2020 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.descriptors.commonizer.CommonizerTarget
import org.jetbrains.kotlin.descriptors.commonizer.LeafCommonizerTarget
import org.jetbrains.kotlin.descriptors.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.KotlinSourceSetContainer
import org.jetbrains.kotlin.gradle.plugin.mpp.CompilationSourceSetUtil.compilationsBySourceSets
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinSharedNativeCompilation
import org.jetbrains.kotlin.konan.target.KonanTarget
internal fun Project.getCommonizerTarget(sourceSet: KotlinSourceSet): CommonizerTarget? {
val kotlin = multiplatformExtensionOrNull ?: return null
val sourceSetsDependingOnThisSourceSet = kotlin.resolveSourceSetsDirectlyDependingOn(sourceSet)
if (sourceSetsDependingOnThisSourceSet.isEmpty()) {
return getLeafCommonizerTarget(sourceSet)
}
val dependeeCommonizerTargets = sourceSetsDependingOnThisSourceSet
.map { sourceSetDependingOnThisSourceSet -> getCommonizerTarget(sourceSetDependingOnThisSourceSet) ?: return null }
.toSet()
return when {
dependeeCommonizerTargets.isEmpty() -> throw IllegalStateException()
dependeeCommonizerTargets.size == 1 -> dependeeCommonizerTargets.single()
else -> SharedCommonizerTarget(dependeeCommonizerTargets)
}
}
private fun Project.getLeafCommonizerTarget(sourceSet: KotlinSourceSet): LeafCommonizerTarget? {
val konanTargets = compilationsBySourceSets(this)[sourceSet].orEmpty()
.flatMap { compilation -> compilation.konanTargets }
return when {
konanTargets.isEmpty() -> null
konanTargets.size == 1 -> LeafCommonizerTarget(konanTargets.single())
else -> error("Source set ${sourceSet.name} is not a leaf source set. konanTargets=$konanTargets")
}
}
private fun KotlinSourceSetContainer.resolveSourceSetsDirectlyDependingOn(sourceSet: KotlinSourceSet): Set<KotlinSourceSet> {
val dependeeSourceSets = sourceSets
.filter { candidateSourceSet -> candidateSourceSet != sourceSet }
.filter { candidateSourceSet -> sourceSet in candidateSourceSet.dependsOn }
fun isTransitiveDependee(sourceSet: KotlinSourceSet): Boolean {
return sourceSet.dependsOn.any { it in dependeeSourceSets || isTransitiveDependee(it) }
}
return dependeeSourceSets.filterNot(::isTransitiveDependee).toSet()
}
private val KotlinCompilation<*>.konanTargets: Set<KonanTarget>
get() {
return when (this) {
is KotlinSharedNativeCompilation -> konanTargets.toSet()
is KotlinNativeCompilation -> setOf(konanTarget)
else -> emptySet()
}
}
@@ -10,19 +10,13 @@ import org.gradle.api.file.FileCollection
import org.jetbrains.kotlin.compilerRunner.konanHome
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider
import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.Companion.KOTLIN_NATIVE_HOME
import org.jetbrains.kotlin.gradle.plugin.compareVersionNumbers
import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion
import org.jetbrains.kotlin.gradle.plugin.mpp.CompilationSourceSetUtil
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeCompilation
import org.jetbrains.kotlin.gradle.targets.metadata.getMetadataCompilationForSourceSet
import org.jetbrains.kotlin.gradle.targets.metadata.isKotlinGranularMetadataEnabled
import org.jetbrains.kotlin.gradle.targets.native.internal.NativePlatformDependency.*
import org.jetbrains.kotlin.gradle.tasks.registerTask
import org.jetbrains.kotlin.gradle.tasks.withType
import org.jetbrains.kotlin.gradle.utils.SingleWarningPerBuild
import org.jetbrains.kotlin.konan.library.*
import org.jetbrains.kotlin.konan.target.KonanTarget
@@ -105,16 +99,10 @@ private class NativePlatformDependencyResolver(val project: Project, val kotlinV
check(!alreadyResolved)
alreadyResolved = true
val targetGroups: List<CommonizedCommon> = dependencies.keys.filterIsInstance<CommonizedCommon>()
val commonizerTaskProvider = project.registerTask(
COMMONIZER_TASK_NAME,
CommonizerTask::class.java
) { commonizerTask ->
commonizerTask.targetGroups = targetGroups.map { it.targets }.toSet()
project.commonizeNativeDistributionTask.configure { commonizerTask ->
commonizerTask.targetGroups = dependencies.keys.filterIsInstance<CommonizedCommon>().map { it.targets }.toSet()
}
// then, resolve dependencies one by one
dependencies.forEach { (dependency, actions) ->
val fileCollection = when (dependency) {
@@ -144,7 +132,7 @@ private class NativePlatformDependencyResolver(val project: Project, val kotlinV
val commonizedLibsDir = project.nativeDistributionCommonizerOutputDirectory(dependency.targets)
project.files(Callable {
libsInCommonDir(commonizedLibsDir)
}).builtBy(commonizerTaskProvider)
}).builtBy(project.commonizeNativeDistributionTask)
}
@@ -153,7 +141,7 @@ private class NativePlatformDependencyResolver(val project: Project, val kotlinV
val commonizedLibsDir = project.nativeDistributionCommonizerOutputDirectory(dependency.common.targets)
project.files(Callable {
libsInPlatformDir(commonizedLibsDir, dependency.target) + libsInCommonDir(commonizedLibsDir)
}).builtBy(commonizerTaskProvider)
}).builtBy(project.commonizeNativeDistributionTask)
}
}
@@ -25,11 +25,10 @@ import java.nio.file.attribute.*
import java.time.*
import java.util.*
internal const val COMMONIZER_TASK_NAME = "runCommonizer"
internal typealias KonanTargetGroup = Set<KonanTarget>
internal open class CommonizerTask : DefaultTask() {
internal open class NativeDistributionCommonizerTask : DefaultTask() {
private val konanHome = project.file(project.konanHome)
@@ -13,7 +13,7 @@ import java.io.Serializable
// N.B. TargetPlatform/SimplePlatform are non exhaustive enough to address both target platforms such as
// JVM, JS and concrete Kotlin/Native targets, e.g. macos_x64, ios_x64, linux_x64.
public sealed class CommonizerTarget : Serializable {
final override fun toString(): String = identityString
final override fun toString(): String = prettyName
}
public data class LeafCommonizerTarget public constructor(val name: String) : CommonizerTarget() {