From e2505d1d9e2e49554057a8cd5fb71b0ac0e3ba63 Mon Sep 17 00:00:00 2001 From: Jakob Odersky Date: Sun, 6 Dec 2015 17:03:08 -0800 Subject: Use separate project for native libraries --- README.md | 16 +- examples/demo/build.sbt | 16 -- examples/demo/native/CMakeLists.txt | 45 ----- examples/demo/native/main.c | 16 -- .../demo/native/org_example_jni_demo_Library__.h | 21 --- examples/demo/project/plugins.sbt | 1 - .../main/scala/org/example/jni/demo/Library.scala | 8 - .../src/main/scala/org/example/jni/demo/Main.scala | 12 -- .../main/scala/ch/jodersky/jni/NativeLoader.scala | 19 +- .../src/main/scala/ch/jodersky/jni/Platform.scala | 6 +- .../main/scala/ch/jodersky/sbt/jni/Defaults.scala | 192 --------------------- .../main/scala/ch/jodersky/sbt/jni/JniJvm.scala | 71 ++++++++ .../main/scala/ch/jodersky/sbt/jni/JniNative.scala | 114 ++++++++++++ .../main/scala/ch/jodersky/sbt/jni/JniPlugin.scala | 19 -- .../src/main/scala/ch/jodersky/sbt/jni/Keys.scala | 53 ------ .../ch/jodersky/sbt/jni/build/BuildToolApi.scala | 14 +- .../scala/ch/jodersky/sbt/jni/build/CMake.scala | 53 ++---- .../sbt/jni/build/ConfigureMakeInstall.scala | 49 ++++++ .../scala/ch/jodersky/sbt/jni/build/NoTool.scala | 9 - .../scala/ch/jodersky/sbt/jni/util/ByteCode.scala | 57 ++++++ project/Build.scala | 32 +++- .../main/scala/ch/jodersky/jni/basic/Library.scala | 8 + .../main/scala/ch/jodersky/jni/basic/Main.scala | 13 ++ samples/basic/basic-native/src/CMakeLists.txt | 46 +++++ .../src/include/ch_jodersky_jni_basic_Library__.h | 21 +++ samples/basic/basic-native/src/library.c | 16 ++ samples/basic/build.sbt | 24 +++ samples/basic/project/build.properties | 1 + samples/basic/project/plugins.sbt | 1 + 29 files changed, 485 insertions(+), 468 deletions(-) delete mode 100644 examples/demo/build.sbt delete mode 100644 examples/demo/native/CMakeLists.txt delete mode 100644 examples/demo/native/main.c delete mode 100644 examples/demo/native/org_example_jni_demo_Library__.h delete mode 100644 examples/demo/project/plugins.sbt delete mode 100644 examples/demo/src/main/scala/org/example/jni/demo/Library.scala delete mode 100644 examples/demo/src/main/scala/org/example/jni/demo/Main.scala delete mode 100644 jni-plugin/src/main/scala/ch/jodersky/sbt/jni/Defaults.scala create mode 100644 jni-plugin/src/main/scala/ch/jodersky/sbt/jni/JniJvm.scala create mode 100644 jni-plugin/src/main/scala/ch/jodersky/sbt/jni/JniNative.scala delete mode 100644 jni-plugin/src/main/scala/ch/jodersky/sbt/jni/JniPlugin.scala delete mode 100644 jni-plugin/src/main/scala/ch/jodersky/sbt/jni/Keys.scala create mode 100644 jni-plugin/src/main/scala/ch/jodersky/sbt/jni/build/ConfigureMakeInstall.scala delete mode 100644 jni-plugin/src/main/scala/ch/jodersky/sbt/jni/build/NoTool.scala create mode 100644 jni-plugin/src/main/scala/ch/jodersky/sbt/jni/util/ByteCode.scala create mode 100644 samples/basic/basic-main/src/main/scala/ch/jodersky/jni/basic/Library.scala create mode 100644 samples/basic/basic-main/src/main/scala/ch/jodersky/jni/basic/Main.scala create mode 100644 samples/basic/basic-native/src/CMakeLists.txt create mode 100644 samples/basic/basic-native/src/include/ch_jodersky_jni_basic_Library__.h create mode 100644 samples/basic/basic-native/src/library.c create mode 100644 samples/basic/build.sbt create mode 100644 samples/basic/project/build.properties create mode 100644 samples/basic/project/plugins.sbt 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/CMakeLists.txt b/examples/demo/native/CMakeLists.txt deleted file mode 100644 index 059a0dd..0000000 --- a/examples/demo/native/CMakeLists.txt +++ /dev/null @@ -1,45 +0,0 @@ -cmake_minimum_required(VERSION 2.6) - -# Define project and related variables -# -project (demo) - -# Set versions and library name -# Note: major version will be appended to library name -# -set (VERSION_MAJOR 1) -set (VERSION_MINOR 2) -set (VERSION_PATCH 3) -set (LIB_NAME demo${VERSION_MAJOR}) - -# Command-line options -# -# required by sbt-jni to install binaries to correct places -set (LIB_INSTALL_DIR lib CACHE PATH "Path in which to install libraries (Autoconf equivalent to --libdir).") -# required by sbt-jni to disable versioned libraries -set (ENABLE_VERSIONED_LIB ON CACHE BOOLEAN "Generate versioned library files and symlinks.") - -# Setup JNI -find_package(JNI REQUIRED) -if (JNI_FOUND) - message (STATUS "JNI include directories: ${JNI_INCLUDE_DIRS}") -endif() - -# Include directories -include_directories(.) -include_directories(${JNI_INCLUDE_DIRS}) - -# Setup main shared library -# Note: major version is appended to library name -add_library(${LIB_NAME} SHARED main.c) -if (ENABLE_VERSIONED_LIB) - set_target_properties( - ${LIB_NAME} - PROPERTIES - VERSION 0.${VERSION_MINOR}.${VERSION_PATCH} # major version always 0, it is included in name - SOVERSION 0 - ) -endif() - -# Installation targets -install(TARGETS ${LIB_NAME} LIBRARY DESTINATION ${LIB_INSTALL_DIR}) diff --git a/examples/demo/native/main.c b/examples/demo/native/main.c deleted file mode 100644 index 5916c48..0000000 --- a/examples/demo/native/main.c +++ /dev/null @@ -1,16 +0,0 @@ -#include -#include "org_example_jni_demo_Library__.h" - -/* - * 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 *env, jobject clazz, jstring message) { - const char* msg = (*env)->GetStringUTFChars(env, message, 0); - fprintf(stdout, "Printing from native library: %s", msg); - fflush(stdout); - (*env)->ReleaseStringUTFChars(env, message, msg); - return 0; -} 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 -/* 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/Library.scala b/examples/demo/src/main/scala/org/example/jni/demo/Library.scala deleted file mode 100644 index c157b5d..0000000 --- a/examples/demo/src/main/scala/org/example/jni/demo/Library.scala +++ /dev/null @@ -1,8 +0,0 @@ -package org.example.jni.demo - -/** A demo object, mapping to a native library. */ -object Library { - - @native def print(message: String): Int - -} 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//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/samples/basic/basic-main/src/main/scala/ch/jodersky/jni/basic/Library.scala b/samples/basic/basic-main/src/main/scala/ch/jodersky/jni/basic/Library.scala new file mode 100644 index 0000000..efe3018 --- /dev/null +++ b/samples/basic/basic-main/src/main/scala/ch/jodersky/jni/basic/Library.scala @@ -0,0 +1,8 @@ +package ch.jodersky.jni.basic + +/** A demo object, mapping to a native library. */ +object Library { + + @native def print(message: String): Int + +} 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/samples/basic/basic-native/src/CMakeLists.txt b/samples/basic/basic-native/src/CMakeLists.txt new file mode 100644 index 0000000..ec112b3 --- /dev/null +++ b/samples/basic/basic-native/src/CMakeLists.txt @@ -0,0 +1,46 @@ +cmake_minimum_required(VERSION 2.6) + +# Define project and related variables +# +project (demo) + +# Set versions and library name +# Note: major version will be appended to library name +# +set (VERSION_MAJOR 1) +set (VERSION_MINOR 2) +set (VERSION_PATCH 3) +set (LIB_NAME demo${VERSION_MAJOR}) + +# Command-line options +# +# required by sbt-jni to install binaries to correct places +set (LIB_INSTALL_DIR lib CACHE PATH "Path in which to install libraries (Autoconf equivalent to --libdir).") +# required by sbt-jni to disable versioned libraries +set (ENABLE_VERSIONED_LIB ON CACHE BOOLEAN "Generate versioned library files and symlinks.") + +# 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 +# Note: major version is appended to library name +add_library(${LIB_NAME} SHARED library.c) +if (ENABLE_VERSIONED_LIB) + set_target_properties( + ${LIB_NAME} + PROPERTIES + VERSION 0.${VERSION_MINOR}.${VERSION_PATCH} # major version always 0, it is included in name + SOVERSION 0 + ) +endif() + +# Installation targets +install(TARGETS ${LIB_NAME} LIBRARY DESTINATION ${LIB_INSTALL_DIR}) 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 +/* 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/samples/basic/basic-native/src/library.c b/samples/basic/basic-native/src/library.c new file mode 100644 index 0000000..eec05e5 --- /dev/null +++ b/samples/basic/basic-native/src/library.c @@ -0,0 +1,16 @@ +#include +#include "ch_jodersky_jni_basic_Library__.h" + +/* + * 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 *env, jobject clazz, jstring message) { + const char* msg = (*env)->GetStringUTFChars(env, message, 0); + fprintf(stdout, "Printing from native library: %s", msg); + fflush(stdout); + (*env)->ReleaseStringUTFChars(env, message, msg); + return 0; +} 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") -- cgit v1.2.3