kotlin-multiplatform-libsodium icon indicating copy to clipboard operation
kotlin-multiplatform-libsodium copied to clipboard

KMP Testing can't load libdynamic-linux-arm64-libsodium.so in initialization

Open RoyalSWiSH opened this issue 8 months ago • 5 comments

HI,

I try to initialize Libsodium from a test environment in a Kotlin Multiplattform project in the shared module from Android Studio.

import com.ionspin.kotlin.crypto.sample.EncryptionUtils
import com.ionspin.kotlin.crypto.secretbox.SecretBox
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi
import kotlin.test.Test
import kotlin.test.assertContains
import kotlin.test.assertEquals
import com.ionspin.kotlin.crypto.LibsodiumInitializer
import kotlinx.coroutines.runBlocking


class EncryptionUtilsTest {


    @Test
    @OptIn(ExperimentalUnsignedTypes::class, ExperimentalEncodingApi::class)
    fun encryptMessageWithAssociatedDataTest() = runBlocking {
        LibsodiumInitializer.initializeWithCallback {
//                logger.info("Libsodium initialized")
        }
        val message = "Hello"
        val key = SecretBox.keygen()
        val encryptedMessage = EncryptionUtils.encryptMessageWithAssociatedData(message, key)
        val decryptedMessage = EncryptionUtils.decryptMessageWithAssociatedData(encryptedMessage, key)
        assertEquals(message, decryptedMessage)
    }

But it fails with this error

> Task :shared:testDebugUnitTest FAILED

java.lang.NullPointerException: Cannot invoke "java.net.URL.getFile()" because "url" is null
	at com.goterl.resourceloader.ResourceLoader.getFileFromFileSystem(ResourceLoader.java:244)
	at com.goterl.resourceloader.ResourceLoader.copyToTempDirectory(ResourceLoader.java:88)
	at com.goterl.resourceloader.SharedLibraryLoader.load(SharedLibraryLoader.java:53)
	at com.goterl.resourceloader.SharedLibraryLoader.load(SharedLibraryLoader.java:47)
	at com.ionspin.kotlin.crypto.LibsodiumInitializer.loadLibrary(LibsodiumInitializer.kt:19)
	at com.ionspin.kotlin.crypto.LibsodiumInitializer.initializeWithCallback(LibsodiumInitializer.kt:58)

My guess is, that it can't find the libsodium file in the testing environment here


    private fun loadLibrary() : JnaLibsodiumInterface {
        val libraryFile = when {
            Platform.isMac() -> {
                SharedLibraryLoader.get().load("libdynamic-macos.dylib", JnaLibsodiumInterface::class.java)
            }
            Platform.isLinux() -> {
                if (Platform.isARM()) {
                    SharedLibraryLoader.get().load("libdynamic-linux-arm64-libsodium.so", JnaLibsodiumInterface::class.java)
                } else {
                    SharedLibraryLoader.get()
                        .load("libdynamic-linux-x86-64-libsodium.so", JnaLibsodiumInterface::class.java)
                }
            }
            Platform.isWindows() -> {
                SharedLibraryLoader.get().load("libdynamic-msvc-x86-64-libsodium.dll", JnaLibsodiumInterface::class.java)
            }
            Platform.isAndroid() -> {
                File("irrelevant")
            }
            else -> throw RuntimeException("Unknown platform")

        }

This is the build.gradle.kts file from the shared module

import org.jetbrains.kotlin.gradle.tasks.KotlinNativeLink

plugins {
    alias(libs.plugins.kotlinMultiplatform)
    alias(libs.plugins.androidLibrary)
    id("org.jetbrains.kotlin.plugin.serialization") version "1.8.21"
    id("co.touchlab.skie") version "0.6.1"
}

kotlin {
    androidTarget {
        compilations.all {
            kotlinOptions {
                jvmTarget = "1.8"
            }
        }
    }
    jvm {
        // Do not use withJava() for JVM targets in a multiplatform project with Android
    }

    val iosTargets = listOf(
        iosX64(),
        iosArm64(),
        iosSimulatorArm64()
    )

    iosTargets.forEach {
        it.binaries.framework {
            baseName = "shared"
            isStatic = true
            freeCompilerArgs += listOf(
                "-Xoverride-konan-properties=osVersionMin.ios_arm32=16.1;osVersionMin.ios_arm64=16.1;osVersionMin.ios_x64=16.1",
                "-Xoverride-konan-properties=minVersion.ios=16.1;minVersionSinceXcode15.ios=16.1",
                "-Xbinary=bundleId=bio.gelly.proteinlookup"
            )
        }
    }

    sourceSets {
        val commonMain by getting {
            dependencies {
                implementation(libs.kotlinx.serialization.json)
                implementation("app.softwork:kotlinx-uuid-core:0.0.22")
                implementation(libs.ktor.client.core)
                implementation(libs.ktor.client.cio)
                implementation(libs.ktor.client.json)
                implementation(libs.ktor.client.content.negotiation)
                implementation(libs.ktor.serialization.kotlinx.json)
                implementation("com.ionspin.kotlin:multiplatform-crypto-libsodium-bindings:0.9.2")
            }
        }

        val iosMain by creating {
            dependencies {
                implementation("com.ionspin.kotlin:multiplatform-crypto-libsodium-bindings:0.9.2")
                implementation(libs.ktor.client.darwin)
            }
        }

        iosTargets.forEach {
            it.compilations["main"].defaultSourceSet {
                dependsOn(commonMain)
                dependsOn(iosMain)
            }
        }

        commonTest.dependencies {
            implementation(libs.kotlin.test)
            implementation(kotlin("test-common"))
            implementation(kotlin("test-annotations-common"))
            implementation("com.ionspin.kotlin:multiplatform-crypto-libsodium-bindings:0.9.2")
            implementation("org.slf4j:slf4j-simple:2.0.13") // or the latest version
            implementation("com.ionspin.kotlin:multiplatform-crypto-libsodium-bindings:0.9.2")
            implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0")

        }
        val jvmMain by getting {
            dependencies {
                implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")

               // Added for testing EncryptiionUtils which produces a SLF4J error otherwise
                implementation("org.slf4j:slf4j-simple:2.0.13") // or the latest version
            }
        }
        val jvmTest by getting {
            dependencies {
                implementation("org.jetbrains.kotlin:kotlin-test")
                implementation("org.jetbrains.kotlin:kotlin-test-junit")
                implementation("org.slf4j:slf4j-simple:2.0.13") // or the latest version
                implementation("com.ionspin.kotlin:multiplatform-crypto-libsodium-bindings-jvm:0.9.2")

            }
        }

        // Ensure resources are included
        tasks.withType<Test> {
            systemProperty("java.library.path", "$buildDir/resources/main")
        }

    }

    // Workaround for compilation bug
//    tasks.register("testClasses")
}

android {
    namespace = "example.app.test"
    compileSdk = 34
    defaultConfig {
        minSdk = 29
    }

    sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
    sourceSets["main"].res.srcDirs("src/androidMain/res")
    sourceSets["main"].assets.srcDirs("src/androidMain/assets")
    sourceSets["main"].java.srcDirs("src/androidMain/kotlin")
    sourceSets["test"].java.srcDirs("src/androidTest/kotlin")
}

RoyalSWiSH avatar Jun 15 '24 19:06 RoyalSWiSH