aboutsummaryrefslogtreecommitdiff
path: root/plugin
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 /plugin
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 'plugin')
-rw-r--r--plugin/src/main/resources/ch/jodersky/sbt/jni/templates/CMakeLists.txt61
-rw-r--r--plugin/src/main/scala/ch/jodersky/sbt/jni/build/Autotools.scala.notyet28
-rw-r--r--plugin/src/main/scala/ch/jodersky/sbt/jni/build/BuildTool.scala68
-rw-r--r--plugin/src/main/scala/ch/jodersky/sbt/jni/build/CMake.scala33
-rw-r--r--plugin/src/main/scala/ch/jodersky/sbt/jni/build/ConfigureMakeInstall.scala55
-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
-rw-r--r--plugin/src/main/scala/ch/jodersky/sbt/jni/util/BytecodeUtil.scala62
-rw-r--r--plugin/src/sbt-test/sbt-jni/multiclasses/README.md1
-rw-r--r--plugin/src/sbt-test/sbt-jni/multiclasses/build.sbt16
-rw-r--r--plugin/src/sbt-test/sbt-jni/multiclasses/core/src/main/scala/multiclasses/Adder.scala18
-rw-r--r--plugin/src/sbt-test/sbt-jni/multiclasses/core/src/main/scala/multiclasses/Main.scala30
-rw-r--r--plugin/src/sbt-test/sbt-jni/multiclasses/core/src/main/scala/multiclasses/Multiplier.scala12
-rw-r--r--plugin/src/sbt-test/sbt-jni/multiclasses/native1/src/CMakeLists.txt61
-rw-r--r--plugin/src/sbt-test/sbt-jni/multiclasses/native1/src/library.c28
-rw-r--r--plugin/src/sbt-test/sbt-jni/multiclasses/native2/src/CMakeLists.txt61
-rw-r--r--plugin/src/sbt-test/sbt-jni/multiclasses/native2/src/library.c16
l---------plugin/src/sbt-test/sbt-jni/multiclasses/project/ScriptedHelper.scala1
l---------plugin/src/sbt-test/sbt-jni/multiclasses/project/plugins.sbt1
-rw-r--r--plugin/src/sbt-test/sbt-jni/multiclasses/test2
-rw-r--r--plugin/src/sbt-test/sbt-jni/oneproject/README.md1
-rw-r--r--plugin/src/sbt-test/sbt-jni/oneproject/build.sbt6
l---------plugin/src/sbt-test/sbt-jni/oneproject/project/ScriptedHelper.scala1
l---------plugin/src/sbt-test/sbt-jni/oneproject/project/plugins.sbt1
l---------plugin/src/sbt-test/sbt-jni/oneproject/src/main1
l---------plugin/src/sbt-test/sbt-jni/oneproject/src/native1
-rw-r--r--plugin/src/sbt-test/sbt-jni/oneproject/test4
-rw-r--r--plugin/src/sbt-test/sbt-jni/simple/README.md1
-rw-r--r--plugin/src/sbt-test/sbt-jni/simple/build.sbt12
-rw-r--r--plugin/src/sbt-test/sbt-jni/simple/core/src/main/scala/simple/Library.scala10
-rw-r--r--plugin/src/sbt-test/sbt-jni/simple/core/src/main/scala/simple/Main.scala10
-rw-r--r--plugin/src/sbt-test/sbt-jni/simple/native/src/library.c16
-rw-r--r--plugin/src/sbt-test/sbt-jni/simple/project/ScriptedHelper.scala14
-rw-r--r--plugin/src/sbt-test/sbt-jni/simple/project/plugins.sbt3
-rw-r--r--plugin/src/sbt-test/sbt-jni/simple/test4
37 files changed, 986 insertions, 0 deletions
diff --git a/plugin/src/main/resources/ch/jodersky/sbt/jni/templates/CMakeLists.txt b/plugin/src/main/resources/ch/jodersky/sbt/jni/templates/CMakeLists.txt
new file mode 100644
index 0000000..8292064
--- /dev/null
+++ b/plugin/src/main/resources/ch/jodersky/sbt/jni/templates/CMakeLists.txt
@@ -0,0 +1,61 @@
+################################################################
+# A minimal CMake file that is compatible with sbt-jni #
+# #
+# All settings required by sbt-jni have been marked so, please #
+# add/modify/remove settings to build your specific library. #
+################################################################
+
+cmake_minimum_required(VERSION 2.6)
+
+# Define project and related variables
+#
+project ({{project}})
+
+# Set versions and library name
+# (required by sbt-jni) please use semantic versioning
+#
+set (VERSION_MAJOR 0)
+set (VERSION_MINOR 0)
+set (VERSION_PATCH 0)
+# (required by sbt-jni) major version will always be appended to library name
+set (LIB_NAME ${CMAKE_PROJECT_NAME}${VERSION_MAJOR})
+
+# Command-line options
+#
+# (set by sbt-jni)
+set (LIB_INSTALL_DIR lib CACHE PATH "Path in which to install libraries (equivalent to Autoconf --libdir).")
+# (set by sbt-jni)
+set (LIB_ENABLE_MINOR_VERSIONS ON CACHE BOOLEAN "Build libraries with minor and patch versions appended.")
+
+# Setup JNI
+find_package(JNI REQUIRED)
+if (JNI_FOUND)
+ message (STATUS "JNI include directories: ${JNI_INCLUDE_DIRS}")
+endif()
+
+# Include directories
+include_directories(.)
+include_directories(include)
+include_directories(${JNI_INCLUDE_DIRS})
+
+# Setup main shared library
+file(GLOB LIB_SRC
+ "*.c"
+ "*.cpp"
+)
+add_library(${LIB_NAME} SHARED ${LIB_SRC})
+
+# By default, in a regular build, minor and patch versions are added to the generated files.
+# When built through sbt-jni however, LIB_ENABLE_MINOR_VERSIONS is deactivated and only a
+# major-versioned library file is built.
+if (LIB_ENABLE_MINOR_VERSIONS)
+ set_target_properties(
+ ${LIB_NAME}
+ PROPERTIES
+ VERSION 0.${VERSION_MINOR}.${VERSION_PATCH} # major version always 0, it is included in library name
+ SOVERSION 0
+ )
+endif()
+
+# Installation targets
+install(TARGETS ${LIB_NAME} LIBRARY DESTINATION ${LIB_INSTALL_DIR})
diff --git a/plugin/src/main/scala/ch/jodersky/sbt/jni/build/Autotools.scala.notyet b/plugin/src/main/scala/ch/jodersky/sbt/jni/build/Autotools.scala.notyet
new file mode 100644
index 0000000..7d81858
--- /dev/null
+++ b/plugin/src/main/scala/ch/jodersky/sbt/jni/build/Autotools.scala.notyet
@@ -0,0 +1,28 @@
+package ch.jodersky.sbt.jni
+package build
+
+import java.io.File
+import sbt._
+
+object Autotools extends BuildTool with ConfigureMakeInstall {
+
+ val name = "Autotools"
+
+ def detect(baseDirectory: File) = baseDirectory.list().contains("configure")
+
+ override def getInstance(baseDir: File, buildDir: File, logger: Logger) = new Instance {
+
+ override def log = logger
+ override def baseDirectory = baseDir
+ override def buildDirectory = buildDir
+
+ override def configure(target: File) = Process(
+ s"${base.getAbsolutePath}/configure " +
+ s"--prefix=${target.getAbsolutePath} " +
+ s"--libdir=${target.getAbsolutePath} " +
+ "--disable-versioned-lib",
+ build
+ )
+ }
+
+} \ No newline at end of file
diff --git a/plugin/src/main/scala/ch/jodersky/sbt/jni/build/BuildTool.scala b/plugin/src/main/scala/ch/jodersky/sbt/jni/build/BuildTool.scala
new file mode 100644
index 0000000..3d0deb1
--- /dev/null
+++ b/plugin/src/main/scala/ch/jodersky/sbt/jni/build/BuildTool.scala
@@ -0,0 +1,68 @@
+package ch.jodersky.sbt.jni
+package build
+
+import java.io.{ File, InputStream }
+import java.nio.file.Files
+
+import scala.io.Source
+import sbt.Logger
+
+trait BuildTool {
+
+ /** Name of this build tool. */
+ def name: String
+
+ /** Detect if this build tool is configured in the given directory.
+ * E.g. for the Make build tool, this would return true if a Makefile is present
+ * in the given directory.
+ */
+ def detect(baseDirectory: File): Boolean
+
+ protected def templateMappings: Seq[(String, String)]
+
+ /** Initialize the given directory with a minimal, functioning configuration for
+ * this build tool. E.g. for the Make build tool, this would create a Makefile in
+ * the given directory that is compatible with sbt-jni.
+ * @return all created files
+ */
+ def initTemplate(baseDirectory: File, projectName: String): Seq[File] =
+ for ((resource, name) <- templateMappings) yield {
+ val resourceStream = this.getClass.getResourceAsStream(resource)
+
+ if (resourceStream == null) sys.error(s"Template for $name not found.")
+
+ val raw = Source.fromInputStream(resourceStream).mkString("")
+ val replaced = raw.replaceAll("\\{\\{project\\}\\}", projectName)
+
+ baseDirectory.mkdir()
+ val out = baseDirectory.toPath().resolve(name)
+ Files.write(out, replaced.getBytes)
+ out.toFile()
+ }
+
+ /** Actual tasks that can be perfomed on a specific configuration, such as
+ * configured in a Makefile.
+ */
+ trait Instance {
+
+ /** Invokes the native build tool's clean task */
+ def clean(): Unit
+
+ /** 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(
+ targetDirectory: File
+ ): File
+
+ }
+
+ /** Get an instance (build configuration) of this tool, in the specified directory. */
+ def getInstance(baseDirectory: File, buildDirectory: File, logger: Logger): Instance
+
+}
diff --git a/plugin/src/main/scala/ch/jodersky/sbt/jni/build/CMake.scala b/plugin/src/main/scala/ch/jodersky/sbt/jni/build/CMake.scala
new file mode 100644
index 0000000..eb7be1e
--- /dev/null
+++ b/plugin/src/main/scala/ch/jodersky/sbt/jni/build/CMake.scala
@@ -0,0 +1,33 @@
+package ch.jodersky.sbt.jni
+package build
+
+import sbt._
+
+object CMake extends BuildTool with ConfigureMakeInstall {
+
+ override val name = "CMake"
+
+ override def detect(baseDirectory: File) = baseDirectory.list().contains("CMakeLists.txt")
+
+ override protected def templateMappings = Seq(
+ "/ch/jodersky/sbt/jni/templates/CMakeLists.txt" -> "CMakeLists.txt"
+ )
+
+ override def getInstance(baseDir: File, buildDir: File, logger: Logger) = new Instance {
+
+ override def log = logger
+ override def baseDirectory = baseDir
+ override def buildDirectory = buildDir
+
+ override def configure(target: File) = Process(
+ // disable producing versioned library files, not needed for fat jars
+ "cmake " +
+ s"-DCMAKE_INSTALL_PREFIX:PATH=${target.getAbsolutePath} " +
+ s"-DLIB_INSTALL_DIR:PATH=${target.getAbsolutePath} " +
+ "-DLIB_ENABLE_MINOR_VERSIONS:BOOLEAN=OFF " +
+ baseDirectory.getAbsolutePath,
+ buildDirectory
+ )
+ }
+
+}
diff --git a/plugin/src/main/scala/ch/jodersky/sbt/jni/build/ConfigureMakeInstall.scala b/plugin/src/main/scala/ch/jodersky/sbt/jni/build/ConfigureMakeInstall.scala
new file mode 100644
index 0000000..7bfedae
--- /dev/null
+++ b/plugin/src/main/scala/ch/jodersky/sbt/jni/build/ConfigureMakeInstall.scala
@@ -0,0 +1,55 @@
+package ch.jodersky.sbt.jni
+package build
+
+import java.io.File
+import sbt._
+
+trait ConfigureMakeInstall { self: BuildTool =>
+
+ /* API for native build tools that use a standard 'configure && make && make install' process,
+ * where the configure step is left ab
+ stract. */
+ trait Instance extends self.Instance {
+
+ def log: Logger
+ def baseDirectory: File
+ def buildDirectory: File
+
+ def clean() = Process("make clean", buildDirectory) ! log
+
+ def configure(targetDirectory: File): ProcessBuilder
+
+ def make(): ProcessBuilder = Process("make", buildDirectory)
+
+ def install(): ProcessBuilder = Process("make install", buildDirectory)
+
+ def library(
+ targetDirectory: File
+ ): File = {
+
+ val ev: Int = (
+ configure(targetDirectory) #&& make() #&& install()
+ ) ! log
+
+ if (ev != 0) sys.error(s"Building native library failed. Exit code: ${ev}")
+
+ val products: List[File] =
+ (targetDirectory ** ("*.so" | "*.dylib")).get.filter(_.isFile).toList
+
+ // only one produced library is expected
+ products match {
+ case Nil =>
+ sys.error(s"No files were created during compilation, " +
+ "something went wrong with the ${name} 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/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
+
+}
diff --git a/plugin/src/main/scala/ch/jodersky/sbt/jni/util/BytecodeUtil.scala b/plugin/src/main/scala/ch/jodersky/sbt/jni/util/BytecodeUtil.scala
new file mode 100644
index 0000000..fe728a6
--- /dev/null
+++ b/plugin/src/main/scala/ch/jodersky/sbt/jni/util/BytecodeUtil.scala
@@ -0,0 +1,62 @@
+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 BytecodeUtil {
+
+ private class NativeFinder extends ClassVisitor(Opcodes.ASM5) {
+
+ // 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 //return 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()
+ }
+ }
+ }
+
+ /** Finds classes containing native implementations.
+ * @param classFile java class file from which classes are read
+ * @return all fully qualified names of classes that contain at least one member annotated
+ * with @native
+ */
+ def nativeClasses(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/plugin/src/sbt-test/sbt-jni/multiclasses/README.md b/plugin/src/sbt-test/sbt-jni/multiclasses/README.md
new file mode 100644
index 0000000..b039e7a
--- /dev/null
+++ b/plugin/src/sbt-test/sbt-jni/multiclasses/README.md
@@ -0,0 +1 @@
+Tests multiple native projects and native loading for classes.
diff --git a/plugin/src/sbt-test/sbt-jni/multiclasses/build.sbt b/plugin/src/sbt-test/sbt-jni/multiclasses/build.sbt
new file mode 100644
index 0000000..d12a711
--- /dev/null
+++ b/plugin/src/sbt-test/sbt-jni/multiclasses/build.sbt
@@ -0,0 +1,16 @@
+ivyLoggingLevel := UpdateLogging.Quiet
+
+lazy val root = (project in file(".")).
+ aggregate(core, native1, native2)
+
+lazy val core = (project in file("core")).
+ dependsOn(native1 % Runtime).
+ dependsOn(native2 % Runtime)
+
+lazy val native1 = (project in file("native1")).
+ settings(sourceDirectory in nativeCompile := sourceDirectory.value).
+ enablePlugins(JniNative)
+
+lazy val native2 = (project in file("native2")).
+ settings(sourceDirectory in nativeCompile := sourceDirectory.value).
+ enablePlugins(JniNative)
diff --git a/plugin/src/sbt-test/sbt-jni/multiclasses/core/src/main/scala/multiclasses/Adder.scala b/plugin/src/sbt-test/sbt-jni/multiclasses/core/src/main/scala/multiclasses/Adder.scala
new file mode 100644
index 0000000..91d5683
--- /dev/null
+++ b/plugin/src/sbt-test/sbt-jni/multiclasses/core/src/main/scala/multiclasses/Adder.scala
@@ -0,0 +1,18 @@
+package multiclasses
+
+import ch.jodersky.jni.nativeLoader
+
+@nativeLoader("demo0")
+class Adder(base0: Int) {
+
+ final private val base = base0
+
+ @native def plus(term: Int): Int
+
+}
+
+object Adder {
+
+ @native def sum(term1: Int, term2: Int): Int
+
+}
diff --git a/plugin/src/sbt-test/sbt-jni/multiclasses/core/src/main/scala/multiclasses/Main.scala b/plugin/src/sbt-test/sbt-jni/multiclasses/core/src/main/scala/multiclasses/Main.scala
new file mode 100644
index 0000000..6734ae9
--- /dev/null
+++ b/plugin/src/sbt-test/sbt-jni/multiclasses/core/src/main/scala/multiclasses/Main.scala
@@ -0,0 +1,30 @@
+package multiclasses
+
+object Main {
+
+ def addition(): Unit = {
+ val zero = new Adder(0)
+ val one = new Adder(1)
+ assert((zero plus 1) == 1)
+ assert((one plus 1) == 2)
+ assert(Adder.sum(0,1) == 1)
+ }
+
+ def multiplication(): Unit = {
+ val zero = new Multiplier {
+ override def base = 0
+ }
+
+ val one = new Multiplier {
+ override def base = 1
+ }
+ assert((zero times 1) == 0)
+ assert((one times 1) == 1)
+ }
+
+ def main(args: Array[String]): Unit = {
+ addition()
+ multiplication()
+ }
+
+}
diff --git a/plugin/src/sbt-test/sbt-jni/multiclasses/core/src/main/scala/multiclasses/Multiplier.scala b/plugin/src/sbt-test/sbt-jni/multiclasses/core/src/main/scala/multiclasses/Multiplier.scala
new file mode 100644
index 0000000..19cd25f
--- /dev/null
+++ b/plugin/src/sbt-test/sbt-jni/multiclasses/core/src/main/scala/multiclasses/Multiplier.scala
@@ -0,0 +1,12 @@
+package multiclasses
+
+import ch.jodersky.jni.nativeLoader
+
+@nativeLoader("multiplier1")
+abstract class Multiplier {
+
+ def base: Int = 1
+
+ @native def times(factor: Int): Int
+
+}
diff --git a/plugin/src/sbt-test/sbt-jni/multiclasses/native1/src/CMakeLists.txt b/plugin/src/sbt-test/sbt-jni/multiclasses/native1/src/CMakeLists.txt
new file mode 100644
index 0000000..1539ec2
--- /dev/null
+++ b/plugin/src/sbt-test/sbt-jni/multiclasses/native1/src/CMakeLists.txt
@@ -0,0 +1,61 @@
+################################################################
+# A minimal CMake file that is compatible with sbt-jni #
+# #
+# All settings required by sbt-jni have been marked so, please #
+# add/modify/remove settings to build your specific library. #
+################################################################
+
+cmake_minimum_required(VERSION 2.6)
+
+# Define project and related variables
+#
+project (demo)
+
+# Set versions and library name
+# (required by sbt-jni) please use semantic versioning
+#
+set (VERSION_MAJOR 0)
+set (VERSION_MINOR 0)
+set (VERSION_PATCH 0)
+# (required by sbt-jni) major version will always be appended to library name
+set (LIB_NAME ${CMAKE_PROJECT_NAME}${VERSION_MAJOR})
+
+# Command-line options
+#
+# (set by sbt-jni)
+set (LIB_INSTALL_DIR lib CACHE PATH "Path in which to install libraries (equivalent to Autoconf --libdir).")
+# (set by sbt-jni)
+set (LIB_ENABLE_MINOR_VERSIONS ON CACHE BOOLEAN "Build libraries with minor and patch versions appended.")
+
+# Setup JNI
+find_package(JNI REQUIRED)
+if (JNI_FOUND)
+ message (STATUS "JNI include directories: ${JNI_INCLUDE_DIRS}")
+endif()
+
+# Include directories
+include_directories(.)
+include_directories(../../core/target/native/include)
+include_directories(${JNI_INCLUDE_DIRS})
+
+# Setup main shared library
+file(GLOB LIB_SRC
+ "*.c"
+ "*.cpp"
+)
+add_library(${LIB_NAME} SHARED ${LIB_SRC})
+
+# By default, in a regular build, minor and patch versions are added to the generated files.
+# When built through sbt-jni however, LIB_ENABLE_MINOR_VERSIONS is deactivated and only a
+# major-versioned library file is built.
+if (LIB_ENABLE_MINOR_VERSIONS)
+ set_target_properties(
+ ${LIB_NAME}
+ PROPERTIES
+ VERSION 0.${VERSION_MINOR}.${VERSION_PATCH} # major version always 0, it is included in library name
+ SOVERSION 0
+ )
+endif()
+
+# Installation targets
+install(TARGETS ${LIB_NAME} LIBRARY DESTINATION ${LIB_INSTALL_DIR})
diff --git a/plugin/src/sbt-test/sbt-jni/multiclasses/native1/src/library.c b/plugin/src/sbt-test/sbt-jni/multiclasses/native1/src/library.c
new file mode 100644
index 0000000..8b45660
--- /dev/null
+++ b/plugin/src/sbt-test/sbt-jni/multiclasses/native1/src/library.c
@@ -0,0 +1,28 @@
+#include <jni.h>
+#include "multiclasses_Adder.h"
+#include "multiclasses_Adder__.h"
+
+/*
+ * Class: multiclasses_Adder
+ * Method: plus
+ * Signature: (I)I
+ */
+JNIEXPORT jint JNICALL Java_multiclasses_Adder_plus
+ (JNIEnv* env, jobject instance, jint term)
+{
+ jclass clazz = (*env)->GetObjectClass(env, instance);
+ jfieldID field = (*env)->GetFieldID(env, clazz, "base", "I");
+ jint base = (*env)->GetIntField(env, instance, field);
+ return base + term;
+}
+
+/*
+ * Class: multiclasses_Adder__
+ * Method: sum
+ * Signature: (II)I
+ */
+JNIEXPORT jint JNICALL Java_multiclasses_Adder_00024_sum
+ (JNIEnv* env, jobject instance, jint term1, jint term2)
+{
+ return term1 + term2;
+}
diff --git a/plugin/src/sbt-test/sbt-jni/multiclasses/native2/src/CMakeLists.txt b/plugin/src/sbt-test/sbt-jni/multiclasses/native2/src/CMakeLists.txt
new file mode 100644
index 0000000..feeab89
--- /dev/null
+++ b/plugin/src/sbt-test/sbt-jni/multiclasses/native2/src/CMakeLists.txt
@@ -0,0 +1,61 @@
+################################################################
+# A minimal CMake file that is compatible with sbt-jni #
+# #
+# All settings required by sbt-jni have been marked so, please #
+# add/modify/remove settings to build your specific library. #
+################################################################
+
+cmake_minimum_required(VERSION 2.6)
+
+# Define project and related variables
+#
+project (multiplier)
+
+# Set versions and library name
+# (required by sbt-jni) please use semantic versioning
+#
+set (VERSION_MAJOR 1)
+set (VERSION_MINOR 2)
+set (VERSION_PATCH 3)
+# (required by sbt-jni) major version will always be appended to library name
+set (LIB_NAME ${CMAKE_PROJECT_NAME}${VERSION_MAJOR})
+
+# Command-line options
+#
+# (set by sbt-jni)
+set (LIB_INSTALL_DIR lib CACHE PATH "Path in which to install libraries (equivalent to Autoconf --libdir).")
+# (set by sbt-jni)
+set (LIB_ENABLE_MINOR_VERSIONS ON CACHE BOOLEAN "Build libraries with minor and patch versions appended.")
+
+# Setup JNI
+find_package(JNI REQUIRED)
+if (JNI_FOUND)
+ message (STATUS "JNI include directories: ${JNI_INCLUDE_DIRS}")
+endif()
+
+# Include directories
+include_directories(.)
+include_directories(../../core/target/native/include)
+include_directories(${JNI_INCLUDE_DIRS})
+
+# Setup main shared library
+file(GLOB LIB_SRC
+ "*.c"
+ "*.cpp"
+)
+add_library(${LIB_NAME} SHARED ${LIB_SRC})
+
+# By default, in a regular build, minor and patch versions are added to the generated files.
+# When built through sbt-jni however, LIB_ENABLE_MINOR_VERSIONS is deactivated and only a
+# major-versioned library file is built.
+if (LIB_ENABLE_MINOR_VERSIONS)
+ set_target_properties(
+ ${LIB_NAME}
+ PROPERTIES
+ VERSION 0.${VERSION_MINOR}.${VERSION_PATCH} # major version always 0, it is included in library name
+ SOVERSION 0
+ )
+endif()
+
+# Installation targets
+install(TARGETS ${LIB_NAME} LIBRARY DESTINATION ${LIB_INSTALL_DIR})
diff --git a/plugin/src/sbt-test/sbt-jni/multiclasses/native2/src/library.c b/plugin/src/sbt-test/sbt-jni/multiclasses/native2/src/library.c
new file mode 100644
index 0000000..5fe817e
--- /dev/null
+++ b/plugin/src/sbt-test/sbt-jni/multiclasses/native2/src/library.c
@@ -0,0 +1,16 @@
+#include <jni.h>
+#include "multiclasses_Multiplier.h"
+
+/*
+ * Class: multiclasses_Multiplier
+ * Method: times
+ * Signature: (I)I
+ */
+JNIEXPORT jint JNICALL Java_multiclasses_Multiplier_times
+ (JNIEnv* env, jobject instance, jint factor)
+{
+ jclass clazz = (*env)->GetObjectClass(env, instance);
+ jmethodID method = (*env)->GetMethodID(env, clazz, "base", "()I");
+ jint base = (*env)->CallIntMethod(env, instance, method);
+ return base * factor;
+}
diff --git a/plugin/src/sbt-test/sbt-jni/multiclasses/project/ScriptedHelper.scala b/plugin/src/sbt-test/sbt-jni/multiclasses/project/ScriptedHelper.scala
new file mode 120000
index 0000000..aeaba9d
--- /dev/null
+++ b/plugin/src/sbt-test/sbt-jni/multiclasses/project/ScriptedHelper.scala
@@ -0,0 +1 @@
+../../simple/project/ScriptedHelper.scala \ No newline at end of file
diff --git a/plugin/src/sbt-test/sbt-jni/multiclasses/project/plugins.sbt b/plugin/src/sbt-test/sbt-jni/multiclasses/project/plugins.sbt
new file mode 120000
index 0000000..1c60ea7
--- /dev/null
+++ b/plugin/src/sbt-test/sbt-jni/multiclasses/project/plugins.sbt
@@ -0,0 +1 @@
+../../simple/project/plugins.sbt \ No newline at end of file
diff --git a/plugin/src/sbt-test/sbt-jni/multiclasses/test b/plugin/src/sbt-test/sbt-jni/multiclasses/test
new file mode 100644
index 0000000..e9e0d97
--- /dev/null
+++ b/plugin/src/sbt-test/sbt-jni/multiclasses/test
@@ -0,0 +1,2 @@
+> javah
+> core/run \ No newline at end of file
diff --git a/plugin/src/sbt-test/sbt-jni/oneproject/README.md b/plugin/src/sbt-test/sbt-jni/oneproject/README.md
new file mode 100644
index 0000000..9d78fef
--- /dev/null
+++ b/plugin/src/sbt-test/sbt-jni/oneproject/README.md
@@ -0,0 +1 @@
+Tests the combination of native and scala sources in a single project.
diff --git a/plugin/src/sbt-test/sbt-jni/oneproject/build.sbt b/plugin/src/sbt-test/sbt-jni/oneproject/build.sbt
new file mode 100644
index 0000000..3b95d55
--- /dev/null
+++ b/plugin/src/sbt-test/sbt-jni/oneproject/build.sbt
@@ -0,0 +1,6 @@
+ivyLoggingLevel := UpdateLogging.Quiet
+
+enablePlugins(JniNative)
+
+target in javah := (sourceDirectory in nativeCompile).value / "include"
+
diff --git a/plugin/src/sbt-test/sbt-jni/oneproject/project/ScriptedHelper.scala b/plugin/src/sbt-test/sbt-jni/oneproject/project/ScriptedHelper.scala
new file mode 120000
index 0000000..aeaba9d
--- /dev/null
+++ b/plugin/src/sbt-test/sbt-jni/oneproject/project/ScriptedHelper.scala
@@ -0,0 +1 @@
+../../simple/project/ScriptedHelper.scala \ No newline at end of file
diff --git a/plugin/src/sbt-test/sbt-jni/oneproject/project/plugins.sbt b/plugin/src/sbt-test/sbt-jni/oneproject/project/plugins.sbt
new file mode 120000
index 0000000..1c60ea7
--- /dev/null
+++ b/plugin/src/sbt-test/sbt-jni/oneproject/project/plugins.sbt
@@ -0,0 +1 @@
+../../simple/project/plugins.sbt \ No newline at end of file
diff --git a/plugin/src/sbt-test/sbt-jni/oneproject/src/main b/plugin/src/sbt-test/sbt-jni/oneproject/src/main
new file mode 120000
index 0000000..1b483d7
--- /dev/null
+++ b/plugin/src/sbt-test/sbt-jni/oneproject/src/main
@@ -0,0 +1 @@
+../../simple/core/src/main/ \ No newline at end of file
diff --git a/plugin/src/sbt-test/sbt-jni/oneproject/src/native b/plugin/src/sbt-test/sbt-jni/oneproject/src/native
new file mode 120000
index 0000000..ebbac72
--- /dev/null
+++ b/plugin/src/sbt-test/sbt-jni/oneproject/src/native
@@ -0,0 +1 @@
+../../simple/native/src/ \ No newline at end of file
diff --git a/plugin/src/sbt-test/sbt-jni/oneproject/test b/plugin/src/sbt-test/sbt-jni/oneproject/test
new file mode 100644
index 0000000..2c485fa
--- /dev/null
+++ b/plugin/src/sbt-test/sbt-jni/oneproject/test
@@ -0,0 +1,4 @@
+> javah
+$ exists src/native/include/simple_Library__.h
+> nativeInit cmake demo
+> run \ No newline at end of file
diff --git a/plugin/src/sbt-test/sbt-jni/simple/README.md b/plugin/src/sbt-test/sbt-jni/simple/README.md
new file mode 100644
index 0000000..54179b2
--- /dev/null
+++ b/plugin/src/sbt-test/sbt-jni/simple/README.md
@@ -0,0 +1 @@
+Very basic test.
diff --git a/plugin/src/sbt-test/sbt-jni/simple/build.sbt b/plugin/src/sbt-test/sbt-jni/simple/build.sbt
new file mode 100644
index 0000000..e324d5a
--- /dev/null
+++ b/plugin/src/sbt-test/sbt-jni/simple/build.sbt
@@ -0,0 +1,12 @@
+ivyLoggingLevel := UpdateLogging.Quiet
+
+lazy val root = (project in file(".")).
+ aggregate(core, native)
+
+lazy val core = (project in file("core")).
+ settings(target in javah := (sourceDirectory in nativeCompile in native).value / "include").
+ dependsOn(native % Runtime)
+
+lazy val native = (project in file("native")).
+ settings(sourceDirectory in nativeCompile := sourceDirectory.value).
+ enablePlugins(JniNative)
diff --git a/plugin/src/sbt-test/sbt-jni/simple/core/src/main/scala/simple/Library.scala b/plugin/src/sbt-test/sbt-jni/simple/core/src/main/scala/simple/Library.scala
new file mode 100644
index 0000000..785f8dd
--- /dev/null
+++ b/plugin/src/sbt-test/sbt-jni/simple/core/src/main/scala/simple/Library.scala
@@ -0,0 +1,10 @@
+package simple
+
+import ch.jodersky.jni.nativeLoader
+
+@nativeLoader("demo0")
+object Library {
+
+ @native def say(message: String): Int
+
+}
diff --git a/plugin/src/sbt-test/sbt-jni/simple/core/src/main/scala/simple/Main.scala b/plugin/src/sbt-test/sbt-jni/simple/core/src/main/scala/simple/Main.scala
new file mode 100644
index 0000000..c54d6e0
--- /dev/null
+++ b/plugin/src/sbt-test/sbt-jni/simple/core/src/main/scala/simple/Main.scala
@@ -0,0 +1,10 @@
+package simple
+
+object Main {
+
+ def main(args: Array[String]): Unit = {
+ val result = Library.say("hello world")
+ assert(result == 42)
+ }
+
+}
diff --git a/plugin/src/sbt-test/sbt-jni/simple/native/src/library.c b/plugin/src/sbt-test/sbt-jni/simple/native/src/library.c
new file mode 100644
index 0000000..4fa15b1
--- /dev/null
+++ b/plugin/src/sbt-test/sbt-jni/simple/native/src/library.c
@@ -0,0 +1,16 @@
+#include <stdio.h>
+#include "simple_Library__.h"
+
+/*
+ * Class: simple_Library__
+ * Method: say
+ * Signature: (Ljava/lang/String;)I
+ */
+JNIEXPORT jint JNICALL Java_simple_Library_00024_say
+(JNIEnv *env, jobject clazz, jstring message) {
+ const char* msg = (*env)->GetStringUTFChars(env, message, 0);
+ fprintf(stdout, "Printing from native library: %s\n", msg);
+ fflush(stdout);
+ (*env)->ReleaseStringUTFChars(env, message, msg);
+ return 42;
+}
diff --git a/plugin/src/sbt-test/sbt-jni/simple/project/ScriptedHelper.scala b/plugin/src/sbt-test/sbt-jni/simple/project/ScriptedHelper.scala
new file mode 100644
index 0000000..cd63c89
--- /dev/null
+++ b/plugin/src/sbt-test/sbt-jni/simple/project/ScriptedHelper.scala
@@ -0,0 +1,14 @@
+import sbt._
+import sbt.Keys._
+
+object ScriptedHelper extends AutoPlugin {
+
+ override def requires = empty
+ override def trigger = allRequirements
+
+ override def projectSettings = Seq(
+ crossScalaVersions := Seq("2.11.8", "2.12.0-M4"),
+ scalaVersion := crossScalaVersions.value.head
+ )
+
+}
diff --git a/plugin/src/sbt-test/sbt-jni/simple/project/plugins.sbt b/plugin/src/sbt-test/sbt-jni/simple/project/plugins.sbt
new file mode 100644
index 0000000..b8fc7ed
--- /dev/null
+++ b/plugin/src/sbt-test/sbt-jni/simple/project/plugins.sbt
@@ -0,0 +1,3 @@
+ivyLoggingLevel := UpdateLogging.Quiet
+
+addSbtPlugin("ch.jodersky" % "sbt-jni" % System.getProperty("plugin.version"))
diff --git a/plugin/src/sbt-test/sbt-jni/simple/test b/plugin/src/sbt-test/sbt-jni/simple/test
new file mode 100644
index 0000000..dd9e5bf
--- /dev/null
+++ b/plugin/src/sbt-test/sbt-jni/simple/test
@@ -0,0 +1,4 @@
+> javah
+$ exists native/src/include/simple_Library__.h
+> nativeInit cmake demo
+> core/run \ No newline at end of file