Kotlin Multiplatform Mobile can't find klib package
Asked Answered
O

1

4

I've read these SO posts 1, 2, 3 which faced a similar problem. I'm trying to use a .klib in my KMM Android project. The Klib is built from library.h C header. Here's what I did:

I built the Kotlin Library

Using the following Gradle block in the KMM shared project:

kotlin {
    ...
    androidNativeArm64 { // target
        compilations.getByName("main") {
            val mylib by cinterops.creating {
                defFile(project.file("mylib.def"))

                packageName("c.mylib")
                // Options to be passed to compiler by cinterop tool.
                compilerOpts("-I/home/me/CLionProjects/mylib/")

                // Directories for header search (an analogue of the -I<path> compiler option).
                includeDirs.allHeaders("/home/me/CLionProjects/mylib/")

                // A shortcut for includeDirs.allHeaders.
                includeDirs("/home/me/CLionProjects/mylib/")
            }
        }
        binaries {
            sharedLib() // https://kotlinlang.org/docs/mpp-build-native-binaries.html#declare-binaries
        }
    }
}

with mylib.def file

headers = /home/me/CLionProjects/mylib/library.h
headerFilter = /home/me/CLionProjects/mylib/*
package = c.mylib

On building, the .klib and build folder appears in the classes directory of the shared project as shown below:

enter image description here

The red line under Platform is for the error:

Expected class 'Platform' has no actual declaration in module MyApplication.shared.androidNativeArm64Main for Native

but apparently that may just be a system glitch (not sure - the Alt+Enter solution to "create actual class..." doesn't seem to do anything). Assuming this is not a problem I continue...

I check the .klib details

Running .konan/.../bin/klib info mylib.klib I don't get c.mylib as the package name but com.example.myapplication:shared-cinterop-mylib instead (see below). I can live with that (although not sure why it isn't what I specified in Gradle)

Resolved to: /home/me/AndroidStudioProjects/MyApplication/shared/build/classes/kotlin/androidNativeArm64/main/shared-cinterop-mylib
Module name: <com.example.myapplication:shared-cinterop-mylib>
ABI version: 1.4.1
Compiler version: 1.4.10
Library version: null
Metadata version: 1.4.0
IR version: 1.0.0
Available targets: android_arm64

I tried including the package in my androidApp Gradle

I want to access the .klib inside my androidApp project. I tried both packages c.mylib and com.example.myapplication:shared-cinterop-mylib.

I tried adding implementation("com.example.myapplication:shared-cinterop-mylib") to my androidApp Gradle file, but got the error:

Could not determine the dependencies of task ':androidApp:lintVitalRelease'. Could not resolve all artifacts for configuration ':androidApp:debugCompileClasspath'. Could not find com.example.myapplication:shared-cinterop-mylib:. Required by: project :androidApp Possible solution:

I'm not sure if the hint would solve the problem, but I tried adding the file to the androidApp Gradle repositories { ... } block anyway using e.g.

    maven {
        url = uri("/home/me/AndroidStudioProjects/MyApplication/shared/build/classes/kotlin/androidNativeArm64/main/shared-cinterop-mylib.klib")
    }

but I'm not sure that's the right way to add a file to a repository.

Question

Can anyone help me to get the package recognised in androidApp ? I'll keep trying.

Outfitter answered 29/3, 2021 at 8:57 Comment(0)
D
5

You are probably confused about different Kotlin versions aiming for a different platform. Please check out the documentation for details, while I'll try to explain the basic concept here.

Kotlin Multiplatform in general consists of three different compilers: one to build for the JVM, one to transpile to the JS, and one to compile platform-specific native binaries(executables, static or shared libraries). KMM technology is about sharing your code between two different platforms, iOS and Android.

The important detail here is that KMM aims to make it easier for developers to include this shared code into their existing codebase - usually Swift or Objective-C for iOS and Java or Kotlin/JVM for Android. This is important because this project is built with Kotlin/Native and Kotlin/JVM.

In your code, I see an additional platform declaration. The androidNativeArm64, this is a Kotlin/Native target, not a classic Android one. It uses the other compiler, and produces a shared library as a result. This is the reason why

  1. You're able to define a cinterop block, creating bindings for the header, but
  2. Those bindings are applicable only as a part of the Kotlin/Native compilation.

KLIB, which you created here, is not a library itself, but a set of bindings for the native library(mylib). It provides you an ability to use mylibs API from Kotlin code, written in the androidNativeArm64Main source set. As you don't have such a source set at this moment, your project cannot build correctly, and shows warning about missing actual declaration.

Concluding everything described above. Here are the things you can do, and cannot do in the current state of the project:

Can

  • Use bindings from your Kotlin/Native code(add files to shared/src/androidNativeArm64Main/kotlin/)
  • Compile the code using those bindings to a shared library(.so file)
  • Use this shared library as described in the Android NDK documentation(here)

or

  • Delete the Kotlin/Native Android target, and code without C API usage at the shared/src/androidMain/kotlin/

Cannot

  • Depend on a KLIB from the Kotlin/JVM modules(e.g. androidApp project in general or androidMain source set in shared)
  • Publish bindings and do not care about linking the resulting artifact with the C library
  • Use shared or static library compiled from Kotlin/Native easier than using C/C++ native library(sorry, but it will be the same JNI thing you probably try to avoid here)

Sorry for a long and complicated answer, please contact me with any questions in the comment section below. I would be glad to correct any fo my mistakes or inaccuracies.

Debbydebee answered 2/4, 2021 at 14:36 Comment(4)
Thanks for the answer I will consider it further. What I wanted to achieve was to compile my C/C++ library into a single Kotlin library and use that single API in both the iOS and Android projects without having to use JNI in Android or other wrappers in iOS. I thought that KMM could let me compile my C++ code to a Kotlin library and use that library in both Android and iOS. But your last point makes me think that's not possible?Outfitter
Yep, this is not an option right now. KMM uses Kotlin/Native compiler, which allows you to USE C libraries(there is no C++ interop yet, but some C-like headers will also work) from Kotlin code and to link the result binary with those libraries. Or, it allows you to generate a native library, and use it from C/C++.Debbydebee
Hi Artyom. Your last sentence made me flip my thinking. Are you saying I can create a common shared Kotlin API containing expected and actual functions (actual functions implemented in the respective iOS and Android sub-projects), then compile and build that Kotlin API and pass it to two copies of my C/C++ core code each of which sit inside the iOS and Android sub-projects? If that is possible then I think that would solve my problem and I may have been thinking about KMM the wrong way round.Outfitter
Yes, if you use a Kotlin/Native target(see here, for android it will require the androidNative one), you can set the project to produce a static or dynamic library(see this doc). The result will be available from C/C++ code the same way as the library built from C sources. This approach is not so popular, so I can only refer to this old tutorial. You can always ask people here or at kotlinlang Slack for advice.Debbydebee

© 2022 - 2024 — McMap. All rights reserved.