Unverified Commit 1afda4d3 authored by Alexey Tsvetkov's avatar Alexey Tsvetkov Committed by GitHub

Fix dynamic framework support and fix binary compatibility of SkikoUI… (#763)

* Fix dynamic framework support and fix binary compatibility of SkikoUIView

This change configures Kotlin Gradle
plugin to include a CInterop def file
(https://kotlinlang.org/docs/native-c-interop.html)
to fix linking a dynamic framework for iOS

Also helps with https://youtrack.jetbrains.com/issue/KT-60399
(at least in some cases). Internal discussion https://jetbrains.slack.com/archives/G017NLN12D8/p1689594898607469

* CR fix: use flatmap
parent c013ba4e
...@@ -4,6 +4,7 @@ import org.gradle.crypto.checksum.Checksum ...@@ -4,6 +4,7 @@ import org.gradle.crypto.checksum.Checksum
import org.jetbrains.compose.internal.publishing.MavenCentralProperties import org.jetbrains.compose.internal.publishing.MavenCentralProperties
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.kotlin.gradle.tasks.CInteropProcess
import org.jetbrains.kotlin.gradle.tasks.KotlinCompileTool import org.jetbrains.kotlin.gradle.tasks.KotlinCompileTool
plugins { plugins {
...@@ -427,6 +428,32 @@ kotlin { ...@@ -427,6 +428,32 @@ kotlin {
} }
} }
fun configureCinterop(
cinteropName: String,
os: OS,
arch: Arch,
target: KotlinNativeTarget,
targetString: String,
linkerOpts: List<String>,
) {
val tasks = target.project.tasks
val taskNameSuffix = joinToTitleCamelCase(os.idWithSuffix(isIosSim = target.isIosSimArm64()), arch.id)
val writeCInteropDef = tasks.register("writeCInteropDef$taskNameSuffix", WriteCInteropDefFile::class.java) {
this.linkerOpts.set(linkerOpts)
outputFile.set(project.layout.buildDirectory.file("cinterop/$targetString/skiko.def"))
}
tasks.withType(CInteropProcess::class.java).configureEach {
if (konanTarget == target.konanTarget) {
dependsOn(writeCInteropDef)
}
}
target.compilations.getByName("main") {
cinterops.create(cinteropName).apply {
defFileProperty.set(writeCInteropDef.map { it.outputFile.get().asFile })
}
}
}
fun configureNativeTarget(os: OS, arch: Arch, target: KotlinNativeTarget) { fun configureNativeTarget(os: OS, arch: Arch, target: KotlinNativeTarget) {
if (!os.isCompatibleWithHost) return if (!os.isCompatibleWithHost) return
...@@ -444,24 +471,27 @@ fun configureNativeTarget(os: OS, arch: Arch, target: KotlinNativeTarget) { ...@@ -444,24 +471,27 @@ fun configureNativeTarget(os: OS, arch: Arch, target: KotlinNativeTarget) {
val skiaBinDir = "$skiaDir/out/${buildType.id}-$targetString" val skiaBinDir = "$skiaDir/out/${buildType.id}-$targetString"
val linkerFlags = when (os) { val linkerFlags = when (os) {
OS.MacOS -> mutableListOf("-linker-option", "-framework", "-linker-option", "Metal", OS.MacOS -> mutableListOfLinkerOptions(
"-linker-option", "-framework", "-linker-option", "CoreGraphics", listOfFrameworks("Metal", "CoreGraphics", "CoreText", "CoreServices")
"-linker-option", "-framework", "-linker-option", "CoreText",
"-linker-option", "-framework", "-linker-option", "CoreServices"
) )
OS.IOS -> mutableListOf("-linker-option", "-framework", "-linker-option", "Metal", OS.IOS -> {
"-linker-option", "-framework", "-linker-option", "CoreGraphics", val iosFrameworks = listOfFrameworks("Metal", "CoreGraphics", "CoreText", "UIKit")
"-linker-option", "-framework", "-linker-option", "UIKit", // list of linker options to be included into klib, which are needed for skiko consumers
"-linker-option", "-framework", "-linker-option", "CoreText") // https://github.com/JetBrains/compose-multiplatform/issues/3178
OS.Linux -> mutableListOf( // Important! Removing or renaming cinterop-uikit publication might cause compile error
"-linker-option", "-L/usr/lib/x86_64-linux-gnu", // for projects depending on older Compose/Skiko transitively https://youtrack.jetbrains.com/issue/KT-60399
"-linker-option", "-lfontconfig", configureCinterop("uikit", os, arch, target, targetString, iosFrameworks)
"-linker-option", "-lGL", mutableListOfLinkerOptions(iosFrameworks)
}
OS.Linux -> mutableListOfLinkerOptions(
"-L/usr/lib/x86_64-linux-gnu",
"-lfontconfig",
"-lGL",
// TODO: an ugly hack, Linux linker searches only unresolved symbols. // TODO: an ugly hack, Linux linker searches only unresolved symbols.
"-linker-option", "$skiaBinDir/libsksg.a", "$skiaBinDir/libsksg.a",
"-linker-option", "$skiaBinDir/libskshaper.a", "$skiaBinDir/libskshaper.a",
"-linker-option", "$skiaBinDir/libskunicode.a", "$skiaBinDir/libskunicode.a",
"-linker-option", "$skiaBinDir/libskia.a" "$skiaBinDir/libskia.a"
) )
else -> mutableListOf() else -> mutableListOf()
} }
......
import org.gradle.api.DefaultTask
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
abstract class WriteCInteropDefFile : DefaultTask() {
@get:Input
abstract val linkerOpts: ListProperty<String>
@get:OutputFile
abstract val outputFile: RegularFileProperty
@TaskAction
fun run() {
val outputFile = outputFile.get().asFile
outputFile.parentFile.mkdirs()
outputFile.bufferedWriter().use { writer ->
val linkerOpts = linkerOpts.get()
if (linkerOpts.isNotEmpty()) {
writer.appendLine("linkerOpts=${linkerOpts.joinToString(" ")}")
}
}
}
}
\ No newline at end of file
...@@ -6,6 +6,7 @@ import org.gradle.api.tasks.TaskContainer ...@@ -6,6 +6,7 @@ import org.gradle.api.tasks.TaskContainer
import org.gradle.api.tasks.TaskProvider import org.gradle.api.tasks.TaskProvider
import org.gradle.kotlin.dsl.register import org.gradle.kotlin.dsl.register
import java.util.* import java.util.*
import kotlin.collections.ArrayList
// Utils, that are not needed in scripts can be placed to internal/utils // Utils, that are not needed in scripts can be placed to internal/utils
...@@ -43,3 +44,15 @@ fun Task.projectDirs(vararg relativePaths: String): List<Directory> { ...@@ -43,3 +44,15 @@ fun Task.projectDirs(vararg relativePaths: String): List<Directory> {
val projectDir = project.layout.projectDirectory val projectDir = project.layout.projectDirectory
return relativePaths.map { path -> projectDir.dir(path) } return relativePaths.map { path -> projectDir.dir(path) }
} }
fun listOfFrameworks(vararg frameworks: String): List<String> =
frameworks.toList().addElementBeforeEach("-framework")
fun mutableListOfLinkerOptions(options: Collection<String>): MutableList<String> =
options.addElementBeforeEach("-linker-option")
fun mutableListOfLinkerOptions(vararg options: String): MutableList<String> =
mutableListOfLinkerOptions(options.toList())
private fun <T> Collection<T>.addElementBeforeEach(element: T): MutableList<T> =
flatMapTo(ArrayList(size * 2)) { listOf(element, it) }
...@@ -11,6 +11,10 @@ import platform.darwin.NSInteger ...@@ -11,6 +11,10 @@ import platform.darwin.NSInteger
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
/*
TODO: remove org.jetbrains.skiko.objc.UIViewExtensionProtocol after Kotlin 1.8.20
https://youtrack.jetbrains.com/issue/KT-40426
*/
@Suppress("CONFLICTING_OVERLOADS") @Suppress("CONFLICTING_OVERLOADS")
@ExportObjCClass @ExportObjCClass
class SkikoUIView : UIView, UIKeyInputProtocol, UITextInputProtocol { class SkikoUIView : UIView, UIKeyInputProtocol, UITextInputProtocol {
...@@ -30,11 +34,20 @@ class SkikoUIView : UIView, UIKeyInputProtocol, UITextInputProtocol { ...@@ -30,11 +34,20 @@ class SkikoUIView : UIView, UIKeyInputProtocol, UITextInputProtocol {
private var _inputDelegate: UITextInputDelegateProtocol? = null private var _inputDelegate: UITextInputDelegateProtocol? = null
private var _currentTextMenuActions: TextActions? = null private var _currentTextMenuActions: TextActions? = null
// merging two constructors might cause a binary incompatibility, which will result in a unclear linking error,
// if a project using newer compose depends on an older compose transitively
// https://youtrack.jetbrains.com/issue/KT-60399
constructor(
skiaLayer: SkiaLayer,
frame: CValue<CGRect> = CGRectNull.readValue(),
pointInside: (Point, UIEvent?) -> Boolean = {_,_-> true }
) : this(skiaLayer, frame, pointInside, skikoUITextInputTrains = object : SkikoUITextInputTraits {})
constructor( constructor(
skiaLayer: SkiaLayer, skiaLayer: SkiaLayer,
frame: CValue<CGRect> = CGRectNull.readValue(), frame: CValue<CGRect> = CGRectNull.readValue(),
pointInside: (Point, UIEvent?) -> Boolean = {_,_-> true }, pointInside: (Point, UIEvent?) -> Boolean = {_,_-> true },
skikoUITextInputTrains: SkikoUITextInputTraits = object : SkikoUITextInputTraits {}, skikoUITextInputTrains: SkikoUITextInputTraits
) : super(frame) { ) : super(frame) {
this.skiaLayer = skiaLayer this.skiaLayer = skiaLayer
_pointInside = pointInside _pointInside = pointInside
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment