I'm trying to include an objective-c library into a Kotlin multiplatform project. If I do it with this hierarchy structure, like it is described in the docs it works:
Objective-C library -> main Kotlin multiplatform module (called "shared") -> iOS App
But I would like to have this structure:
Objective-C library -> Kotlin multiplatform library module -> "shared" -> iOS App
But this does not work and gives me the following error:
> Task :shared:linkDebugFrameworkIosArm64 FAILED
error: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld invocation reported errors
Please try to disable compiler caches and rerun the build. To disable compiler caches, add the following line to the gradle.properties file in the project's root directory:
kotlin.native.cacheKind.iosArm64=none
Also, consider filing an issue with full Gradle log here: https://kotl.in/issue
The /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ld command returned non-zero exit code: 1.
output:
Undefined symbols for architecture arm64:
"_OBJC_CLASS_$_Mat", referenced from:
objc-class-ref in result.o
ld: symbol(s) not found for architecture arm64
error: Compilation finished with errors
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':shared:linkDebugFrameworkIosArm64'.
> Compilation finished with errors
Does this mean that the shared module sees the native library? (It shouldn't!) I guess that I'm just missing a simple setting or compiler/linker option.
My project-level build.gradle
:
plugins {
id("com.android.application").version("7.4.2").apply(false)
id("com.android.library").version("7.4.2").apply(false)
kotlin("android").version("1.8.20").apply(false)
kotlin("multiplatform").version("1.8.20").apply(false)
}
tasks.register("clean", Delete::class) {
delete(rootProject.buildDir)
}
The build.gradle
of the shared module:
plugins {
kotlin("multiplatform")
id("com.android.library")
}
kotlin {
jvm()
android {
compilations.all {
kotlinOptions {
jvmTarget = "1.8"
}
}
}
listOf(iosArm64(), iosX64(), iosSimulatorArm64()).forEach {
it.binaries.framework {
baseName = "shared"
}
}
sourceSets {
val commonMain by getting {
dependencies {
implementation(project(":otherModule"))
}
}
val commonTest by getting {
dependencies {
implementation(kotlin("test"))
}
}
val jvmMain by getting
val jvmTest by getting
val androidMain by getting
val androidInstrumentedTest by getting {
dependsOn(commonTest)
dependencies {
implementation("androidx.test:runner:1.5.2")
}
}
val iosX64Main by getting
val iosArm64Main by getting
val iosSimulatorArm64Main by getting
val iosMain by creating {
dependsOn(commonMain)
iosX64Main.dependsOn(this)
iosArm64Main.dependsOn(this)
iosSimulatorArm64Main.dependsOn(this)
}
val iosX64Test by getting
val iosArm64Test by getting
val iosSimulatorArm64Test by getting
val iosTest by creating {
dependsOn(commonTest)
iosX64Test.dependsOn(this)
iosArm64Test.dependsOn(this)
iosSimulatorArm64Test.dependsOn(this)
}
}
}
android {
namespace = "XXX"
compileSdk = 33
defaultConfig {
minSdk = 25
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
}
And the build.gradle
of the other module:
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
plugins {
kotlin("multiplatform")
id("com.android.library")
}
kotlin {
jvm()
android {
compilations.all {
kotlinOptions {
jvmTarget = "1.8"
}
}
}
listOf(
iosArm64 {
includeNativeLib(IOSArch.IPhone)
},
iosX64{ includeNativeLib(IOSArch.Simulator) },
iosSimulatorArm64 { includeNativeLib(IOSArch.Simulator) }
).forEach {
it.binaries.framework {
baseName = "otherModule"
}
}
sourceSets {
val commonMain by getting
val commonTest by getting {
dependencies {
implementation(kotlin("test"))
}
}
val jvmMain by getting
val jvmTest by getting
val androidMain by getting
val androidUnitTest by getting
val iosX64Main by getting
val iosArm64Main by getting
val iosSimulatorArm64Main by getting
val iosMain by creating {
dependsOn(commonMain)
iosX64Main.dependsOn(this)
iosArm64Main.dependsOn(this)
iosSimulatorArm64Main.dependsOn(this)
}
val iosX64Test by getting
val iosArm64Test by getting
val iosSimulatorArm64Test by getting
val iosTest by creating {
dependsOn(commonTest)
iosX64Test.dependsOn(this)
iosArm64Test.dependsOn(this)
iosSimulatorArm64Test.dependsOn(this)
}
}
}
android {
namespace = "XXX"
compileSdk = 33
defaultConfig {
minSdk = 25
}
}
enum class IOSArch(val folderName: String) {
IPhone("iphoneos"),
Simulator("iphonesimulator")
}
fun KotlinNativeTarget.includeNativeLib(arch: IOSArch) {
val opts = arrayOf(
"-framework",
"iosLib",
"-F$projectDir/iosLib/${arch.folderName}"
)
compilations.getByName("main") {
val iosLib by cinterops.creating {
defFile("/src/nativeInterop/cinterop/iosLib.def")
compilerOpts(*opts)
}
}
binaries.all {
linkerOpts(*opts)
}
}