aboutsummaryrefslogblamecommitdiff
path: root/macros/src/main/scala/ch/jodersky/jni/annotations.scala
blob: 6805ebf3dd6df2e3453b7d71c51ff95aaa93b2a1 (plain) (tree)
1
2
3
4
5
6
7
8
9
10

                       
                           
 


                                              


                                        
  

                                         
 
                                                    

























                                                                                        









































                                                                                                            

             

                                                
                     
                                                             

             
                  
           
             
 
                                                                            













                                                                                                           


                   


                                                                       


























































































                                                                                                    
 
package ch.jodersky.jni

//import macrocompat.bundle

//import scala.language.experimental.macros
//import scala.reflect.macros.whitebox.Context
import scala.collection.immutable.Seq
import scala.annotation.StaticAnnotation
import scala.annotation.compileTimeOnly

/*
@bundle
class nativeLoaderMacro(val c: Context) {

  def impl(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 extra = q"""
          {
            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 = {
                val line = try {
                  scala.sys.process.Process("uname -sm").lines.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
                }
              }

              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)
            }

            def load(): Unit = try {
              System.loadLibrary($nativeLibrary)
            } catch {
              case ex: UnsatisfiedLinkError => loadPackaged()
            }

            load()
          }
          """

        ModuleDef(mods, name, Template(parents, self, body :+ extra)) :: 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)
  }

}
 */

import scala.meta._

@compileTimeOnly("Macro Paradise must be enabled to apply annotation.")
class nativeLoader(nativeLibrary: String) extends StaticAnnotation {

  inline def apply(defn: Any): Any = meta {
    val nativeLibrary = this match {
      case q"new $_(${Lit(arg: String)})" => arg
      case _ => throw new IllegalArgumentException("Native library must be a constant")
    }

    defn match {

      case Term.Block(Seq(
        cls: Defn.Class,
        companion: Defn.Object)) =>

        val newStats: Seq[Stat] = MacroUtil.staticInitializer(nativeLibrary) +:
          companion.templ.stats.getOrElse(Nil)

        val newCompanion = companion.copy(
          templ = companion.templ.copy(stats = Some(newStats)))

        Term.Block(Seq(cls, newCompanion))

      case companion: Defn.Object =>
        val newStats: Seq[Stat] = MacroUtil.staticInitializer(nativeLibrary) +:
          companion.templ.stats.getOrElse(Nil)

        val newCompanion = companion.copy(
          templ = companion.templ.copy(stats = Some(newStats)))

        Term.Block(Seq(newCompanion))

      case _ =>
        throw new IllegalArgumentException(
          "nativeLoader can only be annotated to classes and singleton objects")
    }
  }
}

object MacroUtil {

  def staticInitializer(nativeLibrary: String) = q"""{
    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 = {
        val line = try {
          scala.sys.process.Process("uname -sm").lines.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
        }
      }

      val resourcePath: String = "/native/" + plat + "/" + lib
      val resourceStream = Option(this.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)
    }

    def load(): Unit = try {
      System.loadLibrary($nativeLibrary)
    } catch {
      case ex: UnsatisfiedLinkError => loadPackaged()
    }

    load()
  }"""

}