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
import org.jetbrains.compose.internal.publishing.MavenCentralProperties
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.kotlin.gradle.tasks.CInteropProcess
import org.jetbrains.kotlin.gradle.tasks.KotlinCompileTool
plugins {
......@@ -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) {
if (!os.isCompatibleWithHost) return
......@@ -444,24 +471,27 @@ fun configureNativeTarget(os: OS, arch: Arch, target: KotlinNativeTarget) {
val skiaBinDir = "$skiaDir/out/${buildType.id}-$targetString"
val linkerFlags = when (os) {
OS.MacOS -> mutableListOf("-linker-option", "-framework", "-linker-option", "Metal",
"-linker-option", "-framework", "-linker-option", "CoreGraphics",
"-linker-option", "-framework", "-linker-option", "CoreText",
"-linker-option", "-framework", "-linker-option", "CoreServices"
OS.MacOS -> mutableListOfLinkerOptions(
listOfFrameworks("Metal", "CoreGraphics", "CoreText", "CoreServices")
)
OS.IOS -> mutableListOf("-linker-option", "-framework", "-linker-option", "Metal",
"-linker-option", "-framework", "-linker-option", "CoreGraphics",
"-linker-option", "-framework", "-linker-option", "UIKit",
"-linker-option", "-framework", "-linker-option", "CoreText")
OS.Linux -> mutableListOf(
"-linker-option", "-L/usr/lib/x86_64-linux-gnu",
"-linker-option", "-lfontconfig",
"-linker-option", "-lGL",
OS.IOS -> {
val iosFrameworks = listOfFrameworks("Metal", "CoreGraphics", "CoreText", "UIKit")
// list of linker options to be included into klib, which are needed for skiko consumers
// https://github.com/JetBrains/compose-multiplatform/issues/3178
// Important! Removing or renaming cinterop-uikit publication might cause compile error
// for projects depending on older Compose/Skiko transitively https://youtrack.jetbrains.com/issue/KT-60399
configureCinterop("uikit", os, arch, target, targetString, iosFrameworks)
mutableListOfLinkerOptions(iosFrameworks)
}
OS.Linux -> mutableListOfLinkerOptions(
"-L/usr/lib/x86_64-linux-gnu",
"-lfontconfig",
"-lGL",
// TODO: an ugly hack, Linux linker searches only unresolved symbols.
"-linker-option", "$skiaBinDir/libsksg.a",
"-linker-option", "$skiaBinDir/libskshaper.a",
"-linker-option", "$skiaBinDir/libskunicode.a",
"-linker-option", "$skiaBinDir/libskia.a"
"$skiaBinDir/libsksg.a",
"$skiaBinDir/libskshaper.a",
"$skiaBinDir/libskunicode.a",
"$skiaBinDir/libskia.a"
)
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
import org.gradle.api.tasks.TaskProvider
import org.gradle.kotlin.dsl.register
import java.util.*
import kotlin.collections.ArrayList
// 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> {
val projectDir = project.layout.projectDirectory
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
import kotlin.math.max
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")
@ExportObjCClass
class SkikoUIView : UIView, UIKeyInputProtocol, UITextInputProtocol {
......@@ -30,11 +34,20 @@ class SkikoUIView : UIView, UIKeyInputProtocol, UITextInputProtocol {
private var _inputDelegate: UITextInputDelegateProtocol? = 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(
skiaLayer: SkiaLayer,
frame: CValue<CGRect> = CGRectNull.readValue(),
pointInside: (Point, UIEvent?) -> Boolean = {_,_-> true },
skikoUITextInputTrains: SkikoUITextInputTraits = object : SkikoUITextInputTraits {},
skikoUITextInputTrains: SkikoUITextInputTraits
) : super(frame) {
this.skiaLayer = skiaLayer
_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