From 67a34220c068a0fb2dcb43cecbc844dd2bb8f102 Mon Sep 17 00:00:00 2001 From: Darius Maitia Date: Sun, 28 Jul 2024 15:43:46 +0200 Subject: [PATCH] Zenoh native lib loading refactor --- .../src/androidMain/kotlin/io.zenoh/Zenoh.kt | 15 +- .../src/commonMain/kotlin/io/zenoh/Session.kt | 4 - .../src/commonMain/kotlin/io/zenoh/Zenoh.kt | 6 +- .../kotlin/io/zenoh/jni/JNIKeyExpr.kt | 8 +- .../kotlin/io/zenoh/jni/JNISession.kt | 6 + .../commonTest/kotlin/io/zenoh/KeyExprTest.kt | 4 - .../kotlin/io/zenoh/SelectorTest.kt | 4 - .../src/jvmMain/kotlin/io/zenoh/Zenoh.kt | 229 +++++++++--------- 8 files changed, 125 insertions(+), 151 deletions(-) diff --git a/zenoh-kotlin/src/androidMain/kotlin/io.zenoh/Zenoh.kt b/zenoh-kotlin/src/androidMain/kotlin/io.zenoh/Zenoh.kt index c8d9ad082..2d8c3d5ca 100644 --- a/zenoh-kotlin/src/androidMain/kotlin/io.zenoh/Zenoh.kt +++ b/zenoh-kotlin/src/androidMain/kotlin/io.zenoh/Zenoh.kt @@ -18,18 +18,9 @@ package io.zenoh * Static singleton class to load the Zenoh native library once and only once, as well as the logger in function of the * log level configuration. */ -internal actual class Zenoh private actual constructor() { - - actual companion object { - private const val ZENOH_LIB_NAME = "zenoh_jni" - private const val ZENOH_LOGS_PROPERTY = "zenoh.logger" - - private var instance: Zenoh? = null - - actual fun load() { - instance ?: Zenoh().also { instance = it } - } - } +internal actual object ZenohLoad { + private const val ZENOH_LIB_NAME = "zenoh_jni" + private const val ZENOH_LOGS_PROPERTY = "zenoh.logger" init { System.loadLibrary(ZENOH_LIB_NAME) diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/Session.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/Session.kt index 906ea6e5e..79f6e596a 100644 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/Session.kt +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/Session.kt @@ -78,10 +78,6 @@ class Session private constructor(private val config: Config) : AutoCloseable { } } - init { - Zenoh.load() - } - /** Close the session. */ override fun close() { jniSession?.close() diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/Zenoh.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/Zenoh.kt index ee1561306..a6861ba8f 100644 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/Zenoh.kt +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/Zenoh.kt @@ -18,8 +18,4 @@ package io.zenoh * Static singleton class to load the Zenoh native library once and only once, as well as the logger in function of the * log level configuration. */ -internal expect class Zenoh private constructor() { - companion object { - fun load() - } -} +internal expect object ZenohLoad \ No newline at end of file diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt index b8d4e9b57..553df26fb 100644 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNIKeyExpr.kt @@ -14,19 +14,21 @@ package io.zenoh.jni -import io.zenoh.Zenoh +import io.zenoh.ZenohLoad import io.zenoh.keyexpr.KeyExpr internal class JNIKeyExpr(internal val ptr: Long) { companion object { + init { + ZenohLoad + } + fun tryFrom(keyExpr: String): Result = runCatching { - Zenoh.load() // It may happen the zenoh library is not yet loaded when creating a key expression. KeyExpr(tryFromViaJNI(keyExpr)) } fun autocanonize(keyExpr: String): Result = runCatching { - Zenoh.load() KeyExpr(autocanonizeViaJNI(keyExpr)) } diff --git a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt index 93a67df21..ceaba4678 100644 --- a/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt +++ b/zenoh-kotlin/src/commonMain/kotlin/io/zenoh/jni/JNISession.kt @@ -44,6 +44,12 @@ import java.util.concurrent.atomic.AtomicLong /** Adapter class to handle the communication with the Zenoh JNI code for a [Session]. */ internal class JNISession { + companion object { + init { + ZenohLoad + } + } + /* Pointer to the underlying Rust zenoh session. */ private var sessionPtr: AtomicLong = AtomicLong(0) diff --git a/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/KeyExprTest.kt b/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/KeyExprTest.kt index e43e35703..37116af9c 100644 --- a/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/KeyExprTest.kt +++ b/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/KeyExprTest.kt @@ -21,10 +21,6 @@ import kotlin.test.* class KeyExprTest { - init { - Zenoh.load() - } - @Test fun creation_TryFromTest() { // A couple of examples of valid and invalid key expressions. diff --git a/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/SelectorTest.kt b/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/SelectorTest.kt index 0a579563c..87b1a2d29 100644 --- a/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/SelectorTest.kt +++ b/zenoh-kotlin/src/commonTest/kotlin/io/zenoh/SelectorTest.kt @@ -9,10 +9,6 @@ import kotlin.test.assertFailsWith class SelectorTest { - init { - Zenoh.load() - } - @Test fun selectorFromStringTest() { "a/b/c?arg1=val1".intoSelector().getOrThrow().use { selector: Selector -> diff --git a/zenoh-kotlin/src/jvmMain/kotlin/io/zenoh/Zenoh.kt b/zenoh-kotlin/src/jvmMain/kotlin/io/zenoh/Zenoh.kt index d6b7dd9b4..7f1c08816 100644 --- a/zenoh-kotlin/src/jvmMain/kotlin/io/zenoh/Zenoh.kt +++ b/zenoh-kotlin/src/jvmMain/kotlin/io/zenoh/Zenoh.kt @@ -15,155 +15,146 @@ package io.zenoh import java.io.File +import java.io.FileInputStream import java.io.FileOutputStream import java.io.InputStream -import java.io.FileInputStream import java.util.zip.ZipInputStream /** * Static singleton class to load the Zenoh native library once and only once, as well as the logger in function of the * log level configuration. */ -internal actual class Zenoh private actual constructor() { - - actual companion object { - private const val ZENOH_LIB_NAME = "zenoh_jni" - private const val ZENOH_LOGS_PROPERTY = "zenoh.logger" +internal actual object ZenohLoad { + private const val ZENOH_LIB_NAME = "zenoh_jni" + private const val ZENOH_LOGS_PROPERTY = "zenoh.logger" - private var instance: Zenoh? = null + init { + // Try first to load the local native library for cases in which the module was built locally, + // otherwise try to load from the JAR. + if (tryLoadingLocalLibrary().isFailure) { + val target = determineTarget().getOrThrow() + tryLoadingLibraryFromJarPackage(target).getOrThrow() + } - actual fun load() { - instance ?: Zenoh().also { instance = it } + val logLevel = System.getProperty(ZENOH_LOGS_PROPERTY) + if (logLevel != null) { + Logger.start(logLevel) } + } - /** - * Determine target - * - * Determines the [Target] corresponding to the machine on top of which the native code will run. - * - * @return A result with the target. - */ - private fun determineTarget(): Result = runCatching { - val osName = System.getProperty("os.name").lowercase() - val osArch = System.getProperty("os.arch") - - val target = when { - osName.contains("win") -> when { - osArch.contains("x86_64") || osArch.contains("amd64") -> Target.WINDOWS_X86_64_MSVC - else -> throw UnsupportedOperationException("Unsupported architecture: $osArch") - } - - osName.contains("mac") -> when { - osArch.contains("x86_64") || osArch.contains("amd64") -> Target.APPLE_X86_64 - osArch.contains("aarch64") -> Target.APPLE_AARCH64 - else -> throw UnsupportedOperationException("Unsupported architecture: $osArch") - } - - osName.contains("nix") || osName.contains("nux") || osName.contains("aix") -> when { - osArch.contains("x86_64") || osArch.contains("amd64") -> Target.LINUX_X86_64 - osArch.contains("aarch64") -> Target.LINUX_AARCH64 - else -> throw UnsupportedOperationException("Unsupported architecture: $osArch") - } - - else -> throw UnsupportedOperationException("Unsupported platform: $osName") + /** + * Determine target + * + * Determines the [Target] corresponding to the machine on top of which the native code will run. + * + * @return A result with the target. + */ + private fun determineTarget(): Result = runCatching { + val osName = System.getProperty("os.name").lowercase() + val osArch = System.getProperty("os.arch") + + val target = when { + osName.contains("win") -> when { + osArch.contains("x86_64") || osArch.contains("amd64") -> Target.WINDOWS_X86_64_MSVC + else -> throw UnsupportedOperationException("Unsupported architecture: $osArch") } - return Result.success(target) - } - /** - * Unzip library. - * - * The Zenoh libraries are stored within the JAR as compressed ZIP files. - * The location of the zipped files is expected to be under target/target.zip. - * It is expected that the zip file only contains the compressed library. - * - * The uncompressed library will be stored temporarily and deleted on exit. - * - * @param compressedLib Input stream pointing to the compressed library. - * @return A result with the uncompressed library file. - */ - private fun unzipLibrary(compressedLib: InputStream): Result = runCatching { - val zipInputStream = ZipInputStream(compressedLib) - val buffer = ByteArray(1024) - val zipEntry = zipInputStream.nextEntry - - val library = File.createTempFile(zipEntry!!.name, ".tmp") - library.deleteOnExit() - - val parent = library.parentFile - if (!parent.exists()) { - parent.mkdirs() + osName.contains("mac") -> when { + osArch.contains("x86_64") || osArch.contains("amd64") -> Target.APPLE_X86_64 + osArch.contains("aarch64") -> Target.APPLE_AARCH64 + else -> throw UnsupportedOperationException("Unsupported architecture: $osArch") } - val fileOutputStream = FileOutputStream(library) - var len: Int - while (zipInputStream.read(buffer).also { len = it } > 0) { - fileOutputStream.write(buffer, 0, len) + osName.contains("nix") || osName.contains("nux") || osName.contains("aix") -> when { + osArch.contains("x86_64") || osArch.contains("amd64") -> Target.LINUX_X86_64 + osArch.contains("aarch64") -> Target.LINUX_AARCH64 + else -> throw UnsupportedOperationException("Unsupported architecture: $osArch") } - fileOutputStream.close() - zipInputStream.closeEntry() - zipInputStream.close() - return Result.success(library) + else -> throw UnsupportedOperationException("Unsupported platform: $osName") } + return Result.success(target) + } - private fun loadLibraryAsInputStream(target: Target): Result = runCatching { - val libUrl = ClassLoader.getSystemClassLoader().getResourceAsStream("$target/$target.zip")!! - val uncompressedLibFile = unzipLibrary(libUrl) - return Result.success(FileInputStream(uncompressedLibFile.getOrThrow())) + /** + * Unzip library. + * + * The Zenoh libraries are stored within the JAR as compressed ZIP files. + * The location of the zipped files is expected to be under target/target.zip. + * It is expected that the zip file only contains the compressed library. + * + * The uncompressed library will be stored temporarily and deleted on exit. + * + * @param compressedLib Input stream pointing to the compressed library. + * @return A result with the uncompressed library file. + */ + private fun unzipLibrary(compressedLib: InputStream): Result = runCatching { + val zipInputStream = ZipInputStream(compressedLib) + val buffer = ByteArray(1024) + val zipEntry = zipInputStream.nextEntry + + val library = File.createTempFile(zipEntry!!.name, ".tmp") + library.deleteOnExit() + + val parent = library.parentFile + if (!parent.exists()) { + parent.mkdirs() } - @Suppress("UnsafeDynamicallyLoadedCode") - private fun loadZenohJNI(inputStream: InputStream) { - val tempLib = File.createTempFile("tempLib", ".tmp") - tempLib.deleteOnExit() + val fileOutputStream = FileOutputStream(library) + var len: Int + while (zipInputStream.read(buffer).also { len = it } > 0) { + fileOutputStream.write(buffer, 0, len) + } + fileOutputStream.close() - FileOutputStream(tempLib).use { output -> - inputStream.copyTo(output) - } + zipInputStream.closeEntry() + zipInputStream.close() + return Result.success(library) + } - System.load(tempLib.absolutePath) - } + private fun loadLibraryAsInputStream(target: Target): Result = runCatching { + val libUrl = ClassLoader.getSystemClassLoader().getResourceAsStream("$target/$target.zip")!! + val uncompressedLibFile = unzipLibrary(libUrl) + return Result.success(FileInputStream(uncompressedLibFile.getOrThrow())) + } - /** - * Load library from jar package. - * - * Attempts to load the library corresponding to the `target` specified from the zenoh kotlin jar. - * - * @param target - */ - private fun tryLoadingLibraryFromJarPackage(target: Target): Result = runCatching { - val lib: Result = loadLibraryAsInputStream(target) - lib.onSuccess { loadZenohJNI(it) }.onFailure { throw Exception("Unable to load Zenoh JNI: $it") } - } + @Suppress("UnsafeDynamicallyLoadedCode") + private fun loadZenohJNI(inputStream: InputStream) { + val tempLib = File.createTempFile("tempLib", ".tmp") + tempLib.deleteOnExit() - /** - * Try loading local library. - * - * This function aims to load the default library that is usually included when building the zenoh kotlin library - * locally. - */ - private fun tryLoadingLocalLibrary(): Result = runCatching { - val lib = ClassLoader.getSystemClassLoader().findLibraryStream(ZENOH_LIB_NAME) - if (lib != null) { - loadZenohJNI(lib) - } else { - throw Exception("Unable to load local Zenoh JNI.") - } + FileOutputStream(tempLib).use { output -> + inputStream.copyTo(output) } + + System.load(tempLib.absolutePath) } - init { - // Try first to load the local native library for cases in which the module was built locally, - // otherwise try to load from the JAR. - if (tryLoadingLocalLibrary().isFailure) { - val target = determineTarget().getOrThrow() - tryLoadingLibraryFromJarPackage(target).getOrThrow() - } + /** + * Load library from jar package. + * + * Attempts to load the library corresponding to the `target` specified from the zenoh kotlin jar. + * + * @param target + */ + private fun tryLoadingLibraryFromJarPackage(target: Target): Result = runCatching { + val lib: Result = loadLibraryAsInputStream(target) + lib.onSuccess { loadZenohJNI(it) }.onFailure { throw Exception("Unable to load Zenoh JNI: $it") } + } - val logLevel = System.getProperty(ZENOH_LOGS_PROPERTY) - if (logLevel != null) { - Logger.start(logLevel) + /** + * Try loading local library. + * + * This function aims to load the default library that is usually included when building the zenoh kotlin library + * locally. + */ + private fun tryLoadingLocalLibrary(): Result = runCatching { + val lib = ClassLoader.getSystemClassLoader().findLibraryStream(ZENOH_LIB_NAME) + if (lib != null) { + loadZenohJNI(lib) + } else { + throw Exception("Unable to load local Zenoh JNI.") } } }