From f58311e23153fdba2b950af42bf78fdb9719dc4f Mon Sep 17 00:00:00 2001 From: Jakob Odersky Date: Thu, 1 Dec 2016 00:04:50 -0800 Subject: Implement basic build with scala meta --- build.sbt | 15 +-- .../main/scala/ch/jodersky/jni/annotations.scala | 103 ++++++++++++++++++++- .../ch/jodersky/sbt/jni/plugins/JniLoad.scala | 8 +- 3 files changed, 112 insertions(+), 14 deletions(-) diff --git a/build.sbt b/build.sbt index 30fed7c..43e5de2 100644 --- a/build.sbt +++ b/build.sbt @@ -1,8 +1,8 @@ -val scalaVersions = Seq("2.12.0", "2.11.8", "2.10.6") -val macrosParadiseVersion = "2.1.0" +val scalaVersions = Seq("2.11.8", "2.12.0", "2.10.6") +val macrosParadiseVersion = "3.0.0.132" // version is derived from latest git tag -version in ThisBuild := ("git describe --always --dirty --match v[0-9].*" !!).tail.trim +version in ThisBuild := ("git describe --always --dirty=-SNAPSHOT --match v[0-9].*" !!).tail.trim organization in ThisBuild := "ch.jodersky" scalacOptions in ThisBuild ++= Seq( "-deprecation", @@ -28,10 +28,11 @@ lazy val macros = (project in file("macros")) name := "sbt-jni-macros", scalaVersion := scalaVersions.head, crossScalaVersions := scalaVersions, - addCompilerPlugin("org.scalamacros" % "paradise" % macrosParadiseVersion cross CrossVersion.full), - libraryDependencies += "org.typelevel" %% "macro-compat" % "1.1.1", - libraryDependencies += "org.scala-lang" % "scala-compiler" % scalaVersion.value % Provided, - libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value + resolvers += Resolver.url( + "scalameta", + url("http://dl.bintray.com/scalameta/maven"))(Resolver.ivyStylePatterns), + addCompilerPlugin("org.scalameta" % "paradise" % macrosParadiseVersion cross CrossVersion.full), + libraryDependencies += "org.scalameta" %% "scalameta" % "1.3.0" ) lazy val plugin = (project in file("plugin")) diff --git a/macros/src/main/scala/ch/jodersky/jni/annotations.scala b/macros/src/main/scala/ch/jodersky/jni/annotations.scala index f2ace96..6805ebf 100644 --- a/macros/src/main/scala/ch/jodersky/jni/annotations.scala +++ b/macros/src/main/scala/ch/jodersky/jni/annotations.scala @@ -1,12 +1,14 @@ package ch.jodersky.jni -import macrocompat.bundle +//import macrocompat.bundle -import scala.language.experimental.macros -import scala.reflect.macros.whitebox.Context +//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) { @@ -106,8 +108,101 @@ class nativeLoaderMacro(val c: Context) { } } + */ + +import scala.meta._ @compileTimeOnly("Macro Paradise must be enabled to apply annotation.") class nativeLoader(nativeLibrary: String) extends StaticAnnotation { - def macroTransform(annottees: Any*): Any = macro nativeLoaderMacro.impl + + 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() + }""" + } diff --git a/plugin/src/main/scala/ch/jodersky/sbt/jni/plugins/JniLoad.scala b/plugin/src/main/scala/ch/jodersky/sbt/jni/plugins/JniLoad.scala index 9f88dbb..47bf9be 100644 --- a/plugin/src/main/scala/ch/jodersky/sbt/jni/plugins/JniLoad.scala +++ b/plugin/src/main/scala/ch/jodersky/sbt/jni/plugins/JniLoad.scala @@ -12,12 +12,14 @@ object JniLoad extends AutoPlugin { lazy val settings: Seq[Setting[_]] = Seq( // Macro Paradise plugin and dependencies are needed to expand annotation macros. // Once expanded however, downstream projects don't need these dependencies anymore - // (hence the "Provided" configuration). + // (hence the "Provided" configurations). + resolvers += Resolver.url("scalameta", + url("http://dl.bintray.com/scalameta/maven"))(Resolver.ivyStylePatterns), addCompilerPlugin( - "org.scalamacros" % "paradise" % ProjectVersion.MacrosParadise cross CrossVersion.full + "org.scalameta" % "paradise" % ProjectVersion.MacrosParadise cross CrossVersion.full ), resolvers += Resolver.jcenterRepo, - libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value % Provided, + //libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value % Provided, libraryDependencies += "ch.jodersky" %% "sbt-jni-macros" % ProjectVersion.Macros % Provided ) -- cgit v1.2.3