From 3e90312b3f2d912bf27e91b454a6ef21a81a2fc5 Mon Sep 17 00:00:00 2001 From: Jakob Odersky Date: Fri, 4 Dec 2015 20:50:53 -0800 Subject: initial commit --- .../main/scala/ch/jodersky/jni/NativeLoader.scala | 75 ++++++++++++++++++++++ .../src/main/scala/ch/jodersky/jni/Platform.scala | 50 +++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 jni-library/src/main/scala/ch/jodersky/jni/NativeLoader.scala create mode 100644 jni-library/src/main/scala/ch/jodersky/jni/Platform.scala (limited to 'jni-library/src/main/scala/ch') diff --git a/jni-library/src/main/scala/ch/jodersky/jni/NativeLoader.scala b/jni-library/src/main/scala/ch/jodersky/jni/NativeLoader.scala new file mode 100644 index 0000000..46d4b2d --- /dev/null +++ b/jni-library/src/main/scala/ch/jodersky/jni/NativeLoader.scala @@ -0,0 +1,75 @@ +package ch.jodersky.jni + +import java.io.{File, FileOutputStream, InputStream, OutputStream} +import scala.io.Source + +/** + * Provides enhanced native library loading functionality. + */ +object NativeLoader { + + /** Name of the shared library file that is contained in a jar. */ + final val LibraryName = "library" + + final val BufferSize = 4096 + + /** Extract a resource from this class loader to a temporary file. */ + private def extract(path: String): Option[File] = { + var in: Option[InputStream] = None + var out: Option[OutputStream] = None + + try { + in = Option(NativeLoader.getClass.getResourceAsStream(path)) + if (in.isEmpty) return None + + val file = File.createTempFile(path, "") + out = Some(new FileOutputStream(file)) + + val buffer = new Array[Byte](BufferSize) + var length = -1; + do { + length = in.get.read(buffer) + if (length != -1) out.get.write(buffer, 0, length) + } while (length != -1) + + Some(file) + } finally { + in.foreach(_.close) + out.foreach(_.close) + } + } + + private def loadError(msg: String): Nothing = throw new UnsatisfiedLinkError( + "Error during native library extraction " + + "(this can happen if your platform is not supported): " + + msg + ) + + def fullLibraryPath(libraryPath: String, platform: Platform) = { + libraryPath + "/native/" + platform.id + "/" + LibraryName + } + + private def loadFromJar(libraryPath: String): Unit = { + val platform = Platform.current.getOrElse{ + loadError("Cannot determine current platform.") + } + + val resource = fullLibraryPath(libraryPath, platform) + + val file = extract(resource) getOrElse loadError( + s"Shared library $resource not found." + ) + System.load(file.getAbsolutePath()) + } + + /** + * Load a native library from the available library path or fall back + * to extracting and loading a native library from available resources. + */ + def load(library: String, libraryPath: String): Unit = try { + System.loadLibrary(library) + } catch { + case ex: UnsatisfiedLinkError => loadFromJar(libraryPath) + } + +} diff --git a/jni-library/src/main/scala/ch/jodersky/jni/Platform.scala b/jni-library/src/main/scala/ch/jodersky/jni/Platform.scala new file mode 100644 index 0000000..db1662d --- /dev/null +++ b/jni-library/src/main/scala/ch/jodersky/jni/Platform.scala @@ -0,0 +1,50 @@ +package ch.jodersky.jni + +import java.io.IOException +import scala.sys.process.Process + +/** + * A platform is the representation of an architecture-kernel combination. + * It is a somewhat ambigous concept defined as the set of all system configurations + * capable of running a native binary. + */ +case class Platform private (arch: String, kernel: String) { + + /** + * String representation of this platform. It is inspired by Autotools' platform + * triplet, without the vendor field. + */ + def id = arch + "-" + kernel + +} + +object Platform { + + final val Unknown = Platform("unknown", "unknown") + + /** Create a platform with spaces stripped and case normalized. */ + def normalized(arch: String, kernel: String): Platform = { + def normalize(str: String) = str.toLowerCase.filter(!_.isWhitespace) + Platform(normalize(arch), normalize(kernel)) + } + + /** Run 'uname' to determine current platform. Returns None if uname does not exist. */ + def uname: Option[Platform] = { + val lineOpt = try { + Some(Process("uname -sm").lines.head) + } catch { + case _: IOException => None + } + lineOpt.map { line => + val parts = line.split(" ") + if (parts.length != 2) { + sys.error("Could not determine platform: 'uname -sm' returned unexpected string: " + line) + } else { + Platform.normalized(parts(1), parts(0)) + } + } + } + + def current = uname + +} -- cgit v1.2.3