0

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)
    }
}

Balazs
  • 35
  • 4

0 Answers0