aboutsummaryrefslogtreecommitdiff
path: root/macros
diff options
context:
space:
mode:
authorJakob Odersky <jakob@odersky.com>2016-05-02 05:19:07 -0700
committerJakob Odersky <jakob@odersky.com>2016-05-11 11:17:09 -0700
commit791cb363b77332e3abdf4039102dfcdb863ce6c3 (patch)
tree09ff5d807a1407abedade57b692204ceac3f3280 /macros
parent49563ee13599b0cb1add27b24446677a13b1f563 (diff)
downloadsbt-jni-791cb363b77332e3abdf4039102dfcdb863ce6c3.tar.gz
sbt-jni-791cb363b77332e3abdf4039102dfcdb863ce6c3.tar.bz2
sbt-jni-791cb363b77332e3abdf4039102dfcdb863ce6c3.zip
Use macro annotation to load native library
This also removes the need for third projects to depend on a "loader library".
Diffstat (limited to 'macros')
-rw-r--r--macros/src/main/scala/ch/jodersky/jni/annotations.scala102
-rw-r--r--macros/src/main/scala/ch/jodersky/jni/util/PlatformMacros.scala31
2 files changed, 133 insertions, 0 deletions
diff --git a/macros/src/main/scala/ch/jodersky/jni/annotations.scala b/macros/src/main/scala/ch/jodersky/jni/annotations.scala
new file mode 100644
index 0000000..c7a6555
--- /dev/null
+++ b/macros/src/main/scala/ch/jodersky/jni/annotations.scala
@@ -0,0 +1,102 @@
+package ch.jodersky.jni
+
+import util.PlatformMacros
+
+import scala.language.experimental.macros
+
+import scala.reflect.macros.whitebox.Context
+import scala.annotation.StaticAnnotation
+import scala.annotation.compileTimeOnly
+
+object nativeLoaderMacro {
+
+ def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
+ import c.universe._
+
+ val nativeLibrary: String = c.prefix.tree match {
+ case Apply(_, List(Literal(Constant(x: String)))) => x
+ case Apply(_, xs :: tail) => c.abort(xs.pos, "Native library must be a constant.")
+ case t => c.abort(t.pos, "Native library not specified.")
+ }
+
+ def inject(annottees: List[Tree]): List[Tree] = annottees match {
+
+ case ClassDef(mods, name, tparams, Template(parents, self, body)) :: tail =>
+ val extra = q"""
+ {
+ ${name.toTermName}
+ }
+ """
+
+ val module: List[Tree] = tail match {
+ case Nil => inject(List(q"""object ${name.toTermName}"""))
+ case other => inject(other)
+ }
+
+ ClassDef(mods, name, tparams, Template(parents, self, body :+ extra)) :: module
+
+ //q"$mods object $name extends ..$parents {$self => ..$body }" :: Nil =>
+ case ModuleDef(mods, name, Template(parents, self, body)) :: Nil =>
+ val loadPackagedDef: DefDef = q"""
+ private def loadPackaged(): Unit = {
+ import java.io.File
+ import java.nio.file.{Files, Path}
+
+ val lib: String = System.mapLibraryName($nativeLibrary)
+
+ val tmp: Path = Files.createTempDirectory("jni-")
+ val plat: String = ${PlatformMacros.current(c)}
+
+ val resourcePath: String = "/native/" + plat + "/" + lib
+ val resourceStream = Option($name.getClass.getResourceAsStream(resourcePath)) match {
+ case Some(s) => s
+ case None => throw new UnsatisfiedLinkError(
+ "Native library " + lib + " (" + resourcePath + ") cannot be found on the classpath.")
+ }
+
+ val extractedPath = tmp.resolve(lib)
+
+ try {
+ Files.copy(resourceStream, extractedPath)
+ } catch {
+ case ex: Exception => throw new UnsatisfiedLinkError(
+ "Error while extracting native library: " + ex)
+ }
+
+ System.load(extractedPath.toAbsolutePath.toString)
+ }
+ """
+ val loadDef: DefDef = q"""
+ private def load(): Unit = try {
+ System.loadLibrary($nativeLibrary)
+ } catch {
+ case ex: UnsatisfiedLinkError => loadPackaged()
+ }
+ """
+
+ val extraBody = q"""
+ $loadPackagedDef
+ $loadDef
+ load()
+ """
+
+ ModuleDef(mods, name, Template(parents, self, body :+ extraBody)) :: Nil
+
+ case _ =>
+ c.abort(c.enclosingPosition, "nativeLoader can only be annotated to classes and singleton objects")
+
+ }
+
+ val outputs = inject(annottees.map(_.tree).toList)
+
+ val result: Tree = Block(outputs, Literal(Constant(())))
+
+ c.Expr[Any](result)
+ }
+
+}
+
+@compileTimeOnly("Macro Paradise must be enabled to apply annotation.")
+class nativeLoader(nativeLibrary: String) extends StaticAnnotation {
+ def macroTransform(annottees: Any*): Any = macro nativeLoaderMacro.impl
+}
diff --git a/macros/src/main/scala/ch/jodersky/jni/util/PlatformMacros.scala b/macros/src/main/scala/ch/jodersky/jni/util/PlatformMacros.scala
new file mode 100644
index 0000000..a4af100
--- /dev/null
+++ b/macros/src/main/scala/ch/jodersky/jni/util/PlatformMacros.scala
@@ -0,0 +1,31 @@
+package ch.jodersky.jni
+package util
+
+import scala.language.experimental.macros
+
+import scala.reflect.macros.whitebox.Context
+
+object PlatformMacros {
+
+ // arch-kernel
+ def current(c: Context): c.Expr[String] = {
+ import c.universe._
+ val result = q"""
+ val line = try {
+ scala.sys.process.Process("uname -sm").lineStream.head
+ } catch {
+ case ex: Exception => sys.error("Error running `uname` command")
+ }
+ val parts = line.split(" ")
+ if (parts.length != 2) {
+ sys.error("Could not determine platform: 'uname -sm' returned unexpected string: " + line)
+ } else {
+ val arch = parts(1).toLowerCase.replaceAll("\\s", "")
+ val kernel = parts(0).toLowerCase.replaceAll("\\s", "")
+ arch + "-" + kernel
+ }
+ """
+ c.Expr[String](result)
+ }
+
+}