Skip to content

Commit

Permalink
Fix that SKIE adds Kotlin runtime as a dependency of published klibs …
Browse files Browse the repository at this point in the history
…of the final module.
  • Loading branch information
FilipDolnik committed Apr 29, 2024
1 parent 242c348 commit a2c6dfb
Show file tree
Hide file tree
Showing 11 changed files with 171 additions and 90 deletions.
Original file line number Diff line number Diff line change
@@ -1,16 +1,28 @@
package co.touchlab.skie.configuration

class RootConfiguration(
val enabledFlags: Set<SkieConfigurationFlag>,
enabledFlags: Set<SkieConfigurationFlag>,
private val supportedKeys: Set<ConfigurationKey<*>>,
) : SkieConfiguration(null) {

private val mutableEnabledFlags = enabledFlags.toMutableSet()

val enabledFlags: Set<SkieConfigurationFlag> by ::mutableEnabledFlags

override val rootConfiguration: RootConfiguration
get() = this

fun isFlagEnabled(flag: SkieConfigurationFlag): Boolean =
flag in enabledFlags

fun disableFlag(flag: SkieConfigurationFlag) {
mutableEnabledFlags.remove(flag)
}

fun enableFlag(flag: SkieConfigurationFlag) {
mutableEnabledFlags.add(flag)
}

operator fun <KEY, VALUE> get(configurationKey: KEY): VALUE where KEY : ConfigurationKey<VALUE>, KEY : ConfigurationScope.Root =
getUnsafe(configurationKey)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,12 @@ interface CommonSkieContext {

val SkieConfigurationFlag.isDisabled: Boolean
get() = this.isEnabled.not()

fun SkieConfigurationFlag.enable() {
rootConfiguration.enableFlag(this)
}

fun SkieConfigurationFlag.disable() {
rootConfiguration.disableFlag(this)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ enum class SupportedFlow(private val directParent: SupportedFlow?) {

sealed interface Variant {

val kotlinClassFqName: String

val kind: SupportedFlow

fun getCoroutinesKirClass(kirProvider: KirProvider): KirClass =
Expand All @@ -48,8 +50,10 @@ enum class SupportedFlow(private val directParent: SupportedFlow?) {

class Required(override val kind: SupportedFlow) : Variant {

override val kotlinClassFqName: String = "co.touchlab.skie.runtime.coroutines.flow.SkieKotlin${kind.name}"

override fun getKotlinKirClass(kirProvider: KirProvider): KirClass =
kirProvider.getClassByFqName("co.touchlab.skie.runtime.coroutines.flow.SkieKotlin${kind.name}")
kirProvider.getClassByFqName(kotlinClassFqName)

override fun getSwiftClass(sirProvider: SirProvider): SirClass =
sirProvider.getClassByFqName(SirFqName(sirProvider.skieModule, "SkieSwift${kind.name}"))
Expand All @@ -61,8 +65,10 @@ enum class SupportedFlow(private val directParent: SupportedFlow?) {

class Optional(override val kind: SupportedFlow) : Variant {

override val kotlinClassFqName: String = "co.touchlab.skie.runtime.coroutines.flow.SkieKotlinOptional${kind.name}"

override fun getKotlinKirClass(kirProvider: KirProvider): KirClass =
kirProvider.getClassByFqName("co.touchlab.skie.runtime.coroutines.flow.SkieKotlinOptional${kind.name}")
kirProvider.getClassByFqName(kotlinClassFqName)

override fun getSwiftClass(sirProvider: SirProvider): SirClass =
sirProvider.getClassByFqName(SirFqName(sirProvider.skieModule, "SkieSwiftOptional${kind.name}"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import co.touchlab.skie.phases.features.flow.FlowConversionConstructorsGenerator
import co.touchlab.skie.phases.features.flow.UnifyFlowConfigurationForOverridesPhase
import co.touchlab.skie.phases.features.functions.FileScopeConvertorPhase
import co.touchlab.skie.phases.features.sealed.SealedInteropGenerator
import co.touchlab.skie.phases.features.suspend.CheckSkieRuntimePresencePhase
import co.touchlab.skie.phases.features.suspend.SuspendGenerator
import co.touchlab.skie.phases.header.AddTypeDefPhase
import co.touchlab.skie.phases.header.DeclareSkieErrorTypesPhase
Expand Down Expand Up @@ -92,6 +93,7 @@ class LinkerPhaseScheduler : SkiePhaseScheduler {
VerifyModuleNamePhase,
VerifyMinOSVersionPhase,
VerifyNoBitcodeEmbeddingPhase,
CheckSkieRuntimePresencePhase,
FixLibrariesShortNamePhase,
ClassExportAnalyticsPhase,
ExtraClassExportPhase(context),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package co.touchlab.skie.phases.features.suspend

import co.touchlab.skie.configuration.SkieConfigurationFlag
import co.touchlab.skie.kir.type.SupportedFlow
import co.touchlab.skie.phases.ClassExportPhase
import co.touchlab.skie.phases.descriptorProvider
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe

object CheckSkieRuntimePresencePhase : ClassExportPhase {

context(ClassExportPhase.Context)
override fun isActive(): Boolean = SkieConfigurationFlag.Feature_CoroutinesInterop.isEnabled

context(ClassExportPhase.Context)
override suspend fun execute() {
val skieRuntimeClassFqName = SupportedFlow.allVariants.first().kotlinClassFqName

val isRuntimePresent = descriptorProvider.exposedClasses.any { it.fqNameUnsafe.toString() == skieRuntimeClassFqName }

if (!isRuntimePresent) {
SkieConfigurationFlag.Feature_CoroutinesInterop.disable()
}
}
}
8 changes: 2 additions & 6 deletions SKIE/skie-gradle/plugin/gradle-plugin.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,10 @@ buildConfig {
buildConfigField("String", "SKIE_VERSION", "\"${project.version}\"")

val kotlinPlugin = project.provider { projects.kotlinCompiler.kotlinCompilerLinkerPlugin.dependencyProject }
buildConfigField("String", "KOTLIN_PLUGIN_GROUP", kotlinPlugin.map { it.group.toString().enquoted() })
buildConfigField("String", "KOTLIN_PLUGIN_NAME", kotlinPlugin.map { it.name.enquoted() })
buildConfigField("String", "KOTLIN_PLUGIN_VERSION", kotlinPlugin.map { it.version.toString().enquoted() })
buildConfigField("String", "SKIE_KOTLIN_PLUGIN_COORDINATES", kotlinPlugin.map { it.dependencyName.enquoted() })

val runtime = project.provider { projects.runtime.runtimeKotlin.dependencyProject }
buildConfigField("String", "RUNTIME_DEPENDENCY_GROUP", runtime.map { it.group.toString().enquoted() })
buildConfigField("String", "RUNTIME_DEPENDENCY_NAME", runtime.map { it.name.enquoted() })
buildConfigField("String", "RUNTIME_DEPENDENCY_VERSION", runtime.map { it.version.toString().enquoted() })
buildConfigField("String", "SKIE_KOTLIN_RUNTIME_COORDINATES", runtime.map { it.dependencyName.enquoted() })

val pluginId: String by properties
buildConfigField("String", "KOTLIN_PLUGIN_ID", "\"$pluginId\"")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ abstract class SkieGradlePlugin : Plugin<Project> {
project.configureSkieGradlePlugin()

project.afterEvaluate {
project.configureRuntimeVariantFallback()
project.configureSkieCompilerPlugin(shims, kotlinVersion)
}
}
Expand Down Expand Up @@ -202,15 +201,6 @@ abstract class SkieGradlePlugin : Plugin<Project> {
SkieSubPluginManager.configureDependenciesForSubPlugins(project)
}

private fun Project.configureRuntimeVariantFallback() {
if (!skieInternal.runtimeVariantFallback.isPresent) {
val extraPropertiesKey = "skieRuntimeVariantFallback"
skieInternal.runtimeVariantFallback.set(
project.properties[extraPropertiesKey]?.toString().toBoolean(),
)
}
}

private fun Project.configureSkieCompilerPlugin(shims: ShimEntrypoint, kotlinToolingVersion: String) {
if (!isSkieEnabled) {
return
Expand All @@ -220,10 +210,12 @@ abstract class SkieGradlePlugin : Plugin<Project> {

FatFrameworkConfigurator.configureSkieForFatFrameworks(project)

kotlinMultiplatformExtension?.appleTargets?.all {
kotlinMultiplatformExtension?.appleTargets?.configureEach {
val target = this
binaries.withType<Framework>().all {

binaries.withType<Framework>().configureEach {
val binary = this

skieInternal.targets.add(
SkieTarget.TargetBinary(
project = project,
Expand All @@ -235,11 +227,11 @@ abstract class SkieGradlePlugin : Plugin<Project> {
}
}

kotlinArtifactsExtension.artifacts.withType<KotlinNativeArtifact>().all {
kotlinArtifactsExtension.artifacts.withType<KotlinNativeArtifact>().configureEach {
skieInternal.targets.addAll(skieTargetsOf(this))
}

skieInternal.targets.all {
skieInternal.targets.configureEach {
configureSkie(shims, kotlinToolingVersion)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,4 @@ import org.gradle.api.provider.Property
internal interface SkieInternalExtension {

val targets: NamedDomainObjectContainer<SkieTarget>

// Used for local development because it requires a different path to the runtime dependency
val runtimeVariantFallback: Property<Boolean>
}
Original file line number Diff line number Diff line change
@@ -1,75 +1,129 @@
package co.touchlab.skie.plugin.coroutines

import co.touchlab.skie.gradle.KotlinCompilerVersion
import co.touchlab.skie.gradle_plugin.BuildConfig
import co.touchlab.skie.plugin.skieInternal
import co.touchlab.skie.plugin.util.SkieTarget
import co.touchlab.skie.plugin.util.lowerCamelCaseName
import org.jetbrains.kotlin.gradle.plugin.mpp.AbstractNativeLibrary
import org.jetbrains.kotlin.konan.target.KonanTarget
import co.touchlab.skie.plugin.util.named
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ModuleIdentifier
import org.gradle.api.artifacts.ResolvedDependency
import org.gradle.api.attributes.Usage
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinUsages
import org.jetbrains.kotlin.konan.target.presetName

internal fun SkieTarget.addDependencyOnSkieRuntime(kotlinToolingVersion: String) {
if (!project.isCoroutinesInteropEnabled) {
return
}

val configurationNames = when (this) {
is SkieTarget.TargetBinary -> listOfNotNull(
binary.compilation.apiConfigurationName,
(binary as? AbstractNativeLibrary)?.exportConfigurationName,
)
is SkieTarget.Artifact -> {
val nameSuffix = SkieTarget.Artifact.artifactNameSuffix(artifact)
listOf(
lowerCamelCaseName(konanTarget.presetName, artifact.artifactName + nameSuffix, "linkLibrary"),
lowerCamelCaseName(konanTarget.presetName, artifact.artifactName + nameSuffix, "linkExport"),
)
}
val skieRuntimeConfiguration = getOrCreateSkieRuntimeConfiguration(kotlinToolingVersion)

linkerConfiguration.incoming.afterResolve {
registerSkieRuntime(skieRuntimeConfiguration)
}
}

private fun SkieTarget.registerSkieRuntime(
skieRuntimeConfiguration: Configuration,
) {
val skieRuntimeDependency = skieRuntimeConfiguration.getSkieRuntimeDependency()

val skieRuntimeDirectDependencies = skieRuntimeDependency.getSkieRuntimeDirectDependencies()

val linkerDependenciesIds = linkerConfiguration.resolvedConfiguration.resolvedArtifacts.map { it.moduleVersion.id.module }

if (!areCoroutinesUsedInProject(skieRuntimeDirectDependencies, linkerDependenciesIds)) {
return
}

val dependency = if (project.skieInternal.runtimeVariantFallback.get()) {
BuildConfig.DEFAULT_RUNTIME_DEPENDENCY
} else {
BuildConfig.SPECIFIC_RUNTIME_DEPENDENCY(konanTarget, kotlinToolingVersion)
verifyAllRuntimeDependenciesAreAvailable(skieRuntimeDirectDependencies, linkerDependenciesIds)

passRuntimeDependencyToCompiler(skieRuntimeDependency)
}

private fun SkieTarget.getOrCreateSkieRuntimeConfiguration(kotlinToolingVersion: String): Configuration {
val skieRuntimeConfigurationName = skieRuntimeConfigurationName

project.configurations.findByName(skieRuntimeConfigurationName)?.let {
return it
}
configurationNames.forEach { configurationName ->
project.dependencies.add(configurationName, dependency)

val skieRuntimeConfiguration = project.configurations.create(skieRuntimeConfigurationName) {
attributes {
attribute(KotlinPlatformType.attribute, KotlinPlatformType.native)
attribute(KotlinNativeTarget.konanTargetAttribute, konanTarget.name)
attribute(Usage.USAGE_ATTRIBUTE, project.objects.named(KotlinUsages.KOTLIN_API))
attribute(KotlinCompilerVersion.attribute, project.objects.named(kotlinToolingVersion))
}
}

project.dependencies.add(skieRuntimeConfigurationName, BuildConfig.SKIE_KOTLIN_RUNTIME_COORDINATES)

return skieRuntimeConfiguration
}

private val BuildConfig.DEFAULT_RUNTIME_DEPENDENCY: String
get() = "$RUNTIME_DEPENDENCY_GROUP:$RUNTIME_DEPENDENCY_NAME:$RUNTIME_DEPENDENCY_VERSION"
private fun Configuration.getSkieRuntimeDependency(): ResolvedDependency =
resolvedConfiguration.firstLevelModuleDependencies
.single()
.unwrapCommonKMPModule()
.single()

@Suppress("FunctionName")
private fun BuildConfig.SPECIFIC_RUNTIME_DEPENDENCY(konanTarget: KonanTarget, kotlinVersion: String): String {
return "$RUNTIME_DEPENDENCY_GROUP:$RUNTIME_DEPENDENCY_NAME-${konanTarget.dependencyPresetName}__kgp_${kotlinVersion}:$RUNTIME_DEPENDENCY_VERSION"
private fun ResolvedDependency.getSkieRuntimeDirectDependencies(): List<ModuleIdentifier> =
children.flatMap { it.unwrapCommonKMPModule() }.map { it.module.id.module }

private fun areCoroutinesUsedInProject(
skieRuntimeDirectDependencies: List<ModuleIdentifier>,
linkerDependenciesIds: List<ModuleIdentifier>,
): Boolean {
val coroutinesDependency = skieRuntimeDirectDependencies.single { it.name.startsWith("kotlinx-coroutines") }

return coroutinesDependency in linkerDependenciesIds
}

private fun SkieTarget.verifyAllRuntimeDependenciesAreAvailable(
skieRuntimeDirectDependencies: List<ModuleIdentifier>,
linkerDependenciesIds: List<ModuleIdentifier>,
) {
skieRuntimeDirectDependencies.forEach {
if (it !in linkerDependenciesIds) {
throw IllegalStateException(
"SKIE runtime requires a dependency '$it' which the target's configuration '${linkerConfigurationName}' does not have. " +
"This is most likely a bug in SKIE.",
)
}
}
}

private val KonanTarget.dependencyPresetName: String
private fun SkieTarget.passRuntimeDependencyToCompiler(skieRuntimeDependency: ResolvedDependency) {
skieRuntimeDependency.moduleArtifacts
.single { it.file.extension == "klib" }
.let { moduleArtifact ->
addFreeCompilerArgs(
"-Xexport-library=${moduleArtifact.file.absolutePath}",
"-library=${moduleArtifact.file.absolutePath}",
)
}
}

private val SkieTarget.linkerConfiguration: Configuration
get() = project.configurations.getByName(linkerConfigurationName)

private val SkieTarget.linkerConfigurationName: String
get() = when (this) {
KonanTarget.IOS_ARM32 -> "iosArm32"
KonanTarget.IOS_ARM64 -> "iosArm64"
KonanTarget.IOS_X64 -> "iosX64"
KonanTarget.IOS_SIMULATOR_ARM64 -> "iosSimulatorArm64"

KonanTarget.MACOS_ARM64 -> "macosArm64"
KonanTarget.MACOS_X64 -> "macosX64"

KonanTarget.TVOS_ARM64 -> "tvosArm64"
KonanTarget.TVOS_SIMULATOR_ARM64 -> "tvosSimulatorArm64"
KonanTarget.TVOS_X64 -> "tvosX64"

KonanTarget.WATCHOS_ARM32 -> "watchosArm32"
KonanTarget.WATCHOS_ARM64 -> "watchosArm64"
KonanTarget.WATCHOS_DEVICE_ARM64 -> "watchosDeviceArm64"
KonanTarget.WATCHOS_X86 -> "watchosX86"
KonanTarget.WATCHOS_X64 -> "watchosX64"
KonanTarget.WATCHOS_SIMULATOR_ARM64 -> "watchosSimulatorArm64"

KonanTarget.ANDROID_ARM32, KonanTarget.ANDROID_ARM64, KonanTarget.ANDROID_X64, KonanTarget.ANDROID_X86, KonanTarget.LINUX_ARM32_HFP,
KonanTarget.LINUX_ARM64, KonanTarget.LINUX_MIPS32, KonanTarget.LINUX_MIPSEL32, KonanTarget.LINUX_X64, KonanTarget.MINGW_X64,
KonanTarget.MINGW_X86, KonanTarget.WASM32, is KonanTarget.ZEPHYR,
-> error(
"SKIE doesn't support these platforms, so it should never ask for the preset name of this target.",
)
}.lowercase()
is SkieTarget.TargetBinary -> binary.compilation.compileDependencyConfigurationName
is SkieTarget.Artifact -> {
val nameSuffix = SkieTarget.Artifact.artifactNameSuffix(artifact)

lowerCamelCaseName(konanTarget.presetName, artifact.artifactName + nameSuffix, "linkLibrary")
}
}

private val SkieTarget.skieRuntimeConfigurationName: String
get() = "skieRuntimeFor" + linkerConfigurationName.replaceFirstChar { it.uppercase() }

// Due to how KMP dependencies work there is a difference in the behavior of local and remote dependencies.
private fun ResolvedDependency.unwrapCommonKMPModule(): Set<ResolvedDependency> =
if (moduleArtifacts.isEmpty()) children else setOf(this)
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,7 @@ internal object SkieCompilerPluginDependencyProvider {
exclude(group = "org.jetbrains", module = "annotations")
}

project.dependencies.add(
configurationName,
mapOf(
"group" to BuildConfig.KOTLIN_PLUGIN_GROUP,
"name" to BuildConfig.KOTLIN_PLUGIN_NAME,
"version" to BuildConfig.KOTLIN_PLUGIN_VERSION,
),
)
project.dependencies.add(configurationName, BuildConfig.SKIE_KOTLIN_PLUGIN_COORDINATES)

return skieCompilerPluginConfiguration
}
Expand Down
Loading

0 comments on commit a2c6dfb

Please sign in to comment.