[Gradle] Implement CInteropCommonizerDependent factories as safe suspend functions

^KT-34662 Verification Pending
This commit is contained in:
Sebastian Sellmair
2023-03-13 14:48:00 +01:00
committed by Space Team
parent 9517667081
commit b064493c7b
12 changed files with 109 additions and 108 deletions
@@ -18,6 +18,7 @@ import org.jetbrains.kotlin.gradle.targets.native.internal.CInteropCommonizerDep
import org.jetbrains.kotlin.gradle.targets.native.internal.from import org.jetbrains.kotlin.gradle.targets.native.internal.from
import org.jetbrains.kotlin.gradle.targets.native.internal.isAllowCommonizer import org.jetbrains.kotlin.gradle.targets.native.internal.isAllowCommonizer
import org.jetbrains.kotlin.gradle.utils.findAppliedAndroidPluginIdOrNull import org.jetbrains.kotlin.gradle.utils.findAppliedAndroidPluginIdOrNull
import org.jetbrains.kotlin.gradle.utils.future
import org.jetbrains.kotlin.gradle.utils.runProjectConfigurationHealthCheck import org.jetbrains.kotlin.gradle.utils.runProjectConfigurationHealthCheck
private class KotlinMultiplatformProjectConfigurationException(message: String) : Exception(message) private class KotlinMultiplatformProjectConfigurationException(message: String) : Exception(message)
@@ -77,7 +78,10 @@ internal fun Project.runDisabledCInteropCommonizationOnHmppProjectConfigurationH
val sharedCompilationsWithInterops = multiplatformExtension.targets.flatMap { it.compilations } val sharedCompilationsWithInterops = multiplatformExtension.targets.flatMap { it.compilations }
.filterIsInstance<KotlinSharedNativeCompilation>() .filterIsInstance<KotlinSharedNativeCompilation>()
.mapNotNull { compilation -> compilation to (CInteropCommonizerDependent.from(compilation) ?: return@mapNotNull null) } .mapNotNull { compilation ->
val cinteropDependent = future { CInteropCommonizerDependent.from(compilation) }.getOrThrow() ?: return@mapNotNull null
compilation to cinteropDependent
}
.toMap() .toMap()
val affectedCompilations = sharedCompilationsWithInterops.keys val affectedCompilations = sharedCompilationsWithInterops.keys
@@ -21,6 +21,7 @@ import org.jetbrains.kotlin.gradle.plugin.extraProperties
import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets.MetadataProvider.ArtifactMetadataProvider import org.jetbrains.kotlin.gradle.plugin.mpp.MetadataDependencyResolution.ChooseVisibleSourceSets.MetadataProvider.ArtifactMetadataProvider
import org.jetbrains.kotlin.gradle.plugin.sources.internal import org.jetbrains.kotlin.gradle.plugin.sources.internal
import org.jetbrains.kotlin.gradle.utils.LazyResolvedConfiguration import org.jetbrains.kotlin.gradle.utils.LazyResolvedConfiguration
import org.jetbrains.kotlin.gradle.utils.future
import org.jetbrains.kotlin.gradle.utils.getOrPut import org.jetbrains.kotlin.gradle.utils.getOrPut
import java.util.* import java.util.*
@@ -143,7 +144,7 @@ internal class GranularMetadataTransformation(
params.resolvedMetadataConfiguration params.resolvedMetadataConfiguration
.root .root
.dependencies .dependencies
.filter { !it.isConstraint} .filter { !it.isConstraint }
.filterIsInstance<ResolvedDependencyResult>() .filterIsInstance<ResolvedDependencyResult>()
) )
} }
@@ -332,15 +333,18 @@ internal val ResolvedComponentResult.currentBuildProjectIdOrNull
} }
} }
private val Project.allProjectsData: Map<String, GranularMetadataTransformation.ProjectData> get() = rootProject private val Project.allProjectsData: Map<String, GranularMetadataTransformation.ProjectData>
.extraProperties get() = rootProject
.getOrPut("all${GranularMetadataTransformation.ProjectData::class.java.simpleName}") { collectAllProjectsData() } .extraProperties
.getOrPut("all${GranularMetadataTransformation.ProjectData::class.java.simpleName}") {
future { collectAllProjectsData() }.getOrThrow()
}
private fun Project.collectAllProjectsData(): Map<String, GranularMetadataTransformation.ProjectData> { private fun Project.collectAllProjectsData(): Map<String, GranularMetadataTransformation.ProjectData> {
return rootProject.allprojects.associateBy { it.path }.mapValues { (path, subProject) -> return rootProject.allprojects.associateBy { it.path }.mapValues { (path, subProject) ->
GranularMetadataTransformation.ProjectData( GranularMetadataTransformation.ProjectData(
path = path, path = path,
sourceSetMetadataOutputsProvider = { subProject.collectSourceSetMetadataOutputs() }, sourceSetMetadataOutputsProvider = future { subProject.collectSourceSetMetadataOutputs() }::getOrThrow,
moduleIdProvider = { ModuleIds.idOfRootModule(subProject) } moduleIdProvider = { ModuleIds.idOfRootModule(subProject) }
) )
} }
@@ -48,7 +48,7 @@ private class ProjectMetadataProviderImpl(
} }
} }
internal fun Project.collectSourceSetMetadataOutputs(): Map<SourceSetName, SourceSetMetadataOutputs> { internal suspend fun Project.collectSourceSetMetadataOutputs(): Map<SourceSetName, SourceSetMetadataOutputs> {
val multiplatformExtension = multiplatformExtensionOrNull ?: return emptyMap() val multiplatformExtension = multiplatformExtensionOrNull ?: return emptyMap()
val sourceSetMetadata = multiplatformExtension.sourceSetsMetadataOutputs() val sourceSetMetadata = multiplatformExtension.sourceSetsMetadataOutputs()
@@ -82,7 +82,7 @@ private fun KotlinMultiplatformExtension.sourceSetsMetadataOutputs(): Map<Kotlin
}.toMap() }.toMap()
} }
private fun KotlinMultiplatformExtension.cInteropMetadataOfSourceSets( private suspend fun KotlinMultiplatformExtension.cInteropMetadataOfSourceSets(
sourceSets: Iterable<KotlinSourceSet> sourceSets: Iterable<KotlinSourceSet>
): Map<KotlinSourceSet, SourceSetMetadataOutputs.CInterop?> { ): Map<KotlinSourceSet, SourceSetMetadataOutputs.CInterop?> {
val taskForCLI = project.commonizeCInteropTask ?: return emptyMap() val taskForCLI = project.commonizeCInteropTask ?: return emptyMap()
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.commonizer.CommonizerOutputFileLayout.base64Hash
import org.jetbrains.kotlin.commonizer.CommonizerOutputFileLayout.ensureMaxFileNameLength import org.jetbrains.kotlin.commonizer.CommonizerOutputFileLayout.ensureMaxFileNameLength
import org.jetbrains.kotlin.commonizer.identityString import org.jetbrains.kotlin.commonizer.identityString
import org.jetbrains.kotlin.gradle.utils.changing import org.jetbrains.kotlin.gradle.utils.changing
import org.jetbrains.kotlin.gradle.utils.future
import org.jetbrains.kotlin.gradle.utils.outputFilesProvider import org.jetbrains.kotlin.gradle.utils.outputFilesProvider
import java.io.File import java.io.File
@@ -20,7 +21,7 @@ internal abstract class AbstractCInteropCommonizerTask : DefaultTask() {
@get:OutputDirectory @get:OutputDirectory
abstract val outputDirectory: File abstract val outputDirectory: File
internal abstract fun findInteropsGroup(dependent: CInteropCommonizerDependent): CInteropCommonizerGroup? internal abstract suspend fun findInteropsGroup(dependent: CInteropCommonizerDependent): CInteropCommonizerGroup?
} }
internal fun AbstractCInteropCommonizerTask.outputDirectory(group: CInteropCommonizerGroup): File { internal fun AbstractCInteropCommonizerTask.outputDirectory(group: CInteropCommonizerGroup): File {
@@ -35,14 +36,15 @@ internal fun AbstractCInteropCommonizerTask.outputDirectory(group: CInteropCommo
internal fun AbstractCInteropCommonizerTask.commonizedOutputLibraries(dependent: CInteropCommonizerDependent): FileCollection { internal fun AbstractCInteropCommonizerTask.commonizedOutputLibraries(dependent: CInteropCommonizerDependent): FileCollection {
return outputFilesProvider { return outputFilesProvider {
val outputDirectory = commonizedOutputDirectory(dependent) ?: return@outputFilesProvider emptySet<File>() val outputDirectory = project.future { commonizedOutputDirectory(dependent) }.getOrThrow()
?: return@outputFilesProvider emptySet<File>()
project.providers.changing { project.providers.changing {
outputDirectory.listFiles().orEmpty().toSet() outputDirectory.listFiles().orEmpty().toSet()
} }
} }
} }
internal fun AbstractCInteropCommonizerTask.commonizedOutputDirectory(dependent: CInteropCommonizerDependent): File? { internal suspend fun AbstractCInteropCommonizerTask.commonizedOutputDirectory(dependent: CInteropCommonizerDependent): File? {
val group = findInteropsGroup(dependent) ?: return null val group = findInteropsGroup(dependent) ?: return null
return CommonizerOutputFileLayout return CommonizerOutputFileLayout
.resolveCommonizedDirectory(outputDirectory(group), dependent.target) .resolveCommonizedDirectory(outputDirectory(group), dependent.target)
@@ -9,16 +9,19 @@ import org.gradle.api.Project
import org.gradle.api.tasks.TaskProvider import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.bundling.Zip import org.gradle.api.tasks.bundling.Zip
import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull
import org.jetbrains.kotlin.gradle.plugin.launch
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinSharedNativeCompilation import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinSharedNativeCompilation
import org.jetbrains.kotlin.gradle.targets.native.internal.CInteropCommonizerCompositeMetadataJarBundling.cinteropMetadataDirectoryPath import org.jetbrains.kotlin.gradle.targets.native.internal.CInteropCommonizerCompositeMetadataJarBundling.cinteropMetadataDirectoryPath
internal fun Project.includeCommonizedCInteropMetadata( internal fun Project.includeCommonizedCInteropMetadata(
metadataKlib: TaskProvider<out Zip>, compilation: KotlinSharedNativeCompilation metadataKlib: TaskProvider<out Zip>, compilation: KotlinSharedNativeCompilation
) { ) {
metadataKlib.configure { jar -> includeCommonizedCInteropMetadata(jar, compilation) } metadataKlib.configure { jar ->
launch { includeCommonizedCInteropMetadata(jar, compilation) }
}
} }
internal fun Project.includeCommonizedCInteropMetadata(metadataKlib: Zip, compilation: KotlinSharedNativeCompilation) { internal suspend fun Project.includeCommonizedCInteropMetadata(metadataKlib: Zip, compilation: KotlinSharedNativeCompilation) {
val commonizerTask = commonizeCInteropTask?.get() ?: return val commonizerTask = commonizeCInteropTask?.get() ?: return
val commonizerDependencyToken = CInteropCommonizerDependent.from(compilation) ?: return val commonizerDependencyToken = CInteropCommonizerDependent.from(compilation) ?: return
val outputDirectory = commonizerTask.commonizedOutputDirectory(commonizerDependencyToken) ?: return val outputDirectory = commonizerTask.commonizedOutputDirectory(commonizerDependencyToken) ?: return
@@ -14,6 +14,7 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinSharedNativeCompilation
import org.jetbrains.kotlin.gradle.plugin.sources.DefaultKotlinSourceSet import org.jetbrains.kotlin.gradle.plugin.sources.DefaultKotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.sources.internal import org.jetbrains.kotlin.gradle.plugin.sources.internal
import org.jetbrains.kotlin.gradle.utils.filesProvider import org.jetbrains.kotlin.gradle.utils.filesProvider
import org.jetbrains.kotlin.gradle.utils.future
import java.io.File import java.io.File
internal fun Project.setupCInteropCommonizerDependencies() { internal fun Project.setupCInteropCommonizerDependencies() {
@@ -33,7 +34,8 @@ private fun Project.setupCInteropCommonizerDependenciesForCompilation(compilatio
val cinteropCommonizerTask = project.commonizeCInteropTask ?: return val cinteropCommonizerTask = project.commonizeCInteropTask ?: return
compilation.compileDependencyFiles += filesProvider { compilation.compileDependencyFiles += filesProvider {
val cinteropCommonizerDependent = CInteropCommonizerDependent.from(compilation) ?: return@filesProvider emptySet<File>() val cinteropCommonizerDependent = future { CInteropCommonizerDependent.from(compilation) }.getOrThrow()
?: return@filesProvider emptySet<File>()
cinteropCommonizerTask.get().commonizedOutputLibraries(cinteropCommonizerDependent) cinteropCommonizerTask.get().commonizedOutputLibraries(cinteropCommonizerDependent)
} }
} }
@@ -51,12 +53,14 @@ internal fun Project.cinteropCommonizerDependencies(sourceSet: DefaultKotlinSour
val cinteropCommonizerTask = project.copyCommonizeCInteropForIdeTask ?: return project.files() val cinteropCommonizerTask = project.copyCommonizeCInteropForIdeTask ?: return project.files()
return filesProvider { return filesProvider {
val directlyDependent = CInteropCommonizerDependent.from(sourceSet) future {
val associateDependent = CInteropCommonizerDependent.fromAssociateCompilations(sourceSet) val directlyDependent = CInteropCommonizerDependent.from(sourceSet)
val associateDependent = CInteropCommonizerDependent.fromAssociateCompilations(sourceSet)
listOfNotNull(directlyDependent, associateDependent).map { cinteropCommonizerDependent -> listOfNotNull(directlyDependent, associateDependent).map { cinteropCommonizerDependent ->
cinteropCommonizerTask.get().commonizedOutputLibraries(cinteropCommonizerDependent) cinteropCommonizerTask.get().commonizedOutputLibraries(cinteropCommonizerDependent)
} }
}.getOrThrow()
} }
} }
@@ -72,24 +72,24 @@ internal fun CInteropCommonizerDependent.Factory.from(
return CInteropCommonizerDependent(target, scopes, interops) return CInteropCommonizerDependent(target, scopes, interops)
} }
internal fun CInteropCommonizerDependent.Factory.from(compilation: KotlinSharedNativeCompilation): CInteropCommonizerDependent? { internal suspend fun CInteropCommonizerDependent.Factory.from(compilation: KotlinSharedNativeCompilation): CInteropCommonizerDependent? {
return from( return from(
compilation.commonizerTarget.getOrThrow() as? SharedCommonizerTarget ?: return null, compilation.commonizerTarget.await() as? SharedCommonizerTarget ?: return null,
compilation.getImplicitlyDependingNativeCompilations() compilation.getImplicitlyDependingNativeCompilations()
) )
} }
internal fun CInteropCommonizerDependent.Factory.from(sourceSet: KotlinSourceSet): CInteropCommonizerDependent? { internal suspend fun CInteropCommonizerDependent.Factory.from(sourceSet: KotlinSourceSet): CInteropCommonizerDependent? {
return from( return from(
target = sourceSet.commonizerTarget.getOrThrow() as? SharedCommonizerTarget ?: return null, target = sourceSet.commonizerTarget.await() as? SharedCommonizerTarget ?: return null,
compilations = sourceSet.internal.compilations compilations = sourceSet.internal.compilations
.filterIsInstance<KotlinNativeCompilation>().toSet() .filterIsInstance<KotlinNativeCompilation>().toSet()
) )
} }
internal fun CInteropCommonizerDependent.Factory.fromAssociateCompilations(sourceSet: KotlinSourceSet): CInteropCommonizerDependent? { internal suspend fun CInteropCommonizerDependent.Factory.fromAssociateCompilations(sourceSet: KotlinSourceSet): CInteropCommonizerDependent? {
return from( return from(
target = sourceSet.commonizerTarget.getOrThrow() as? SharedCommonizerTarget ?: return null, target = sourceSet.commonizerTarget.await() as? SharedCommonizerTarget ?: return null,
compilations = sourceSet.internal.compilations compilations = sourceSet.internal.compilations
.filterIsInstance<KotlinNativeCompilation>() .filterIsInstance<KotlinNativeCompilation>()
.flatMap { compilation -> compilation.associateWithClosure } .flatMap { compilation -> compilation.associateWithClosure }
@@ -27,9 +27,7 @@ import org.jetbrains.kotlin.gradle.plugin.sources.DefaultKotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.sources.withDependsOnClosure import org.jetbrains.kotlin.gradle.plugin.sources.withDependsOnClosure
import org.jetbrains.kotlin.gradle.targets.native.internal.CInteropCommonizerTask.CInteropGist import org.jetbrains.kotlin.gradle.targets.native.internal.CInteropCommonizerTask.CInteropGist
import org.jetbrains.kotlin.gradle.tasks.CInteropProcess import org.jetbrains.kotlin.gradle.tasks.CInteropProcess
import org.jetbrains.kotlin.gradle.utils.chainedFinalizeValueOnRead import org.jetbrains.kotlin.gradle.utils.*
import org.jetbrains.kotlin.gradle.utils.listProperty
import org.jetbrains.kotlin.gradle.utils.property
import org.jetbrains.kotlin.konan.target.KonanTarget import org.jetbrains.kotlin.konan.target.KonanTarget
import java.io.File import java.io.File
import javax.inject.Inject import javax.inject.Inject
@@ -123,14 +121,14 @@ internal open class CInteropCommonizerTask
* For Gradle Configuration Cache support the Group-to-Dependencies relation should be pre-cached. * For Gradle Configuration Cache support the Group-to-Dependencies relation should be pre-cached.
* It is used during execution phase. * It is used during execution phase.
*/ */
private val groupedCommonizerDependencies: Map<CInteropCommonizerGroup, List<CInteropCommonizerDependencies>> by lazy { private val groupedCommonizerDependencies: Future<Map<CInteropCommonizerGroup, List<CInteropCommonizerDependencies>>> = project.future {
val multiplatformExtension = project.multiplatformExtensionOrNull ?: return@lazy emptyMap() val multiplatformExtension = project.multiplatformExtensionOrNull ?: return@future emptyMap()
val sourceSetsByTarget = multiplatformExtension.sourceSets.groupBy { sourceSet -> sourceSet.commonizerTarget.getOrThrow() } val sourceSetsByTarget = multiplatformExtension.sourceSets.groupBy { sourceSet -> sourceSet.commonizerTarget.getOrThrow() }
val sourceSetsByGroup = multiplatformExtension.sourceSets.groupBy { sourceSet -> val sourceSetsByGroup = multiplatformExtension.sourceSets.groupBy { sourceSet ->
CInteropCommonizerDependent.from(sourceSet)?.let { findInteropsGroup(it) } CInteropCommonizerDependent.from(sourceSet)?.let { findInteropsGroup(it) }
} }
getAllInteropsGroups().associateWith { group -> allInteropGroups.await().associateWith { group ->
(group.targets + group.targets.allLeaves()).map { target -> (group.targets + group.targets.allLeaves()).map { target ->
val externalDependencyFiles: List<FileCollection> = when (target) { val externalDependencyFiles: List<FileCollection> = when (target) {
is LeafCommonizerTarget -> { is LeafCommonizerTarget -> {
@@ -165,7 +163,7 @@ internal open class CInteropCommonizerTask
@get:Classpath @get:Classpath
protected val commonizerDependenciesClasspath: FileCollection protected val commonizerDependenciesClasspath: FileCollection
get() = project.files( get() = project.files(
groupedCommonizerDependencies.values.flatten().map { it.dependencies } groupedCommonizerDependencies.getOrThrow().values.flatten().map { it.dependencies }
) )
@get:Nested @get:Nested
@@ -174,7 +172,7 @@ internal open class CInteropCommonizerTask
@get:OutputDirectories @get:OutputDirectories
val allOutputDirectories: Set<File> val allOutputDirectories: Set<File>
get() = getAllInteropsGroups().map { outputDirectory(it) }.toSet() get() = allInteropGroups.getOrThrow().map { outputDirectory(it) }.toSet()
fun from(vararg tasks: CInteropProcess) = from( fun from(vararg tasks: CInteropProcess) = from(
tasks.toList() tasks.toList()
@@ -192,7 +190,7 @@ internal open class CInteropCommonizerTask
@TaskAction @TaskAction
protected fun commonizeCInteropLibraries() { protected fun commonizeCInteropLibraries() {
getAllInteropsGroups().forEach(::commonize) allInteropGroups.getOrThrow().forEach(::commonize)
} }
private fun commonize(group: CInteropCommonizerGroup) { private fun commonize(group: CInteropCommonizerGroup) {
@@ -217,7 +215,7 @@ internal open class CInteropCommonizerTask
} }
private fun getCInteropCommonizerGroupDependencies(group: CInteropCommonizerGroup): Set<CommonizerDependency> { private fun getCInteropCommonizerGroupDependencies(group: CInteropCommonizerGroup): Set<CommonizerDependency> {
val dependencies = groupedCommonizerDependencies[group] val dependencies = groupedCommonizerDependencies.getOrThrow()[group]
?.flatMap { (target, dependencies) -> ?.flatMap { (target, dependencies) ->
dependencies.files dependencies.files
.filter { file -> file.exists() && (file.isDirectory || file.extension == "klib") } .filter { file -> file.exists() && (file.isDirectory || file.extension == "klib") }
@@ -229,15 +227,15 @@ internal open class CInteropCommonizerTask
return dependencies return dependencies
} }
@Nested @get:Internal
internal fun getAllInteropsGroups(): Set<CInteropCommonizerGroup> { internal val allInteropGroups: Future<Set<CInteropCommonizerGroup>> = project.future {
val dependents = allDependents val dependents = allDependents.await()
val allScopeSets = dependents.map { it.scopes }.toSet() val allScopeSets = dependents.map { it.scopes }.toSet()
val rootScopeSets = allScopeSets.filter { scopeSet -> val rootScopeSets = allScopeSets.filter { scopeSet ->
allScopeSets.none { otherScopeSet -> otherScopeSet != scopeSet && otherScopeSet.containsAll(scopeSet) } allScopeSets.none { otherScopeSet -> otherScopeSet != scopeSet && otherScopeSet.containsAll(scopeSet) }
} }
return rootScopeSets.map { scopeSet -> rootScopeSets.map { scopeSet ->
val dependentsForScopes = dependents.filter { dependent -> val dependentsForScopes = dependents.filter { dependent ->
scopeSet.containsAll(dependent.scopes) scopeSet.containsAll(dependent.scopes)
} }
@@ -249,8 +247,12 @@ internal open class CInteropCommonizerTask
}.toSet() }.toSet()
} }
override fun findInteropsGroup(dependent: CInteropCommonizerDependent): CInteropCommonizerGroup? { @get:Nested
val suitableGroups = getAllInteropsGroups().filter { group -> @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) group.interops.containsAll(dependent.interops) && group.targets.contains(dependent.target)
} }
@@ -261,8 +263,8 @@ internal open class CInteropCommonizerTask
return suitableGroups.firstOrNull() return suitableGroups.firstOrNull()
} }
private val allDependents: Set<CInteropCommonizerDependent> by lazy { private val allDependents: Future<Set<CInteropCommonizerDependent>> = project.future {
val multiplatformExtension = project.multiplatformExtensionOrNull ?: return@lazy emptySet() val multiplatformExtension = project.multiplatformExtensionOrNull ?: return@future emptySet()
val fromSharedNativeCompilations = multiplatformExtension val fromSharedNativeCompilations = multiplatformExtension
.targets.flatMap { target -> target.compilations } .targets.flatMap { target -> target.compilations }
@@ -278,7 +280,7 @@ internal open class CInteropCommonizerTask
.mapNotNull { sourceSet -> CInteropCommonizerDependent.fromAssociateCompilations(sourceSet) } .mapNotNull { sourceSet -> CInteropCommonizerDependent.fromAssociateCompilations(sourceSet) }
.toSet() .toSet()
return@lazy (fromSharedNativeCompilations + fromSourceSets + fromSourceSetsAssociateCompilations) (fromSharedNativeCompilations + fromSourceSets + fromSourceSetsAssociateCompilations)
} }
} }
@@ -26,14 +26,14 @@ internal open class CopyCommonizeCInteropForIdeTask : AbstractCInteropCommonizer
override val outputDirectory: File = project.rootDir.resolve(".gradle/kotlin/commonizer") override val outputDirectory: File = project.rootDir.resolve(".gradle/kotlin/commonizer")
.resolve(project.path.removePrefix(":").replace(":", "/")) .resolve(project.path.removePrefix(":").replace(":", "/"))
override fun findInteropsGroup(dependent: CInteropCommonizerDependent): CInteropCommonizerGroup? { override suspend fun findInteropsGroup(dependent: CInteropCommonizerDependent): CInteropCommonizerGroup? {
return commonizeCInteropTask.get().findInteropsGroup(dependent) return commonizeCInteropTask.get().findInteropsGroup(dependent)
} }
@TaskAction @TaskAction
protected fun copy() { protected fun copy() {
outputDirectory.mkdirs() outputDirectory.mkdirs()
for (group in commonizeCInteropTask.get().getAllInteropsGroups()) { for (group in commonizeCInteropTask.get().allInteropGroups.getOrThrow()) {
val source = commonizeCInteropTask.get().outputDirectory(group) val source = commonizeCInteropTask.get().outputDirectory(group)
if (!source.exists()) continue if (!source.exists()) continue
val target = outputDirectory(group) val target = outputDirectory(group)
@@ -16,6 +16,8 @@ import org.jetbrains.kotlin.gradle.plugin.kotlinPluginLifecycle
import org.jetbrains.kotlin.tooling.core.ExtrasLazyProperty import org.jetbrains.kotlin.tooling.core.ExtrasLazyProperty
import org.jetbrains.kotlin.tooling.core.HasMutableExtras import org.jetbrains.kotlin.tooling.core.HasMutableExtras
import org.jetbrains.kotlin.tooling.core.extrasLazyProperty import org.jetbrains.kotlin.tooling.core.extrasLazyProperty
import java.io.Serializable
import kotlin.reflect.KProperty
/** /**
* See [KotlinPluginLifecycle]: * See [KotlinPluginLifecycle]:
@@ -62,45 +64,31 @@ internal inline fun <Receiver, reified T> lazyFuture(
internal fun <T> Project.future(block: suspend Project.() -> T): Future<T> = kotlinPluginLifecycle.future { block() } internal fun <T> Project.future(block: suspend Project.() -> T): Future<T> = kotlinPluginLifecycle.future { block() }
@OptIn(ExperimentalCoroutinesApi::class)
internal fun <T> KotlinPluginLifecycle.future(block: suspend () -> T): Future<T> { internal fun <T> KotlinPluginLifecycle.future(block: suspend () -> T): Future<T> {
val deferred = CompletableDeferred<T>() return FutureImpl<T>(CompletableDeferred()).also { future ->
launch { future.completeWith(runCatching { block() }) }
launch {
deferred.completeWith(runCatching { block() })
}
return object : Future<T> {
override suspend fun await(): T {
return deferred.await()
}
override fun getOrThrow(): T {
return if (deferred.isCompleted) deferred.getCompleted() else throw IllegalLifecycleException(
"Future was not completed yet. Stage: '${stage}'"
)
}
} }
} }
@OptIn(ExperimentalCoroutinesApi::class)
internal fun <T> CompletableFuture(): CompletableFuture<T> { internal fun <T> CompletableFuture(): CompletableFuture<T> {
val deferred = CompletableDeferred<T>() return FutureImpl()
}
@OptIn(ExperimentalCoroutinesApi::class)
private class FutureImpl<T>(private val deferred: CompletableDeferred<T> = CompletableDeferred()) : CompletableFuture<T>, Serializable {
fun completeWith(result: Result<T>) = deferred.completeWith(result)
return object : CompletableFuture<T> { override fun complete(value: T) {
override fun complete(value: T) { deferred.complete(value)
deferred.complete(value)
}
override suspend fun await(): T {
return deferred.await()
}
override fun getOrThrow(): T {
return if (deferred.isCompleted) deferred.getCompleted() else throw IllegalLifecycleException(
"Future was not completed yet"
)
}
} }
}
override suspend fun await(): T {
return deferred.await()
}
override fun getOrThrow(): T {
return if (deferred.isCompleted) deferred.getCompleted() else throw IllegalLifecycleException(
"Future was not completed yet"
)
}
}
@@ -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.CInteropCommonizerTask
import org.jetbrains.kotlin.gradle.targets.native.internal.CInteropCommonizerGroup 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.commonizeCInteropTask
import org.jetbrains.kotlin.gradle.util.runLifecycleAwareTest
import org.jetbrains.kotlin.konan.target.KonanTarget.* import org.jetbrains.kotlin.konan.target.KonanTarget.*
import kotlin.test.* import kotlin.test.*
@@ -30,7 +31,7 @@ class CInteropCommonizerTaskTest : MultiplatformExtensionTest() {
} }
@Test @Test
fun `nativeMain linux macos`() { fun `nativeMain linux macos`() = project.runLifecycleAwareTest {
val linuxInterop = kotlin.linuxX64("linux").compilations.getByName("main").cinterops.create("anyInteropName") val linuxInterop = kotlin.linuxX64("linux").compilations.getByName("main").cinterops.create("anyInteropName")
val macosInterop = kotlin.macosX64("macos").compilations.getByName("main").cinterops.create("anyInteropName") val macosInterop = kotlin.macosX64("macos").compilations.getByName("main").cinterops.create("anyInteropName")
@@ -43,9 +44,8 @@ class CInteropCommonizerTaskTest : MultiplatformExtensionTest() {
linuxMain.dependsOn(nativeMain) linuxMain.dependsOn(nativeMain)
macosMain.dependsOn(nativeMain) macosMain.dependsOn(nativeMain)
project.evaluate()
val groups = task.getAllInteropsGroups() val groups = task.allInteropGroups.await()
assertEquals(1, groups.size, "Expected only one InteropsGroup") assertEquals(1, groups.size, "Expected only one InteropsGroup")
assertCInteropDependentEqualsForSourceSetAndCompilation(nativeMain) assertCInteropDependentEqualsForSourceSetAndCompilation(nativeMain)
@@ -60,7 +60,7 @@ class CInteropCommonizerTaskTest : MultiplatformExtensionTest() {
} }
@Test @Test
fun `nativeMain linux macos (no macos interop defined)`() { fun `nativeMain linux macos (no macos interop defined)`() = project.runLifecycleAwareTest {
kotlin.linuxX64("linux").compilations.getByName("main").cinterops.create("anyInteropName") kotlin.linuxX64("linux").compilations.getByName("main").cinterops.create("anyInteropName")
kotlin.macosX64("macos") kotlin.macosX64("macos")
@@ -73,8 +73,6 @@ class CInteropCommonizerTaskTest : MultiplatformExtensionTest() {
linuxMain.dependsOn(nativeMain) linuxMain.dependsOn(nativeMain)
macosMain.dependsOn(nativeMain) macosMain.dependsOn(nativeMain)
project.evaluate()
assertNull( assertNull(
findCInteropCommonizerDependent(nativeMain), findCInteropCommonizerDependent(nativeMain),
"Expected no CInteropCommonizerTarget from nativeMain, since one target has not defined any cinterop" "Expected no CInteropCommonizerTarget from nativeMain, since one target has not defined any cinterop"
@@ -87,7 +85,7 @@ class CInteropCommonizerTaskTest : MultiplatformExtensionTest() {
} }
@Test @Test
fun `nativeMain iosMain linux macos iosX64 iosArm64`() { fun `nativeMain iosMain linux macos iosX64 iosArm64`() = project.runLifecycleAwareTest {
val linuxInterop = kotlin.linuxX64("linux").compilations.getByName("main").cinterops.create("anyInteropName").identifier 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 macosInterop = kotlin.macosX64("macos").compilations.getByName("main").cinterops.create("anyInteropName").identifier
val iosX64Interop = kotlin.iosX64("iosX64").compilations.getByName("main").cinterops.create("anyInteropName").identifier val iosX64Interop = kotlin.iosX64("iosX64").compilations.getByName("main").cinterops.create("anyInteropName").identifier
@@ -108,10 +106,8 @@ class CInteropCommonizerTaskTest : MultiplatformExtensionTest() {
iosX64Main.dependsOn(iosMain) iosX64Main.dependsOn(iosMain)
iosArm64Main.dependsOn(iosMain) iosArm64Main.dependsOn(iosMain)
project.evaluate()
assertEquals( assertEquals(
1, task.getAllInteropsGroups().size, 1, task.allInteropGroups.await().size,
"Expected exactly one InteropsGroup for task" "Expected exactly one InteropsGroup for task"
) )
@@ -144,7 +140,7 @@ class CInteropCommonizerTaskTest : MultiplatformExtensionTest() {
private fun `nativeTest nativeMain linux macos`( private fun `nativeTest nativeMain linux macos`(
nativeTestDependsOnNativeMain: Boolean nativeTestDependsOnNativeMain: Boolean
) { ) = project.runLifecycleAwareTest {
val linuxInterop = kotlin.linuxX64("linux").compilations.getByName("main").cinterops.create("anyInteropName").identifier 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 macosInterop = kotlin.macosX64("macos").compilations.getByName("main").cinterops.create("anyInteropName").identifier
@@ -169,10 +165,8 @@ class CInteropCommonizerTaskTest : MultiplatformExtensionTest() {
nativeTest.dependsOn(nativeMain) nativeTest.dependsOn(nativeMain)
} }
project.evaluate()
assertEquals( assertEquals(
1, task.getAllInteropsGroups().size, 1, task.allInteropGroups.await().size,
"Expected exactly 1 'SharedInteropsGroup' for task" "Expected exactly 1 'SharedInteropsGroup' for task"
) )
@@ -209,7 +203,7 @@ class CInteropCommonizerTaskTest : MultiplatformExtensionTest() {
private fun `nativeTest nativeMain linux macos - test compilation defines custom cinterop`( private fun `nativeTest nativeMain linux macos - test compilation defines custom cinterop`(
nativeTestDependsOnNativeMain: Boolean nativeTestDependsOnNativeMain: Boolean
) { ) = project.runLifecycleAwareTest {
val linuxInterop = kotlin.linuxX64("linux").compilations.getByName("main").cinterops.create("anyInteropName").identifier 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 macosInterop = kotlin.macosX64("macos").compilations.getByName("main").cinterops.create("anyInteropName").identifier
kotlin.linuxX64("linux").compilations.getByName("test").cinterops.create("anyOtherName").identifier kotlin.linuxX64("linux").compilations.getByName("test").cinterops.create("anyOtherName").identifier
@@ -235,10 +229,9 @@ class CInteropCommonizerTaskTest : MultiplatformExtensionTest() {
nativeTest.dependsOn(nativeMain) nativeTest.dependsOn(nativeMain)
} }
project.evaluate()
assertEquals( assertEquals(
1, task.getAllInteropsGroups().size, 1, task.allInteropGroups.await().size,
"Expected exactly 1 'SharedInteropsGroup' for task" "Expected exactly 1 'SharedInteropsGroup' for task"
) )
@@ -271,7 +264,7 @@ class CInteropCommonizerTaskTest : MultiplatformExtensionTest() {
`hierarchical project`(testSourceSetsDependOnMainSourceSets = false) `hierarchical project`(testSourceSetsDependOnMainSourceSets = false)
} }
private fun `hierarchical project`(testSourceSetsDependOnMainSourceSets: Boolean) { private fun `hierarchical project`(testSourceSetsDependOnMainSourceSets: Boolean) = project.runLifecycleAwareTest {
/* Define targets */ /* Define targets */
val linux = kotlin.linuxX64("linux") val linux = kotlin.linuxX64("linux")
val macos = kotlin.macosX64("macos") val macos = kotlin.macosX64("macos")
@@ -368,14 +361,13 @@ class CInteropCommonizerTaskTest : MultiplatformExtensionTest() {
target.compilations.getByName("test").defaultSourceSet.dependsOn(nativeTest) target.compilations.getByName("test").defaultSourceSet.dependsOn(nativeTest)
} }
project.evaluate()
assertCInteropDependentEqualsForSourceSetAndCompilation(nativeMain) assertCInteropDependentEqualsForSourceSetAndCompilation(nativeMain)
assertCInteropDependentEqualsForSourceSetAndCompilation(unixMain) assertCInteropDependentEqualsForSourceSetAndCompilation(unixMain)
assertCInteropDependentEqualsForSourceSetAndCompilation(appleMain) assertCInteropDependentEqualsForSourceSetAndCompilation(appleMain)
assertCInteropDependentEqualsForSourceSetAndCompilation(iosMain) assertCInteropDependentEqualsForSourceSetAndCompilation(iosMain)
val groups = task.getAllInteropsGroups() val groups = task.allInteropGroups.await()
assertEquals(2, groups.size, "Expected exactly two interop groups: main and test") assertEquals(2, groups.size, "Expected exactly two interop groups: main and test")
val nativeCommonizerTarget = SharedCommonizerTarget(nativeTargets.map { it.konanTarget }) val nativeCommonizerTarget = SharedCommonizerTarget(nativeTargets.map { it.konanTarget })
@@ -462,7 +454,7 @@ class CInteropCommonizerTaskTest : MultiplatformExtensionTest() {
} }
} }
private fun assertCInteropDependentEqualsForSourceSetAndCompilation(sourceSet: KotlinSourceSet) { private suspend fun assertCInteropDependentEqualsForSourceSetAndCompilation(sourceSet: KotlinSourceSet) {
assertEquals( assertEquals(
expectCInteropCommonizerDependent(sourceSet), expectCInteropCommonizerDependent(sourceSet),
expectCInteropCommonizerDependent(expectSharedNativeCompilation(sourceSet)), expectCInteropCommonizerDependent(expectSharedNativeCompilation(sourceSet)),
@@ -15,11 +15,13 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinSharedNativeCompilation import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinSharedNativeCompilation
import org.jetbrains.kotlin.gradle.targets.metadata.findMetadataCompilation
import org.jetbrains.kotlin.gradle.targets.native.internal.CInteropCommonizerDependent 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.CInteropIdentifier
import org.jetbrains.kotlin.gradle.targets.native.internal.from import org.jetbrains.kotlin.gradle.targets.native.internal.from
import kotlin.test.BeforeTest import kotlin.test.BeforeTest
import kotlin.test.assertNotNull import kotlin.test.assertNotNull
import kotlin.test.fail
abstract class MultiplatformExtensionTest { abstract class MultiplatformExtensionTest {
@@ -39,29 +41,29 @@ abstract class MultiplatformExtensionTest {
project.enableCInteropCommonization() project.enableCInteropCommonization()
} }
internal fun expectCInteropCommonizerDependent(compilation: KotlinSharedNativeCompilation): CInteropCommonizerDependent { internal suspend fun expectCInteropCommonizerDependent(compilation: KotlinSharedNativeCompilation): CInteropCommonizerDependent {
return assertNotNull( return assertNotNull(
CInteropCommonizerDependent.from(compilation), "Can't find SharedInterops for ${compilation.name} compilation" CInteropCommonizerDependent.from(compilation), "Can't find SharedInterops for ${compilation.name} compilation"
) )
} }
internal fun expectCInteropCommonizerDependent(sourceSet: KotlinSourceSet): CInteropCommonizerDependent { internal suspend fun expectCInteropCommonizerDependent(sourceSet: KotlinSourceSet): CInteropCommonizerDependent {
return assertNotNull( return assertNotNull(
CInteropCommonizerDependent.from(sourceSet), "Can't find SharedInterops for ${sourceSet.name} source set" CInteropCommonizerDependent.from(sourceSet), "Can't find SharedInterops for ${sourceSet.name} source set"
) )
} }
internal fun findCInteropCommonizerDependent(compilation: KotlinSharedNativeCompilation): CInteropCommonizerDependent? { internal suspend fun findCInteropCommonizerDependent(compilation: KotlinSharedNativeCompilation): CInteropCommonizerDependent? {
return CInteropCommonizerDependent.from(compilation) return CInteropCommonizerDependent.from(compilation)
} }
internal fun findCInteropCommonizerDependent(sourceSet: KotlinSourceSet): CInteropCommonizerDependent? { internal suspend fun findCInteropCommonizerDependent(sourceSet: KotlinSourceSet): CInteropCommonizerDependent? {
return CInteropCommonizerDependent.from(sourceSet) return CInteropCommonizerDependent.from(sourceSet)
} }
internal fun expectSharedNativeCompilation(sourceSet: KotlinSourceSet): KotlinSharedNativeCompilation { internal suspend fun expectSharedNativeCompilation(sourceSet: KotlinSourceSet): KotlinSharedNativeCompilation {
return kotlin.targets.flatMap { it.compilations }.filterIsInstance<KotlinSharedNativeCompilation>() val compilation = project.findMetadataCompilation(sourceSet) ?: fail("Missing metadata compilation for $sourceSet")
.single { it.defaultSourceSet == sourceSet } return assertIsInstance<KotlinSharedNativeCompilation>(compilation)
} }
internal fun KotlinNativeTarget.mainCinteropIdentifier(name: String): CInteropIdentifier { internal fun KotlinNativeTarget.mainCinteropIdentifier(name: String): CInteropIdentifier {