|
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension |
|
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget |
|
import org.jetbrains.kotlin.konan.target.KonanTarget |
|
import java.nio.file.Paths |
|
|
|
plugins { |
|
id("kotlin-multiplatform") |
|
} |
|
|
|
class FrameworkLinkBuilder { |
|
|
|
val records = mutableListOf<Record>() |
|
|
|
class Record { |
|
var name: String? = null |
|
var defFile: String? = null |
|
var searchPath: String? = null |
|
} |
|
|
|
fun framework(recordBlock: Record.() -> Unit) { |
|
records += Record().apply(recordBlock) |
|
} |
|
} |
|
project.extensions.create("frameworkLinking", KotlinNativeFrameworkLinkingExtension::class) |
|
|
|
open class KotlinNativeFrameworkLinkingExtension(val project: Project) { |
|
|
|
val frameworkDefinitions = mutableListOf<FrameworkDefinition>() |
|
private var isShoulUseAllXcodeFrameworksForLinking = false |
|
|
|
class FrameworkDefinition { |
|
var name: String? = null |
|
var defFile: String? = null |
|
var searchPath: String? = null |
|
|
|
val requireName: String get() = requireNotNull(name) |
|
} |
|
|
|
fun framework(recordBlock: FrameworkDefinition.() -> Unit) { |
|
frameworkDefinitions += FrameworkDefinition().apply(recordBlock) |
|
} |
|
|
|
fun useXcodeFrameworks() { |
|
isShoulUseAllXcodeFrameworksForLinking = true |
|
} |
|
|
|
fun FrameworkDefinition.findHeadersFile(konanTarget: KonanTarget): String = |
|
if (searchPath?.endsWith(".framework") == true) { |
|
Paths.get(requireNotNull(searchPath), "Headers").toString() |
|
} else { |
|
Paths.get(findFrameworkFolder(konanTarget), "${requireName}.framework", "Headers").toString() |
|
} |
|
|
|
fun FrameworkDefinition.findFrameworkFolder(konanTarget: KonanTarget): String { |
|
val path = requireNotNull(searchPath) |
|
return when { |
|
path.endsWith(".framework") -> path |
|
path.endsWith(".xcframework") -> Paths.get(path, konanTarget.xcFrameworkPath).toString() |
|
else -> error("Not implemented") |
|
} |
|
} |
|
|
|
private fun getXcodeParametersFrameworkNameList(): List<String> { |
|
|
|
val otherLdFlags = (System.getenv("OTHER_LDFLAGS") ?: return emptyList()) |
|
.split(" ") |
|
.map { it.removeSurrounding("\"") } |
|
|
|
return otherLdFlags |
|
.filterIndexed { index, flagValue -> |
|
flagValue == "-framework" || otherLdFlags.getOrNull(index - 1) == "-framework" |
|
} |
|
.zipWithNext() |
|
.map { it.first } |
|
.filter { it != "-framework" } |
|
.distinct() |
|
} |
|
|
|
private fun findXcodeFrameworkFolder(name: String, konanTarget: KonanTarget): String? { |
|
val frameworkSearchPath = requireNotNull(System.getenv("FRAMEWORK_SEARCH_PATHS")) { |
|
"Defined framework search from Xcode ENV parameters, but ENV not provided. Link framework manual with framework { }." |
|
}.split(" ").map { it.removeSurrounding("\"") } |
|
for (searchPath in frameworkSearchPath) { |
|
val listFiles = File(searchPath).listFiles() |
|
listFiles?.forEach { searchPathFile -> |
|
val path = when (searchPathFile.name) { |
|
"${name}.framework" -> searchPathFile.parentFile.absolutePath |
|
"${name}.xcframework" -> { |
|
Paths.get( |
|
searchPathFile.absolutePath, |
|
konanTarget.xcFrameworkPath |
|
).toString() |
|
} |
|
else -> null |
|
} |
|
if (path != null) { |
|
println("Found framework path ($name) - $path") |
|
return path |
|
} |
|
} |
|
} |
|
return null |
|
} |
|
|
|
private val KonanTarget.xcFrameworkPath: String |
|
get() = when (this) { |
|
KonanTarget.IOS_ARM64 -> "ios-arm64_armv7" |
|
KonanTarget.IOS_SIMULATOR_ARM64 -> "ios-arm64_i386_x86_64-simulator" |
|
KonanTarget.IOS_X64 -> "ios-arm64_i386_x86_64-simulator" |
|
else -> error("Not implemented support of target: $this") |
|
} |
|
|
|
init { |
|
project.afterEvaluate { |
|
val kmm = project.extensions.getByName("kotlin") as KotlinMultiplatformExtension |
|
val nativeTargets = kmm.targets.mapNotNull { it as? KotlinNativeTarget } |
|
|
|
nativeTargets.forEach { target -> |
|
target.compilations.getByName("main") { |
|
frameworkDefinitions.forEach { frameworkDefinition -> |
|
cinterops.create(requireNotNull(frameworkDefinition.name)) { |
|
frameworkDefinition.defFile?.let { |
|
defFile(it) |
|
} |
|
includeDirs(frameworkDefinition.findHeadersFile(target.konanTarget)) |
|
} |
|
} |
|
} |
|
|
|
target.binaries.all { |
|
if (isShoulUseAllXcodeFrameworksForLinking) { |
|
getXcodeParametersFrameworkNameList().forEach { frameworkName -> |
|
linkerOpts( |
|
"-F${findXcodeFrameworkFolder(frameworkName, compilation.target.konanTarget)}", |
|
"-framework", |
|
frameworkName |
|
) |
|
} |
|
} |
|
|
|
frameworkDefinitions.forEach { frameworkDefinition -> |
|
linkerOpts( |
|
"-F${frameworkDefinition.findFrameworkFolder(compilation.target.konanTarget)}", |
|
"-framework", |
|
requireNotNull(frameworkDefinition.name) |
|
) |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |