kotlin-multiplatform-libsodium
kotlin-multiplatform-libsodium copied to clipboard
KMP Testing can't load libdynamic-linux-arm64-libsodium.so in initialization
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")
}