6

I'm following the guide here to try and use an iOS framework without CocoaPods in a new KMM project:

https://kotlinlang.org/docs/kmm-add-dependencies.html#without-cocoapods

I have an existing, working .xcframework that I added to the project under shared/src. I added a MyKit.def file in src/nativeInterop/cinterop/ and updated the build.gradle.kts file in same shared dir:

MyKit.def looks like

language = Objective-C
modules = MyKit
package = MyKit

build.gradle.kts

import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget

plugins {
    kotlin("multiplatform")
    id("com.android.library")
}

kotlin {
    android()

    val iosTarget: (String, KotlinNativeTarget.() -> Unit) -> KotlinNativeTarget =
        if (System.getenv("SDK_NAME")?.startsWith("iphoneos") == true)
            ::iosArm64
        else
            ::iosX64

    iosTarget("ios") {
        binaries {
            framework {
                baseName = "shared"
            }
        }
    }
    sourceSets {
        val commonMain by getting
        val commonTest by getting {
            dependencies {
                implementation(kotlin("test-common"))
                implementation(kotlin("test-annotations-common"))
            }
        }
        val androidMain by getting
        val androidTest by getting {
            dependencies {
                implementation(kotlin("test-junit"))
                implementation("junit:junit:4.13.2")
            }
        }
        val iosMain by getting
        val iosTest by getting
    }
    iosArm64() {
        compilations.getByName("main") {
            val MyKit by cinterops.creating {
                // Path to .def file
                defFile("src/nativeInterop/cinterop/MyKit.def")
                compilerOpts("-framework", "MyKit", "-F/src/MyKit.framework")
            }
        }

        binaries.all {
            // Linker options required to link to the library -- the framework binary is located under src/MyKit.framework/ios-arm64/MyKit.framework/
            linkerOpts("-framework", "MyKit", "-F/src/MyKit.framework/ios-arm64/MyKit.framework/")
        }
    }
}

android {
    compileSdkVersion(31)
    sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
    defaultConfig {
        minSdkVersion(26)
        targetSdkVersion(31)
    }
}

After adding import MyKit.* to my MainActivity I get unknown reference errors.

Are iOS binary frameworks supported? They have a . separator in the file name so maybe that's an issue. Problem with my -F paths?? I'm not clear on whether the path should be all the way to the dir with Headers and the binary itself or just to the Framework root. TIA

mutable2112
  • 439
  • 4
  • 15
  • Hello, I got to questions to ask. 1. Have you tried with the absolute path to the directory where the framework is? I mean the path you send as `-F` option. 2. Have you performed the `cinteropMyKitIosArm64` task before using the import in your `iosArm64Main` source set? – Artyom Degtyarev Sep 14 '21 at 15:02
  • Artyom - I have not tried #1 but I will. I don't understand your second question? After adding to or modifying the `build.gradle.kts` I'm running Gradle sync and then running the app from Android Studio on a device, so I would assume the `iosArm64` step has been run. I did not see any instructions to run a cinterop task. Thanks! – mutable2112 Sep 15 '21 at 09:04
  • The second question was if you tried to sync and build before checking the code. As you've done it, now I hope the absolute path will help . – Artyom Degtyarev Sep 15 '21 at 09:35
  • i have the same problem. Did you solved ? – Domenico Nov 25 '21 at 09:30
  • @Domenico no I was not able to solve it – mutable2112 Nov 25 '21 at 15:29
  • Hello again, @mutable2112! If the problem is still relevant to you, I would like to suggest two things to check. 1. Right now its hard to understand whether your framework has `.xcframework` or `.framework` extension. If your cinterop block contains the correct extension, then just forget about this question. 2. What are the results of the `cinteropMyKitIosArm64` in your case? In general, it should create a `.klib` file located at `$projectbuild/classes/kotlin/native/main/cinterop/.` if its empty that mean the `cinterop` tool did not found the appropriate headers to generate bindings. – Artyom Degtyarev Jan 26 '22 at 15:35
  • If its true, I would recommend checking if the framework structure is not too different from the standard `MyKit.framework/module.modulemap` and `MyKit.framework/Headers/MyKitHeadersAsNamedInTheModulemap`. – Artyom Degtyarev Jan 26 '22 at 15:36

1 Answers1

3

Assuming you have the default KMM project structure with also the framework and the def file as follows:

- androidApp
- iosApp
  - MyFramework.framework
- shared
  - src
    - nativeInterop
      - cinterop
        - MyFramework.def

This is what you need to add in your build.gradle file:

    val myFrameworkDefFilePath = "src/nativeInterop/cinterop/MyFramework.def"
    val myFrameworkCompilerLinkerOpts = listOf("-framework", "MyFramework", "-F${projectDir}/../iosApp/")
    iosArm64 {
        compilations.getByName("main") {
            val MyFramework by cinterops.creating {
                // Path to .def file
                defFile(myFrameworkDefFilePath)

                compilerOpts(myFrameworkCompilerLinkerOpts)
            }
        }

        binaries.all {
            // Tell the linker where the framework is located.
            linkerOpts(myFrameworkCompilerLinkerOpts)
        }
    }

Unfortunately, as stated here, relative paths don't work correctly as compiler arguments, so what you can do is use projectDir value and from that point to where your framework is located.

Note: you need to point to the parent folder of where the framework is (in the example, I'm pointing to iosApp/, which is the parent of MyFramework.framework).

Another note: if you have an .framework, afaik, you need to replicate this code for iosX64, iosArm64, iosSimulatorArm64, whatever, to make the imports work correctly in iosMain.
If you have an .xcframework the path needs to point to the correct variant inside the .xcframework, e.g. ios-arm64_armv7, ios-arm64_i386_x86_64-simulator, whatever.

Emanuele
  • 131
  • 2
  • 6