Search code examples
androidiosgradlekotlin-native

Kotlin Native compile jar and framework


I'm building a multiplatform library for Android and iOS. My gradle file looks like this:

plugins {
    id 'org.jetbrains.kotlin.multiplatform' version '1.4.0'
}
repositories {
    mavenCentral()
}
group 'com.example'
version '0.0.1'

apply plugin: 'maven-publish'

kotlin {
    jvm()
    // This is for iPhone simulator
    // Switch here to iosArm64 (or iosArm32) to build library for iPhone device
    ios {
        binaries {
            framework()
        }
    }
    sourceSets {
        commonMain {
            dependencies {
                implementation kotlin('stdlib-common')
                implementation("com.ionspin.kotlin:bignum:0.2.2")
            }
        }
        commonTest {
            dependencies {
                implementation kotlin('test-common')
                implementation kotlin('test-annotations-common')
            }
        }
        jvmMain {
            dependencies {
                implementation("com.ionspin.kotlin:bignum:0.2.2")
            }
        }
        jvmTest {
            dependencies {
                implementation kotlin('test')
                implementation kotlin('test-junit')
            }
        }
        iosMain {
        }
        iosTest {
        }
    }
}

configurations {
    compileClasspath
}

Im using a third party library and I'm using it like this:

fun test(value: String): Int {
    return BigDecimal.parseString(value).toBigInteger().intValue()
}

The problem is when I build the .jar the bignum library isn't included, and when I use the lib in an Android project I get an exception ClassNotFoundException: Didn't find class "com.ionspin.kotlin.bignum.decimal.BigDecimal".

Is there a way to include third party libs in the .jar for Android and .framework for iOS?


Solution

  • JVM

    So, the only way I've found to generate a Fat JAR that works like you expect is by adding two custom gradle tasks in project:build.gradle.kts of your KMP library after applying the java plugin.

    plugins {
        [...]
        id("java")
    }
    
    [...]
    
    kotlin {
        jvm {
            [...]
            compilations {
                val main = getByName("main")
                tasks {
                    register<Copy>("unzip") {
                        group = "library"
                        val targetDir = File(buildDir, "3rd-libs")
                        project.delete(files(targetDir))
                        main.compileDependencyFiles.forEach {
                            println(it)
                            if (it.path.contains("com.")) {
                                from(zipTree(it))
                                into(targetDir)
                            }
                        }
                    }
                    register<Jar>("fatJar") {
                        group = "library"
                        manifest {
                            attributes["Implementation-Title"] = "Fat Jar"
                            attributes["Implementation-Version"] = archiveVersion
                        }
                        archiveBaseName.set("${project.name}-fat")
                        val thirdLibsDir = File(buildDir, "3rd-libs")
                        from(main.output.classesDirs, thirdLibsDir)
                        with(jar.get() as CopySpec)
                    }
                }
                tasks.getByName("fatJar").dependsOn("unzip")
            }
    
        }
        [...]
    }
    

    You then must launch the fatJar gradle task that generate a .jar file with the 3rd libraries classes extracted from their corresponding jar archives.

    You can customize the two custom gradle scripts even more in order to better fit your needs (here I only included dependencies in com.* package).

    enter image description here

    Then in your Android app app:build.gradle file you can use it as you did or simply

    implementation files('libs/KMLibraryTest001-fat-1.0-SNAPSHOT.jar')

    iOS

    As you ask also for the iOS part in your title (even if it's a second citizen in the main topic of your question) you need only to use api instead of implementation for your 3rd party library along with the export option of the framework.

    ios() {
        binaries {
            framework() {
                transitiveExport = true // all libraries
                //export(project(":LibA")) // this library project in a transitive way
                //export("your 3rd party lib") // this 3rd party lib in a transitive way
            }
        }
    }
    

    And you can find a full reference here.