aboutsummaryrefslogtreecommitdiff
path: root/plugin/src/main/scala/ch/jodersky/sbt/jni/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'plugin/src/main/scala/ch/jodersky/sbt/jni/plugins')
-rw-r--r--plugin/src/main/scala/ch/jodersky/sbt/jni/plugins/JniJavah.scala68
-rw-r--r--plugin/src/main/scala/ch/jodersky/sbt/jni/plugins/JniLoad.scala25
-rw-r--r--plugin/src/main/scala/ch/jodersky/sbt/jni/plugins/JniNative.scala159
-rw-r--r--plugin/src/main/scala/ch/jodersky/sbt/jni/plugins/JniPackage.scala95
4 files changed, 347 insertions, 0 deletions
diff --git a/plugin/src/main/scala/ch/jodersky/sbt/jni/plugins/JniJavah.scala b/plugin/src/main/scala/ch/jodersky/sbt/jni/plugins/JniJavah.scala
new file mode 100644
index 0000000..edda1fc
--- /dev/null
+++ b/plugin/src/main/scala/ch/jodersky/sbt/jni/plugins/JniJavah.scala
@@ -0,0 +1,68 @@
+package ch.jodersky.sbt.jni
+package plugins
+
+import sbt._
+import sbt.Keys._
+import util.BytecodeUtil
+
+/** Adds `javah` header-generation functionality to projects. */
+object JniJavah extends AutoPlugin {
+
+ override def requires = plugins.JvmPlugin
+ override def trigger = allRequirements
+
+ object autoImport {
+
+ val javahClasses = taskKey[Set[String]](
+ "Finds fully qualified names of classes containing native declarations."
+ )
+
+ val javah = taskKey[File](
+ "Generate JNI headers. Returns the directory containing generated headers."
+ )
+
+ }
+ import autoImport._
+
+ lazy val mainSettings: Seq[Setting[_]] = Seq(
+
+ javahClasses in javah := {
+ val compiled: inc.Analysis = (compile in Compile).value
+ val classFiles: Set[File] = compiled.relations.allProducts.toSet
+ val nativeClasses = classFiles flatMap { file =>
+ BytecodeUtil.nativeClasses(file)
+ }
+ nativeClasses
+ },
+
+ target in javah := target.value / "native" / "include",
+
+ javah := {
+ val out = (target in javah).value
+ val jcp: Seq[File] = { (compile in Compile).value; Seq((classDirectory in Compile).value) }
+ val cp = jcp.mkString(sys.props("path.separator"))
+ val log = streams.value.log
+
+ val classes = (javahClasses in javah).value
+ if (!classes.isEmpty) {
+ log.info("Headers will be generated to " + out.getAbsolutePath)
+ }
+ for (clazz <- classes) {
+ log.info("Generating header for " + clazz)
+ val parts = Seq(
+ "javah",
+ "-d", out.getAbsolutePath,
+ "-classpath", cp,
+ clazz
+ )
+ val cmd = parts.mkString(" ")
+ val ev = Process(cmd) ! streams.value.log
+ if (ev != 0) sys.error(s"Error occured running javah. Exit code: ${ev}")
+ }
+ out
+ }
+ )
+
+ override lazy val projectSettings = mainSettings
+
+}
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
new file mode 100644
index 0000000..51d9c7b
--- /dev/null
+++ b/plugin/src/main/scala/ch/jodersky/sbt/jni/plugins/JniLoad.scala
@@ -0,0 +1,25 @@
+package ch.jodersky.sbt.jni
+package plugins
+
+import sbt._
+import sbt.Keys._
+
+object JniLoad extends AutoPlugin {
+
+ override def requires = empty
+ override def trigger = allRequirements
+
+ 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).
+ addCompilerPlugin(
+ "org.scalamacros" % "paradise" % ProjectVersion.MacrosParadise cross CrossVersion.full
+ ),
+ libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value % Provided,
+ libraryDependencies += "ch.jodersky" %% "sbt-jni-macros" % ProjectVersion.Macros % Provided
+ )
+
+ override def projectSettings = settings
+
+}
diff --git a/plugin/src/main/scala/ch/jodersky/sbt/jni/plugins/JniNative.scala b/plugin/src/main/scala/ch/jodersky/sbt/jni/plugins/JniNative.scala
new file mode 100644
index 0000000..6192a08
--- /dev/null
+++ b/plugin/src/main/scala/ch/jodersky/sbt/jni/plugins/JniNative.scala
@@ -0,0 +1,159 @@
+package ch.jodersky.sbt.jni
+package plugins
+
+import build._
+import sbt._
+import sbt.Keys._
+
+/** Wraps a native build system in sbt tasks. */
+object JniNative extends AutoPlugin {
+
+ object autoImport {
+
+ //Main task, inspect this first
+ val nativeCompile = taskKey[File](
+ "Builds a native library by calling the native build tool."
+ )
+
+ val nativePlatform = settingKey[String](
+ "Platform (architecture-kernel) of the system this build is running on."
+ )
+
+ val nativeBuildTool = taskKey[BuildTool](
+ "The build tool to be used when building a native library."
+ )
+
+ val nativeInit = inputKey[Seq[File]](
+ "Initialize a native build script from a template."
+ )
+
+ }
+ import autoImport._
+
+ val nativeBuildToolInstance = taskKey[BuildTool#Instance]("Get an instance of the current native build tool.")
+
+ lazy val settings: Seq[Setting[_]] = Seq(
+
+ // the value retruned must match that of `ch.jodersky.jni.PlatformMacros#current()` of project `macros`
+ nativePlatform := {
+ try {
+ val lines = Process("uname -sm").lines
+ if (lines.length == 0) {
+ sys.error("Error occured trying to run `uname`")
+ }
+ // uname -sm returns "<kernel> <hardware name>"
+ val parts = lines.head.split(" ")
+ if (parts.length != 2) {
+ sys.error("'uname -sm' returned unexpected string: " + lines.head)
+ } else {
+ val arch = parts(1).toLowerCase.replaceAll("\\s", "")
+ val kernel = parts(0).toLowerCase.replaceAll("\\s", "")
+ arch + "-" + kernel
+ }
+ } catch {
+ case ex: Exception =>
+ sLog.value.error("Error trying to determine platform.")
+ sLog.value.warn("Cannot determine platform! It will be set to 'unknown'.")
+ "unknown-unknown"
+ }
+ },
+
+ sourceDirectory in nativeCompile := sourceDirectory.value / "native",
+
+ target in nativeCompile := target.value / "native" / (nativePlatform).value,
+
+ nativeBuildTool := {
+ val tools = Seq(CMake)
+
+ val src = (sourceDirectory in nativeCompile).value
+
+ val tool = if (src.exists && src.isDirectory) {
+ tools.find(t => t detect src)
+ } else {
+ None
+ }
+ tool getOrElse sys.error("No supported native build tool detected. " +
+ s"Check that the setting 'sourceDirectory in nativeCompile' (currently set to$src) " +
+ "points to a directory containing a supported build script. Supported build tools are: " +
+ tools.map(_.name).mkString(",")
+ )
+
+ },
+
+ nativeBuildToolInstance := {
+ val tool = nativeBuildTool.value
+ val srcDir = (sourceDirectory in nativeCompile).value
+ val buildDir = (target in nativeCompile).value / "build"
+ IO.createDirectory(buildDir)
+ tool.getInstance(
+ baseDirectory = srcDir,
+ buildDirectory = buildDir,
+ logger = streams.value.log
+ )
+ },
+
+ clean in nativeCompile := {
+ val log = streams.value.log
+
+ log.debug("Cleaning native build")
+ try {
+ val toolInstance = nativeBuildToolInstance.value
+ toolInstance.clean()
+ } catch {
+ case ex: Exception =>
+ log.debug(s"Native cleaning failed: $ex")
+ }
+
+ },
+
+ nativeCompile := {
+ val tool = nativeBuildTool.value
+ val toolInstance = nativeBuildToolInstance.value
+ val targetDir = (target in nativeCompile).value / "bin"
+ val log = streams.value.log
+
+ IO.createDirectory(targetDir)
+
+ log.info(s"Building library with native build tool ${tool.name}")
+ val lib = toolInstance.library(targetDir)
+ log.success(s"Library built in ${lib.getAbsolutePath}")
+ lib
+ },
+
+ // also clean native sources
+ clean := {
+ (clean in nativeCompile).value
+ clean.value
+ },
+
+ nativeInit := {
+ import complete.DefaultParsers._
+
+ val log = streams.value.log
+
+ def getTool(toolName: String): BuildTool = toolName.toLowerCase match {
+ case "cmake" => CMake
+ case _ => sys.error("Unsupported build tool: " + toolName)
+ }
+
+ val args = spaceDelimited("<tool> [<libname>]").parsed.toList
+
+ val (tool: BuildTool, lib: String) = args match {
+ case Nil => sys.error("Invalid arguments.")
+ case tool :: Nil => (getTool(tool), name.value)
+ case tool :: lib :: other => (getTool(tool), lib)
+ }
+
+ log.info(s"Initializing native build with ${tool.name} configuration")
+ val files = tool.initTemplate((sourceDirectory in nativeCompile).value, lib)
+ files foreach { file =>
+ log.info("Wrote to " + file.getAbsolutePath)
+ }
+ files
+ }
+
+ )
+
+ override lazy val projectSettings = settings
+
+}
diff --git a/plugin/src/main/scala/ch/jodersky/sbt/jni/plugins/JniPackage.scala b/plugin/src/main/scala/ch/jodersky/sbt/jni/plugins/JniPackage.scala
new file mode 100644
index 0000000..b6b5100
--- /dev/null
+++ b/plugin/src/main/scala/ch/jodersky/sbt/jni/plugins/JniPackage.scala
@@ -0,0 +1,95 @@
+package ch.jodersky.sbt.jni
+package plugins
+
+import sbt._
+import sbt.Keys._
+import java.io.File
+
+/** Packages libraries built with JniNative. */
+object JniPackage extends AutoPlugin {
+
+ // JvmPlugin is required or else it will override resource generators when first included
+ override def requires = JniNative && plugins.JvmPlugin
+ override def trigger = allRequirements
+
+ object autoImport {
+
+ val enableNativeCompilation = settingKey[Boolean](
+ "Determines if native compilation is enabled. If not enabled, only pre-compiled libraries in " +
+ "`unmanagedNativeDirectories` will be packaged."
+ )
+
+ val unmanagedNativeDirectories = settingKey[Seq[File]](
+ "Unmanaged directories containing native libraries. The libraries must be regular files " +
+ "contained in a subdirectory corresponding to a platform. For example " +
+ "`<unamagedNativeDirectory>/x86_64-linux/libfoo.so` is an unmanaged library for machines having " +
+ "the x86_64 architecture and running the Linux kernel."
+ )
+
+ val unmanagedNativeLibraries = taskKey[Seq[(File, String)]](
+ "Reads `unmanagedNativeDirectories` and maps platforms to library files specified theirin."
+ )
+
+ val managedNativeLibraries = taskKey[Seq[(File, String)]](
+ "Maps locally built, platform-dependant libraries."
+ )
+
+ val nativeLibraries = taskKey[Seq[(File, String)]](
+ "All native libraries, managed and unmanaged."
+ )
+
+ }
+ import autoImport._
+ import JniNative.autoImport._
+
+ lazy val settings: Seq[Setting[_]] = Seq(
+
+ enableNativeCompilation := true,
+
+ unmanagedNativeDirectories := Seq(baseDirectory.value / "lib_native"),
+
+ unmanagedNativeLibraries := {
+ val baseDirs: Seq[File] = unmanagedNativeDirectories.value
+ val mappings: Seq[(File, String)] = unmanagedNativeDirectories.value.flatMap { dir =>
+ val files: Seq[File] = (dir ** "*").get
+ files pair rebase(dir, "/native")
+ }
+ mappings
+ },
+
+ managedNativeLibraries := Def.taskDyn[Seq[(File, String)]] {
+ val enableManaged = (enableNativeCompilation).value
+ if (enableManaged) Def.task {
+ val library: File = nativeCompile.value
+ val platform = nativePlatform.value
+
+ Seq(library -> s"/native/$platform/${library.name}")
+ }
+ else Def.task {
+ Seq.empty
+ }
+ }.value,
+
+ nativeLibraries := unmanagedNativeLibraries.value ++ managedNativeLibraries.value,
+
+ resourceGenerators += Def.task {
+ val libraries: Seq[(File, String)] = nativeLibraries.value
+ val resources: Seq[File] = for ((file, path) <- libraries) yield {
+
+ // native library as a managed resource file
+ val resource = resourceManaged.value / path
+
+ // copy native library to a managed resource, so that it is always available
+ // on the classpath, even when not packaged as a jar
+ IO.copyFile(file, resource)
+ resource
+ }
+ resources
+ }.taskValue
+
+ )
+
+ override lazy val projectSettings = inConfig(Compile)(settings) ++
+ Seq(crossPaths := false) // don't add scala version to native jars
+
+}