aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob Odersky <jodersky@gmail.com>2015-12-06 17:03:08 -0800
committerJakob Odersky <jodersky@gmail.com>2015-12-06 17:09:40 -0800
commite2505d1d9e2e49554057a8cd5fb71b0ac0e3ba63 (patch)
treef4fec79ba707aa9b2dec903d54a164f357961f19
parent3e90312b3f2d912bf27e91b454a6ef21a81a2fc5 (diff)
downloadsbt-jni-e2505d1d9e2e49554057a8cd5fb71b0ac0e3ba63.tar.gz
sbt-jni-e2505d1d9e2e49554057a8cd5fb71b0ac0e3ba63.tar.bz2
sbt-jni-e2505d1d9e2e49554057a8cd5fb71b0ac0e3ba63.zip
Use separate project for native libraries
-rw-r--r--README.md16
-rw-r--r--examples/demo/build.sbt16
-rw-r--r--examples/demo/native/org_example_jni_demo_Library__.h21
-rw-r--r--examples/demo/project/plugins.sbt1
-rw-r--r--examples/demo/src/main/scala/org/example/jni/demo/Main.scala12
-rw-r--r--jni-library/src/main/scala/ch/jodersky/jni/NativeLoader.scala19
-rw-r--r--jni-library/src/main/scala/ch/jodersky/jni/Platform.scala6
-rw-r--r--jni-plugin/src/main/scala/ch/jodersky/sbt/jni/Defaults.scala192
-rw-r--r--jni-plugin/src/main/scala/ch/jodersky/sbt/jni/JniJvm.scala71
-rw-r--r--jni-plugin/src/main/scala/ch/jodersky/sbt/jni/JniNative.scala114
-rw-r--r--jni-plugin/src/main/scala/ch/jodersky/sbt/jni/JniPlugin.scala19
-rw-r--r--jni-plugin/src/main/scala/ch/jodersky/sbt/jni/Keys.scala53
-rw-r--r--jni-plugin/src/main/scala/ch/jodersky/sbt/jni/build/BuildToolApi.scala14
-rw-r--r--jni-plugin/src/main/scala/ch/jodersky/sbt/jni/build/CMake.scala53
-rw-r--r--jni-plugin/src/main/scala/ch/jodersky/sbt/jni/build/ConfigureMakeInstall.scala49
-rw-r--r--jni-plugin/src/main/scala/ch/jodersky/sbt/jni/build/NoTool.scala9
-rw-r--r--jni-plugin/src/main/scala/ch/jodersky/sbt/jni/util/ByteCode.scala57
-rw-r--r--project/Build.scala32
-rw-r--r--samples/basic/basic-main/src/main/scala/ch/jodersky/jni/basic/Library.scala (renamed from examples/demo/src/main/scala/org/example/jni/demo/Library.scala)2
-rw-r--r--samples/basic/basic-main/src/main/scala/ch/jodersky/jni/basic/Main.scala13
-rw-r--r--samples/basic/basic-native/src/CMakeLists.txt (renamed from examples/demo/native/CMakeLists.txt)3
-rw-r--r--samples/basic/basic-native/src/include/ch_jodersky_jni_basic_Library__.h21
-rw-r--r--samples/basic/basic-native/src/library.c (renamed from examples/demo/native/main.c)6
-rw-r--r--samples/basic/build.sbt24
-rw-r--r--samples/basic/project/build.properties1
-rw-r--r--samples/basic/project/plugins.sbt1
26 files changed, 421 insertions, 404 deletions
diff --git a/README.md b/README.md
index d861fec..6fd8d55 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,2 @@
-# Definitions
-- Native binaries: artifacts such as libraries and executables that are platform dependent, typically C/C++ projects
-
-# Goals:
-
-## Wrapper
-- Native binaries are compiled with another build tool, such as Make, Autotools or CMake
-- Provides a "Native" configuration with most standard sbt tasks.
-Why not provide separate tasks, like nativeCompile and nativeTest? individual tasks don't map, that's the whole idea to use a wrapper
-
-## JNI
-- Generate JNI headers
-
-
+# SBT-JNI
+Making JNI tolerable.
diff --git a/examples/demo/build.sbt b/examples/demo/build.sbt
deleted file mode 100644
index 8cd1419..0000000
--- a/examples/demo/build.sbt
+++ /dev/null
@@ -1,16 +0,0 @@
-enablePlugins(JniPlugin)
-
-name := "jni-demo"
-
-organization := "org.example"
-
-baseDirectory in jni := (baseDirectory in ThisBuild).value / "native"
-
-javahObjects in javah += "org.example.jni.demo.Library"
-
-libraryDependencies += "ch.jodersky" %% "jni-library" % "0.1-SNAPSHOT"
-
-//exportJars in run := true
-
-//librearDependencies += "org.example" %% "demo" % "0.1"
-//librearDependencies += "org.example" % "demo" % "0.1" % "runtime" classifier "native"
diff --git a/examples/demo/native/org_example_jni_demo_Library__.h b/examples/demo/native/org_example_jni_demo_Library__.h
deleted file mode 100644
index 06a7fa4..0000000
--- a/examples/demo/native/org_example_jni_demo_Library__.h
+++ /dev/null
@@ -1,21 +0,0 @@
-/* DO NOT EDIT THIS FILE - it is machine generated */
-#include <jni.h>
-/* Header for class org_example_jni_demo_Library__ */
-
-#ifndef _Included_org_example_jni_demo_Library__
-#define _Included_org_example_jni_demo_Library__
-#ifdef __cplusplus
-extern "C" {
-#endif
-/*
- * Class: org_example_jni_demo_Library__
- * Method: print
- * Signature: (Ljava/lang/String;)I
- */
-JNIEXPORT jint JNICALL Java_org_example_jni_demo_Library_00024_print
- (JNIEnv *, jobject, jstring);
-
-#ifdef __cplusplus
-}
-#endif
-#endif
diff --git a/examples/demo/project/plugins.sbt b/examples/demo/project/plugins.sbt
deleted file mode 100644
index ab5617e..0000000
--- a/examples/demo/project/plugins.sbt
+++ /dev/null
@@ -1 +0,0 @@
-addSbtPlugin("ch.jodersky" % "sbt-jni" % "0.1-SNAPSHOT")
diff --git a/examples/demo/src/main/scala/org/example/jni/demo/Main.scala b/examples/demo/src/main/scala/org/example/jni/demo/Main.scala
deleted file mode 100644
index 7306104..0000000
--- a/examples/demo/src/main/scala/org/example/jni/demo/Main.scala
+++ /dev/null
@@ -1,12 +0,0 @@
-package org.example.jni.demo
-
-import ch.jodersky.jni.NativeLoader
-
-object Main {
-
- def main(args: Array[String]): Unit = {
- NativeLoader.load("demo1", "/org/example/jni/demo")
- Library.print("Hello world!")
- }
-
-}
diff --git a/jni-library/src/main/scala/ch/jodersky/jni/NativeLoader.scala b/jni-library/src/main/scala/ch/jodersky/jni/NativeLoader.scala
index 46d4b2d..261e52b 100644
--- a/jni-library/src/main/scala/ch/jodersky/jni/NativeLoader.scala
+++ b/jni-library/src/main/scala/ch/jodersky/jni/NativeLoader.scala
@@ -1,11 +1,10 @@
package ch.jodersky.jni
import java.io.{File, FileOutputStream, InputStream, OutputStream}
-import scala.io.Source
/**
- * Provides enhanced native library loading functionality.
- */
+ * Provides enhanced native library loading functionality.
+ */
object NativeLoader {
/** Name of the shared library file that is contained in a jar. */
@@ -13,7 +12,7 @@ object NativeLoader {
final val BufferSize = 4096
- /** Extract a resource from this class loader to a temporary file. */
+ /** Extracts 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
@@ -45,12 +44,16 @@ object NativeLoader {
msg
)
+ /**
+ * Gets the absolute, full path of a resource on the classpath, given a libraryPath
+ * and platform.
+ */
def fullLibraryPath(libraryPath: String, platform: Platform) = {
- libraryPath + "/native/" + platform.id + "/" + LibraryName
+ libraryPath + "/native/" + platform.id + "/" + LibraryName
}
private def loadFromJar(libraryPath: String): Unit = {
- val platform = Platform.current.getOrElse{
+ val platform = Platform.current.getOrElse {
loadError("Cannot determine current platform.")
}
@@ -63,10 +66,10 @@ object NativeLoader {
}
/**
- * Load a native library from the available library path or fall back
+ * Loads 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 {
+ def load(libraryPath: String, library: 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
index db1662d..cff9e95 100644
--- a/jni-library/src/main/scala/ch/jodersky/jni/Platform.scala
+++ b/jni-library/src/main/scala/ch/jodersky/jni/Platform.scala
@@ -20,15 +20,16 @@ case class Platform private (arch: String, kernel: String) {
object Platform {
+ /** The unknown platform. */
final val Unknown = Platform("unknown", "unknown")
- /** Create a platform with spaces stripped and case normalized. */
+ /** Creates 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. */
+ /** Runs '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)
@@ -45,6 +46,7 @@ object Platform {
}
}
+ /** Determines platform the current JVM is running on. */
def current = uname
}
diff --git a/jni-plugin/src/main/scala/ch/jodersky/sbt/jni/Defaults.scala b/jni-plugin/src/main/scala/ch/jodersky/sbt/jni/Defaults.scala
deleted file mode 100644
index 49f4a49..0000000
--- a/jni-plugin/src/main/scala/ch/jodersky/sbt/jni/Defaults.scala
+++ /dev/null
@@ -1,192 +0,0 @@
-package ch.jodersky.sbt.jni
-
-import sbt._
-import sbt.Keys._
-import Keys._
-
-import build.CMake
-import build.BuildTool
-
-import ch.jodersky.jni.Platform
-import ch.jodersky.jni.NativeLoader
-
-object Defaults {
-
- lazy val buildSettings: Seq[Setting[_]] = Seq(
-
- baseDirectory in jni := (baseDirectory in ThisBuild).value / "native",
-
- target in jni := target.value / "native" / (jniPlatform in jni).value.id,
-
- jniPlatform in jni := Platform.current.getOrElse{
- sLog.value.warn("Warning: cannot determine platform! It will be set to 'unknown'.")
- Platform.Unknown
- },
-
- jniBuildTool in jni := {
- val tools = Seq(CMake)
-
- val base = (baseDirectory in jni).value
-
- val tool = if (base.exists && base.isDirectory) {
- tools.find(t => t detect base)
- } else {
- None
- }
- tool.getOrElse(
- sys.error("No supported native build tool detected. " +
- s"Check that the setting 'baseDirectory in jni' (currently $base) " +
- "points to a valid directory. Supported build tools are: " +
- tools.map(_.name).mkString(",")
- )
- )
- },
-
- clean := {
- val log = streams.value.log
- val tool = try {
- Some((jniBuildTool in jni).value)
- } catch {
- case _: Exception => None
- }
- tool foreach { t =>
- log.debug("Cleaning native build")
- t.api.clean(
- (baseDirectory in jni).value,
- log
- )
- }
- clean.value
- },
-
- jni := {
- val tool = (jniBuildTool in jni).value
- val dir = (baseDirectory in jni).value
- val targetDir = (target in jni).value / "lib"
- val log = streams.value.log
-
- tool.api.library(dir, targetDir, log)
- },
-
- javahClasses in javah := Seq(),
-
- javahObjects in javah := Seq(),
-
- target in javah := (baseDirectory in jni).value,
-
- fullClasspath in javah := (fullClasspath in Compile).value,
-
- javah := {
- val out = (target in javah).value
- val jcp: Seq[File] = (fullClasspath in javah).value.map(_.data)
- val cp = jcp.mkString(sys.props("path.separator"))
-
- val classes = (javahClasses in javah).value ++
- (javahObjects in javah).value.map(_ + "$")
-
- for (clazz <- classes) {
- 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
- }
-
- )
-
- lazy val bundleSettings: Seq[Setting[_]] = Seq(
-
- jniLibraryPath in jni := {
- "/" + (organization.value + "." + name.value).replaceAll("\\.|-", "/")
- },
-
- unmanagedResourceDirectories in jni := Seq(
- (baseDirectory).value / "lib_native"
- ),
-
- unmanagedClasspath ++= (unmanagedResourceDirectories in jni).value.map{ file =>
- Attributed.blank(file)
- },
-
- resourceManaged in jni := (target in jni).value / "lib_managed",
-
- managedResourceDirectories in jni := Seq(
- (resourceManaged in jni).value
- ),
-
- managedClasspath ++= {
-
- //build native library
- val library = jni.value
-
- //native library as a managed resource file
- val libraryResource = (resourceManaged in jni).value /
- NativeLoader.fullLibraryPath(
- (jniLibraryPath in jni).value,
- (jniPlatform in jni).value
- )
-
- //copy native library to a managed resource (so it can also be loaded when not packages as a jar)
- IO.copyFile(library, libraryResource)
-
- Seq(Attributed.blank((resourceManaged in jni).value))
- },
-
- packageJni in Global := {
-
- val unmanagedMappings: Seq[(File, String)] = (unmanagedResourceDirectories in jni).value flatMap { dir =>
- val files = (dir ** "*").filter(_.isFile).get
- files map { file =>
- println(file.getAbsolutePath)
- file -> (file relativeTo dir).get.getPath
- }
- }
-
- managedClasspath.value //call this to generate files in resourceManaged
-
- val managedMappings: Seq[(File, String)] = (managedResourceDirectories in jni).value flatMap { dir =>
- val files = (dir ** "*").filter(_.isFile).get
- files map { file =>
- println(file.getAbsolutePath)
- file -> (file relativeTo dir).get.getPath
- }
- }
-
- val out = target.value / (name.value + "-native.jar")
-
- val manifest = new java.util.jar.Manifest
- Package.makeJar(
- unmanagedMappings ++ managedMappings,
- out,
- manifest,
- streams.value.log
- )
- out
- }
-
- )
-
- lazy val clientSettings: Seq[Setting[_]] = Seq(
- libraryDependencies += "ch.jodersky" %% "jni-library" % "0.1-SNAPSHOT",
- fork in run := true,
- artifact in jni := {
- Artifact(
- name = name.value,
- `type` = "jar",
- extension = "jar",
- classifier = Some("native"),
- configurations = Seq(Runtime),
- url = None
- ) extra (
- "platform" -> "all"
- )
- }
- ) ++ addArtifact(artifact in jni, packageJni)
-
-}
diff --git a/jni-plugin/src/main/scala/ch/jodersky/sbt/jni/JniJvm.scala b/jni-plugin/src/main/scala/ch/jodersky/sbt/jni/JniJvm.scala
new file mode 100644
index 0000000..70c5787
--- /dev/null
+++ b/jni-plugin/src/main/scala/ch/jodersky/sbt/jni/JniJvm.scala
@@ -0,0 +1,71 @@
+package ch.jodersky.sbt.jni
+
+import sbt._
+import sbt.Keys._
+import util.ByteCode
+
+object JniJvm extends AutoPlugin {
+
+ override def requires = plugins.JvmPlugin
+
+ object autoImport {
+
+ val javahClasses = taskKey[Set[String]](
+ "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 classFiles: Set[File] = compile.value.relations.allProducts.toSet
+ val nativeClasses = classFiles.flatMap { file =>
+ ByteCode.natives(file)
+ }
+ nativeClasses
+ },
+
+ target in javah := target.value / "include",
+
+ fullClasspath in javah := (fullClasspath in Compile).value,
+
+ javah := {
+ val out = (target in javah).value
+ val jcp: Seq[File] = (fullClasspath in javah).value.map(_.data)
+ val cp = jcp.mkString(sys.props("path.separator"))
+ val log = streams.value.log
+
+ val classes = (javahClasses in javah).value
+
+ 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
+ }
+ )
+
+ lazy val clientSettings = Seq(
+ //enable enhanced native library extraction
+ libraryDependencies += "ch.jodersky" %% "jni-library" % Version.PluginVersion,
+ crossPaths := false, //don't need to appends scala version to native jars
+ fork in run := true //fork new JVM as native libraries can only be loaded once
+ )
+
+ override lazy val projectSettings = inConfig(Compile)(mainSettings) ++ clientSettings
+
+}
diff --git a/jni-plugin/src/main/scala/ch/jodersky/sbt/jni/JniNative.scala b/jni-plugin/src/main/scala/ch/jodersky/sbt/jni/JniNative.scala
new file mode 100644
index 0000000..7e36f50
--- /dev/null
+++ b/jni-plugin/src/main/scala/ch/jodersky/sbt/jni/JniNative.scala
@@ -0,0 +1,114 @@
+package ch.jodersky.sbt.jni
+
+import build._
+import ch.jodersky.jni.{NativeLoader, Platform}
+import sbt._
+import sbt.Keys._
+
+object JniNative extends AutoPlugin {
+
+ override def requires = plugins.JvmPlugin
+
+ object autoImport {
+
+ val jni = taskKey[File]("Builds a native library (by calling the native build tool).")
+
+ val jniPlatform = settingKey[Platform]("Platform of the system this build is running on.")
+ val jniBuildTool = taskKey[BuildTool]("The build tool to be used when building a native library.")
+
+ val jniLibraryPath = settingKey[String]("String that is prepended to the path of a native library when packaged.")
+
+ }
+ import autoImport._
+
+ lazy val rawSettings: Seq[Setting[_]] = Seq(
+
+ sourceDirectory in jni := baseDirectory.value / "src",
+
+ target in Global in jni := target.value / "native" / (jniPlatform in jni).value.id,
+
+ jniPlatform in Global in jni := Platform.current.getOrElse {
+ sLog.value.warn("Warning: cannot determine platform! It will be set to 'unknown'.")
+ Platform.Unknown
+ },
+
+ jniBuildTool in jni := {
+ val tools = Seq(CMake)
+
+ val base = (sourceDirectory in jni).value
+
+ val tool = if (base.exists && base.isDirectory) {
+ tools.find(t => t detect base)
+ } else {
+ None
+ }
+ tool.getOrElse(
+ sys.error("No supported native build tool detected. " +
+ s"Check that the setting 'sourceDirectory in jni' (currently $base) " +
+ "points to a valid directory. Supported build tools are: " +
+ tools.map(_.name).mkString(","))
+ )
+ },
+
+ clean in jni := {
+ val log = streams.value.log
+ val tool = try {
+ Some((jniBuildTool in jni).value)
+ } catch {
+ case _: Exception => None
+ }
+ tool foreach { t =>
+ log.debug("Cleaning native build")
+ t.api.clean(
+ (sourceDirectory in jni).value,
+ log
+ )
+ }
+ },
+
+ jni := {
+ val tool = (jniBuildTool in jni).value
+ val dir = (sourceDirectory in jni).value
+ val buildDir = (target in jni).value / "build"
+ val targetDir = (target in jni).value / "lib"
+ val log = streams.value.log
+
+ IO.createDirectory(buildDir)
+ IO.createDirectory(targetDir)
+
+ tool.api.library(dir, buildDir, targetDir, log)
+ },
+
+ clean := {
+ (clean in jni).value
+ clean.value
+ },
+
+ jniLibraryPath in jni := {
+ "/" + organization.value.replaceAll("\\.", "/") + "/" + name.value
+ },
+
+ unmanagedResourceDirectories += baseDirectory.value / "lib_native",
+
+ resourceGenerators += Def.task {
+ //build native library
+ val library = jni.value
+
+ //native library as a managed resource file
+ val libraryResource = (resourceManaged in Compile).value /
+ NativeLoader.fullLibraryPath(
+ (jniLibraryPath in jni).value,
+ (jniPlatform in jni).value
+ )
+
+ //copy native library to a managed resource (so it can also be loaded when not packaged as a jar)
+ IO.copyFile(library, libraryResource)
+
+ Seq(libraryResource)
+ }.taskValue
+
+ )
+
+ override lazy val projectSettings = inConfig(Compile)(rawSettings)
+
+}
diff --git a/jni-plugin/src/main/scala/ch/jodersky/sbt/jni/JniPlugin.scala b/jni-plugin/src/main/scala/ch/jodersky/sbt/jni/JniPlugin.scala
deleted file mode 100644
index 98f8214..0000000
--- a/jni-plugin/src/main/scala/ch/jodersky/sbt/jni/JniPlugin.scala
+++ /dev/null
@@ -1,19 +0,0 @@
-package ch.jodersky.sbt.jni
-
-import sbt._
-
-object JniPlugin extends AutoPlugin {
-
- override def requires = plugins.JvmPlugin
-
- lazy val autoImport = Keys
- import autoImport._
-
- override lazy val projectSettings =
- Defaults.buildSettings ++
- inConfig(Runtime)(Defaults.bundleSettings) ++
- Defaults.clientSettings
- //++inConfig(Runtime)(Defaults.bundleSettings)
-
-
-}
diff --git a/jni-plugin/src/main/scala/ch/jodersky/sbt/jni/Keys.scala b/jni-plugin/src/main/scala/ch/jodersky/sbt/jni/Keys.scala
deleted file mode 100644
index 4286ff6..0000000
--- a/jni-plugin/src/main/scala/ch/jodersky/sbt/jni/Keys.scala
+++ /dev/null
@@ -1,53 +0,0 @@
-package ch.jodersky.sbt.jni
-
-import build.BuildTool
-
-import ch.jodersky.jni.Platform
-import sbt._
-
-object Keys {
-
- val jni = taskKey[File]("Builds a native library (by calling the native build tool).")
- val jniClean = taskKey[Unit]("Cleans the native build directory.")
-
- val jniPlatform = settingKey[Platform]("Platform of the system this build is running on.")
- val jniBuildTool = taskKey[BuildTool]("The build tool to be used when building a native library.")
-
- //bundle
- val jniLibraryPath = settingKey[String]("Foo")
- val packageJni = taskKey[File]("Package native librraies into a fat jar.")
-
- val javahClasses = settingKey[Seq[String]]("Fully qualified names of classes containing native declarations.")
- val javahObjects = settingKey[Seq[String]]("Fully qualified names of singleton objects containing native declarations.")
- val javah = taskKey[File]("Generate JNI headers. Returns the directory containing generated headers.")
-
-
- //ivy
- //libraryDependencies += "com.github.jodersky" % "flow" % "2.4" extra("platform", "all") artifact("libflow", "so")
-
- //maven
- //libraryDependencies += "com.github.jodersky" % "flow" % "2.4" classifier "native"
-
-
-
- //Wraps tasks associated to an existing native build tool
- //val Native = config("native")
-
- //Extra tasks in native
- // val buildTool = settingKey[BuildTool]("The native build tool used.")
- //val platform = settingKey[Platform]("Platform of the system this build is being run on.")
-
- // organization = org.example
- // name = foo-bar
- // libraryPrefix = organization + "." + normalize(name)
- // libraryPrefix = org/example/foo/bar
- // libraryPrefix = org/example/foo/bar/native/<platform>/libraries
-
-
- //val libraryPath = settingKey[String](
- // "A slash (/) seprated path that specifies where native libraries should be stored (in a jar).")
- //val libraryResourceDirectory = settingKey[File]("")
-
- //Javah
-
-}
diff --git a/jni-plugin/src/main/scala/ch/jodersky/sbt/jni/build/BuildToolApi.scala b/jni-plugin/src/main/scala/ch/jodersky/sbt/jni/build/BuildToolApi.scala
index 47d9a90..43dd17a 100644
--- a/jni-plugin/src/main/scala/ch/jodersky/sbt/jni/build/BuildToolApi.scala
+++ b/jni-plugin/src/main/scala/ch/jodersky/sbt/jni/build/BuildToolApi.scala
@@ -4,19 +4,23 @@ package build
import java.io.File
import sbt.Logger
-
trait BuildToolApi {
/** Invokes the native build tool's clean task */
- def clean(baseDirectory: File, log: Logger): Unit
+ def clean(baseDirectory: File, log: Logger): Unit
/**
- * Invokes the native build tool's main task, resulting in a single shared
- * library file.
- */
+ * Invokes the native build tool's main task, resulting in a single shared
+ * library file.
+ * @param baseDirectory the directory where the native project is located
+ * @param buildDirectory a directory from where the build is called, it may be used to store temporary files
+ * @param targetDirectory the directory into which the native library is copied
+ * @return the native library file
+ */
def library(
baseDirectory: File,
targetDirectory: File,
+ buildDirectory: File,
log: Logger
): File
diff --git a/jni-plugin/src/main/scala/ch/jodersky/sbt/jni/build/CMake.scala b/jni-plugin/src/main/scala/ch/jodersky/sbt/jni/build/CMake.scala
index cd0b363..3f75585 100644
--- a/jni-plugin/src/main/scala/ch/jodersky/sbt/jni/build/CMake.scala
+++ b/jni-plugin/src/main/scala/ch/jodersky/sbt/jni/build/CMake.scala
@@ -1,12 +1,8 @@
package ch.jodersky.sbt.jni
package build
-import Keys._
-import sbt._
-import sbt.Keys._
-import sbt.Logger
import java.io.File
-
+import sbt._
object CMake extends BuildTool {
@@ -14,46 +10,21 @@ object CMake extends BuildTool {
def detect(baseDirectory: File) = baseDirectory.list().contains("CMakeLists.txt")
- object api extends BuildToolApi {
+ object api extends ConfigureMakeInstall {
- def clean(baseDirectory: File, log: Logger) = Process("make clean", baseDirectory) ! log
-
- def library(
- baseDirectory: File,
- targetDirectory: File,
- log: Logger
- ): File = {
- val out = targetDirectory
- val outPath = out.getAbsolutePath
-
- val configure = Process(
+ override def configure(base: File, build: File, target: File) = {
+ val targetPath = target.getAbsolutePath
+ Process(
//Disable producing versioned library files, not needed for fat jars.
- s"cmake -DCMAKE_INSTALL_PREFIX:PATH=$outPath -DLIB_INSTALL_DIR:PATH=$outPath -DENABLE_VERSIONED_LIB:BOOLEAN=OFF",
- baseDirectory
+ "cmake " +
+ s"-DCMAKE_INSTALL_PREFIX:PATH=$targetPath " +
+ s"-DLIB_INSTALL_DIR:PATH=$targetPath " +
+ "-DENABLE_VERSIONED_LIB:BOOLEAN=OFF " +
+ base.getAbsolutePath,
+ build
)
-
- val make = Process("make", baseDirectory)
-
- val makeInstall = Process("make install", baseDirectory)
-
- val ev = configure #&& make #&& makeInstall ! log
- if (ev != 0) sys.error(s"Building native library failed. Exit code: ${ev}")
-
- val products: List[File] = (out ** ("*" -- "*.la")).get.filter(_.isFile).toList
-
- //only one produced library is expected
- products match {
- case Nil =>
- sys.error("No files were created during compilation, " +
- "something went wrong with the autotools configuration.")
- case head :: Nil =>
- head
- case head :: tail =>
- log.warn("More than one file was created during compilation, " +
- s"only the first one (${head.getAbsolutePath}) will be used.")
- head
- }
}
+
}
}
diff --git a/jni-plugin/src/main/scala/ch/jodersky/sbt/jni/build/ConfigureMakeInstall.scala b/jni-plugin/src/main/scala/ch/jodersky/sbt/jni/build/ConfigureMakeInstall.scala
new file mode 100644
index 0000000..97011ed
--- /dev/null
+++ b/jni-plugin/src/main/scala/ch/jodersky/sbt/jni/build/ConfigureMakeInstall.scala
@@ -0,0 +1,49 @@
+package ch.jodersky.sbt.jni
+package build
+
+import java.io.File
+import sbt._
+
+/** Native build tools relying on a standard 'configure && make && make install' process */
+trait ConfigureMakeInstall extends BuildToolApi {
+
+ override def clean(baseDirectory: File, log: Logger) = Process("make clean", baseDirectory) ! log
+
+ def configure(baseDirectory: File, buildDirectory: File, targetDirectory: File): ProcessBuilder
+
+ def make(baseDirectory: File, buildDirectory: File, targetDirectory: File): ProcessBuilder = Process("make", buildDirectory)
+
+ def install(baseDirectory: File, buildDirectory: File, targetDirectory: File): ProcessBuilder = Process("make install", buildDirectory)
+
+ override def library(
+ baseDirectory: File,
+ buildDirectory: File,
+ targetDirectory: File,
+ log: Logger
+ ): File = {
+
+ val ev = (
+ configure(baseDirectory, buildDirectory, targetDirectory) #&&
+ make(baseDirectory, buildDirectory, targetDirectory) #&&
+ install(baseDirectory, buildDirectory, targetDirectory)
+ ) ! log
+
+ if (ev != 0) sys.error(s"Building native library failed. Exit code: ${ev}")
+
+ val products: List[File] = (targetDirectory ** ("*" -- "*.la")).get.filter(_.isFile).toList
+
+ //only one produced library is expected
+ products match {
+ case Nil =>
+ sys.error("No files were created during compilation, " +
+ "something went wrong with the autotools configuration.")
+ case head :: Nil =>
+ head
+ case head :: tail =>
+ log.warn("More than one file was created during compilation, " +
+ s"only the first one (${head.getAbsolutePath}) will be used.")
+ head
+ }
+ }
+}
+
diff --git a/jni-plugin/src/main/scala/ch/jodersky/sbt/jni/build/NoTool.scala b/jni-plugin/src/main/scala/ch/jodersky/sbt/jni/build/NoTool.scala
deleted file mode 100644
index 96dd85f..0000000
--- a/jni-plugin/src/main/scala/ch/jodersky/sbt/jni/build/NoTool.scala
+++ /dev/null
@@ -1,9 +0,0 @@
-package ch.jodersky.sbt.jni
-package build
-
-
-object NoTool {
-
-
-
-}
diff --git a/jni-plugin/src/main/scala/ch/jodersky/sbt/jni/util/ByteCode.scala b/jni-plugin/src/main/scala/ch/jodersky/sbt/jni/util/ByteCode.scala
new file mode 100644
index 0000000..308a077
--- /dev/null
+++ b/jni-plugin/src/main/scala/ch/jodersky/sbt/jni/util/ByteCode.scala
@@ -0,0 +1,57 @@
+package ch.jodersky.sbt.jni
+package util
+
+import java.io.{File, FileInputStream, Closeable}
+import scala.collection.mutable.{HashSet}
+
+import org.objectweb.asm.{ClassReader, ClassVisitor, MethodVisitor, Opcodes}
+
+object ByteCode {
+
+ private class NativeFinder extends ClassVisitor(Opcodes.ASM5) {
+
+ //contains any classes found to contain at least one @native def
+ val _nativeClasses = new HashSet[String]
+ def nativeClasses = _nativeClasses.toSet
+
+ private var fullyQualifiedName: String = ""
+
+ override def visit(version: Int, access: Int, name: String, signature: String,
+ superName: String, interfaces: Array[String]): Unit = {
+ fullyQualifiedName = name.replaceAll("/", ".")
+ }
+
+ override def visitMethod(access: Int, name: String, desc: String,
+ signature: String, exceptions: Array[String]): MethodVisitor = {
+
+ val isNative = (access & Opcodes.ACC_NATIVE) != 0
+
+ if (isNative) {
+ _nativeClasses += fullyQualifiedName
+ }
+
+ null //do not visit method further
+ }
+
+ }
+
+ private def using[A >: Null <: Closeable, R](mkStream: => A)(action: A => R): R = {
+ var stream: A = null
+ try {
+ stream = mkStream
+ action(stream)
+ } finally {
+ if (stream != null) {
+ stream.close()
+ }
+ }
+ }
+
+ def natives(classFile: File): Set[String] = using(new FileInputStream(classFile)) { in =>
+ val reader = new ClassReader(in)
+ val finder = new NativeFinder
+ reader.accept(finder, 0)
+ finder.nativeClasses
+ }
+
+}
diff --git a/project/Build.scala b/project/Build.scala
index e1551a4..9a6c0e0 100644
--- a/project/Build.scala
+++ b/project/Build.scala
@@ -1,10 +1,12 @@
import sbt._
import sbt.Keys._
-object NativeUtilsBuild extends Build {
+object JniBuild extends Build {
+
+ val scalaVersions = List("2.11.7", "2.10.5")
val commonSettings = Seq(
- version := "0.1-SNAPSHOT",
+ version := "0.2-SNAPSHOT",
organization := "ch.jodersky",
scalacOptions ++= Seq("-deprecation", "-feature")
)
@@ -16,21 +18,39 @@ object NativeUtilsBuild extends Build {
library, plugin
),
settings = Seq(
- publish := {}
+ publish := {},
+ publishLocal := {}
)
)
lazy val library = Project(
id = "jni-library",
base = file("jni-library"),
- settings = commonSettings
+ settings = commonSettings ++ Seq(
+ scalaVersion := scalaVersions.head,
+ crossScalaVersions := scalaVersions.reverse
+ )
)
lazy val plugin = Project(
id = "sbt-jni",
base = file("jni-plugin"),
- settings = commonSettings ++ Seq(sbtPlugin := true),
- dependencies = Seq(library)
+ dependencies = Seq(library),
+ settings = commonSettings ++ Seq(
+ sbtPlugin := true,
+ sourceGenerators in Compile += Def.task{
+ val src = s"""|package ch.jodersky.sbt.jni
+ |
+ |private object Version {
+ | final val PluginVersion = "${version.value}"
+ |}
+ |""".stripMargin
+ val file = sourceManaged.value / "ch" / "jodersky" / "sbt" / "jni" / "Version.scala"
+ IO.write(file, src)
+ Seq(file)
+ }.taskValue,
+ libraryDependencies += "org.ow2.asm" % "asm" % "5.0.4"
+ )
)
}
diff --git a/examples/demo/src/main/scala/org/example/jni/demo/Library.scala b/samples/basic/basic-main/src/main/scala/ch/jodersky/jni/basic/Library.scala
index c157b5d..efe3018 100644
--- a/examples/demo/src/main/scala/org/example/jni/demo/Library.scala
+++ b/samples/basic/basic-main/src/main/scala/ch/jodersky/jni/basic/Library.scala
@@ -1,4 +1,4 @@
-package org.example.jni.demo
+package ch.jodersky.jni.basic
/** A demo object, mapping to a native library. */
object Library {
diff --git a/samples/basic/basic-main/src/main/scala/ch/jodersky/jni/basic/Main.scala b/samples/basic/basic-main/src/main/scala/ch/jodersky/jni/basic/Main.scala
new file mode 100644
index 0000000..b46f0d1
--- /dev/null
+++ b/samples/basic/basic-main/src/main/scala/ch/jodersky/jni/basic/Main.scala
@@ -0,0 +1,13 @@
+package ch.jodersky.jni.basic
+
+import ch.jodersky.jni.NativeLoader
+
+object Main {
+
+ def main(args: Array[String]): Unit = {
+ NativeLoader.load("/ch/jodersky/jni/basic", "demo1")
+ val r = Library.print("Hello world!\n")
+ println("Returned: " + r)
+ }
+
+}
diff --git a/examples/demo/native/CMakeLists.txt b/samples/basic/basic-native/src/CMakeLists.txt
index 059a0dd..ec112b3 100644
--- a/examples/demo/native/CMakeLists.txt
+++ b/samples/basic/basic-native/src/CMakeLists.txt
@@ -27,11 +27,12 @@ endif()
# Include directories
include_directories(.)
+include_directories(include)
include_directories(${JNI_INCLUDE_DIRS})
# Setup main shared library
# Note: major version is appended to library name
-add_library(${LIB_NAME} SHARED main.c)
+add_library(${LIB_NAME} SHARED library.c)
if (ENABLE_VERSIONED_LIB)
set_target_properties(
${LIB_NAME}
diff --git a/samples/basic/basic-native/src/include/ch_jodersky_jni_basic_Library__.h b/samples/basic/basic-native/src/include/ch_jodersky_jni_basic_Library__.h
new file mode 100644
index 0000000..60821e4
--- /dev/null
+++ b/samples/basic/basic-native/src/include/ch_jodersky_jni_basic_Library__.h
@@ -0,0 +1,21 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class ch_jodersky_jni_basic_Library__ */
+
+#ifndef _Included_ch_jodersky_jni_basic_Library__
+#define _Included_ch_jodersky_jni_basic_Library__
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class: ch_jodersky_jni_basic_Library__
+ * Method: print
+ * Signature: (Ljava/lang/String;)I
+ */
+JNIEXPORT jint JNICALL Java_ch_jodersky_jni_basic_Library_00024_print
+ (JNIEnv *, jobject, jstring);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/examples/demo/native/main.c b/samples/basic/basic-native/src/library.c
index 5916c48..eec05e5 100644
--- a/examples/demo/native/main.c
+++ b/samples/basic/basic-native/src/library.c
@@ -1,12 +1,12 @@
#include <stdio.h>
-#include "org_example_jni_demo_Library__.h"
+#include "ch_jodersky_jni_basic_Library__.h"
/*
- * Class: org_example_jni_demo_Library__
+ * Class: ch_jodersky_jni_basic_Library__
* Method: print
* Signature: (Ljava/lang/String;)I
*/
-JNIEXPORT jint JNICALL Java_org_example_jni_demo_Library_00024_print
+JNIEXPORT jint JNICALL Java_ch_jodersky_jni_basic_Library_00024_print
(JNIEnv *env, jobject clazz, jstring message) {
const char* msg = (*env)->GetStringUTFChars(env, message, 0);
fprintf(stdout, "Printing from native library: %s", msg);
diff --git a/samples/basic/build.sbt b/samples/basic/build.sbt
new file mode 100644
index 0000000..cceeb16
--- /dev/null
+++ b/samples/basic/build.sbt
@@ -0,0 +1,24 @@
+val commonSettings = Seq(
+ scalaVersion := "2.11.7",
+ organization := "ch.jodersky"
+)
+
+lazy val main = Project(
+ id = "sample-basic-main",
+ base = file("basic-main"),
+ settings = commonSettings ++ Seq(
+ target in (Compile, javah) :=
+ (sourceDirectory in native).value / "include"
+ ),
+ dependencies = Seq(
+ native % Runtime
+ )
+).enablePlugins(JniJvm)
+
+lazy val native = Project(
+ id = "sample-basic-native",
+ base = file("basic-native"),
+ settings = commonSettings ++ Seq(
+ jniLibraryPath in (Compile, jni) := "/ch/jodersky/jni/basic"
+ )
+).enablePlugins(JniNative)
diff --git a/samples/basic/project/build.properties b/samples/basic/project/build.properties
new file mode 100644
index 0000000..817bc38
--- /dev/null
+++ b/samples/basic/project/build.properties
@@ -0,0 +1 @@
+sbt.version=0.13.9
diff --git a/samples/basic/project/plugins.sbt b/samples/basic/project/plugins.sbt
new file mode 100644
index 0000000..08f838a
--- /dev/null
+++ b/samples/basic/project/plugins.sbt
@@ -0,0 +1 @@
+addSbtPlugin("ch.jodersky" % "sbt-jni" % "0.2-SNAPSHOT")