From 1cdbc2d2e04c7ab34d2e5af07567b37c60be571f Mon Sep 17 00:00:00 2001 From: Jakob Odersky Date: Mon, 11 Jan 2016 23:11:28 -0800 Subject: WIP divide plugin --- .../main/scala/ch/jodersky/jni/NativeLoader.scala | 6 +- .../src/main/scala/ch/jodersky/jni/Platform.scala | 6 ++ .../main/scala/ch/jodersky/sbt/jni/JniJvm.scala | 21 ++-- .../main/scala/ch/jodersky/sbt/jni/JniNative.scala | 18 ++-- .../scala/ch/jodersky/sbt/jni/JniPackager.scala | 110 +++++++++++++++++++++ .../main/scala/ch/jodersky/sbt/jni/JniPlugin.scala | 99 +++++++++++++++++++ .../scala/ch/jodersky/sbt/jni/util/ByteCode.scala | 4 +- project/Build.scala | 2 +- 8 files changed, 247 insertions(+), 19 deletions(-) create mode 100644 jni-plugin/src/main/scala/ch/jodersky/sbt/jni/JniPackager.scala create mode 100644 jni-plugin/src/main/scala/ch/jodersky/sbt/jni/JniPlugin.scala 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 261e52b..c806464 100644 --- a/jni-library/src/main/scala/ch/jodersky/jni/NativeLoader.scala +++ b/jni-library/src/main/scala/ch/jodersky/jni/NativeLoader.scala @@ -45,11 +45,11 @@ object NativeLoader { ) /** - * Gets the absolute, full path of a resource on the classpath, given a libraryPath + * Gets the absolute, full path of a native library on the classpath, given a libraryPath * and platform. */ def fullLibraryPath(libraryPath: String, platform: Platform) = { - libraryPath + "/native/" + platform.id + "/" + LibraryName + libraryPath + "/" + platform.id + "/" + LibraryName } private def loadFromJar(libraryPath: String): Unit = { @@ -66,7 +66,7 @@ object NativeLoader { } /** - * Loads a native library from the available library path or fall back + * Loads a native library from the available library path or falls back * to extracting and loading a native library from available resources. */ def load(libraryPath: String, library: String): Unit = try { 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 cff9e95..1ece9e3 100644 --- a/jni-library/src/main/scala/ch/jodersky/jni/Platform.scala +++ b/jni-library/src/main/scala/ch/jodersky/jni/Platform.scala @@ -49,4 +49,10 @@ object Platform { /** Determines platform the current JVM is running on. */ def current = uname + /** Parse an id to a platform. */ + def fromId(id: String) = { + val (arch, dashKernel) = id.span(_ != '-') + Platform(arch, dashKernel.drop(1)) + } + } 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 index 292e1fa..789eabf 100644 --- a/jni-plugin/src/main/scala/ch/jodersky/sbt/jni/JniJvm.scala +++ b/jni-plugin/src/main/scala/ch/jodersky/sbt/jni/JniJvm.scala @@ -24,8 +24,9 @@ object JniJvm extends AutoPlugin { lazy val mainSettings: Seq[Setting[_]] = Seq( javahClasses in javah := { - val classFiles: Set[File] = compile.value.relations.allProducts.toSet - val nativeClasses = classFiles.flatMap { file => + val compiled: inc.Analysis = (compile in Compile).value + val classFiles: Set[File] = compiled.relations.allProducts.toSet + val nativeClasses = classFiles flatMap { file => ByteCode.natives(file) } nativeClasses @@ -33,11 +34,17 @@ object JniJvm extends AutoPlugin { 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 jcp: Seq[File] = { + (compile in Compile).value + //FIXME: a cleaner approach that would also call compile would + //be to use `fullClasspath`, this however results in generating resources + //which in turn might require header files generated by javah. Hence + //to avoid a cyclic dependency `compile` is called first and the + //class directory returned. + Seq((classDirectory in Compile).value) + } val cp = jcp.mkString(sys.props("path.separator")) val log = streams.value.log @@ -62,9 +69,9 @@ object JniJvm extends AutoPlugin { lazy val clientSettings = Seq( //enable enhanced native library extraction libraryDependencies += "ch.jodersky" %% "jni-library" % Version.PluginVersion, - fork in run := true //fork new JVM as native libraries can only be loaded once + fork in run := true //fork new JVM, since native libraries can only be loaded once ) - override lazy val projectSettings = inConfig(Compile)(mainSettings) ++ clientSettings + override lazy val projectSettings = 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 index 90f7cc8..9cfa2af 100644 --- a/jni-plugin/src/main/scala/ch/jodersky/sbt/jni/JniNative.scala +++ b/jni-plugin/src/main/scala/ch/jodersky/sbt/jni/JniNative.scala @@ -21,7 +21,7 @@ object JniNative extends AutoPlugin { } import autoImport._ - lazy val settings: Seq[Setting[_]] = Seq( + lazy val mainSettings: Seq[Setting[_]] = Seq( sourceDirectory in jni := baseDirectory.value / "src", @@ -86,11 +86,15 @@ object JniNative extends AutoPlugin { jniLibraryPath in jni := { "/" + organization.value.replaceAll("\\.", "/") + "/" + name.value - }, + } + + ) - unmanagedResourceDirectories += baseDirectory.value / "lib_native", + lazy val resourceSettings: Seq[Setting[_]] = Seq( - resourceGenerators += Def.task { + unmanagedResourceDirectories in Compile += baseDirectory.value / "lib_native", + + resourceGenerators in Compile += Def.task { //build native library val library = jni.value @@ -109,8 +113,10 @@ object JniNative extends AutoPlugin { ) - override lazy val projectSettings = inConfig(Compile)(settings) ++ Seq( - //don't scala version to native jars + + + override lazy val projectSettings = mainSettings ++ resourceSettings ++ Seq( + //don't add scala version to native jars crossPaths := false ) diff --git a/jni-plugin/src/main/scala/ch/jodersky/sbt/jni/JniPackager.scala b/jni-plugin/src/main/scala/ch/jodersky/sbt/jni/JniPackager.scala new file mode 100644 index 0000000..56322ad --- /dev/null +++ b/jni-plugin/src/main/scala/ch/jodersky/sbt/jni/JniPackager.scala @@ -0,0 +1,110 @@ +package ch.jodersky.sbt.jni + +import build._ +import ch.jodersky.jni.{NativeLoader, Platform} +import sbt._ +import sbt.Keys._ + +object JniPackager extends AutoPlugin { + + override def requires = Jni + override def trigger = allRequirements + + object autoImport { + + //Main task, inspect this first + val packageNative = taskKey[File]("Packages native libraries in a jar.") + + val nativeLibraryPath = settingKey[String]( + "String that is prepended to the path of a native library when packaged.") + + val enableNativeCompilation = settingKey[Boolean]( + "Determines if native compilation is enabled in a scoped key (typically for packaging).") + + val unmanagedNativeDirectories = settingKey[Seq[File]]( + """|Unmanaged directories containing native libraries. The libraries must be regular files + |contained in a subdirectory corresponding to a platform.""".stripMargin) + + val unmanagedNativeLibraries = taskKey[Map[Platform, File]]("") + + } + import autoImport._ + import Jni.autoImport._ + + lazy val settings: Seq[Setting[_]] = Seq( + + nativeLibraryPath := { + val orgPath = organization.value.replaceAll("\\.", "/") + s"/${orgPath}/${name.value}/native" + }, + + //Make NativeLoader available to project + libraryDependencies += "ch.jodersky" %% "jni-library" % Version.PluginVersion, + + name in packageNative := { + name.value + "-native-" + version.value + ".jar" + }, + + enableNativeCompilation in packageNative := true, + + unmanagedNativeDirectories := Seq(baseDirectory.value / "lib_native"), + + unmanagedNativeLibraries := { + val dirs: Seq[File] = unmanagedNativeDirectories.value + val seq: Seq[(Platform, File)] = for ( + dir <- dirs; + platformDir <- dir.listFiles(); + if platformDir.isDirectory; + platform = Platform.fromId(platformDir.name); + library <- platformDir.listFiles(); + if library.isFile + ) yield { + platform -> library.getCanonicalFile() + } + + seq.toMap + }, + + packageNative := { + val log = streams.value.log + + val unmanagedMappings: Seq[(File, String)] = { + unmanagedNativeLibraries.value.toSeq.map{ case (platform, file) => + file -> NativeLoader.fullLibraryPath( + (nativeLibraryPath in Compile).value, + platform) + } + } + + val enableManaged = (enableNativeCompilation in packageNative).value + + val managedMappings: Seq[(File, String)] = if (enableManaged) { + val library: File = (nativeCompile in Compile).value + val path: String = NativeLoader.fullLibraryPath( + (nativeLibraryPath in Compile).value, + (nativePlatform in Compile).value + ) + Seq(library -> path) + } else { + Seq() + } + + val mappings = unmanagedMappings ++ managedMappings + val manifest = new java.util.jar.Manifest + val jar: File = (target in nativeCompile).value / (name in packageNative).value + + Package.makeJar(mappings, jar, manifest, log) + jar + }, + + //Add native jar to runtime classpath. Note that it is not added as a resource (this would cause + //the native library to included in the main jar). + unmanagedClasspath in Runtime += Attributed.blank(packageNative.value), + + fork in run := true //fork new JVM, since native libraries can only be loaded once + + ) + override lazy val projectSettings = (settings) + + +} 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 new file mode 100644 index 0000000..2b76b7a --- /dev/null +++ b/jni-plugin/src/main/scala/ch/jodersky/sbt/jni/JniPlugin.scala @@ -0,0 +1,99 @@ +package ch.jodersky.sbt.jni + +import build._ +import ch.jodersky.jni.{NativeLoader, Platform} +import sbt._ +import sbt.Keys._ + +object Jni extends AutoPlugin { + + override def requires = plugins.JvmPlugin + + object autoImport { + + //Main task, inspect this first + val nativeCompile = taskKey[File]("Builds a native library (by calling the native build tool).") + + val nativePlatform = settingKey[Platform]("Platform of the system this build is running on.") + + val nativeBuildTools = taskKey[Seq[BuildTool]]( + "A collection of build tools that are tested to determine the current build environment") + val nativeBuildTool = taskKey[BuildTool]( + "The build tool to be used when building a native library.") + + + } + import autoImport._ + + lazy val mainSettings: Seq[Setting[_]] = Seq( + + nativePlatform := Platform.current.getOrElse{ + sLog.value.warn("Warning: cannot determine platform! It will be set to 'unknown'.") + Platform.Unknown + }, + + sourceDirectory in nativeCompile := sourceDirectory.value / "native", + + target in nativeCompile := target.value / "native" / (nativePlatform).value.id, + + nativeBuildTools := Seq(CMake, Autotools), + + nativeBuildTool := { + val tools = nativeBuildTools.value + + 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 $src) " + + "points to a valid directory. Supported build tools are: " + + tools.map(_.name).mkString(",")) + ) + + }, + + clean in nativeCompile := { + val log = streams.value.log + + log.debug("Cleaning native build") + try { + val tool = nativeBuildTool.value + tool.api.clean( + (sourceDirectory in nativeCompile).value, + log + ) + } catch { + case ex: Exception => + log.debug(s"Native cleaning failed: $ex") + } + + }, + + nativeCompile := { + val tool = nativeBuildTool.value + val src = (sourceDirectory in nativeCompile).value + val buildDir = (target in nativeCompile).value / "build" + val targetDir = (target in nativeCompile).value / "lib" + val log = streams.value.log + + IO.createDirectory(buildDir) + IO.createDirectory(targetDir) + + tool.api.library(src, buildDir, targetDir, log) + }, + + // change clean task to also clean native sources + clean := { + (clean in nativeCompile).value + clean.value + } + + ) + + override lazy val projectSettings = inConfig(Compile)(mainSettings) +} 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 index 308a077..b3c6fa3 100644 --- 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 @@ -14,7 +14,7 @@ object ByteCode { val _nativeClasses = new HashSet[String] def nativeClasses = _nativeClasses.toSet - private var fullyQualifiedName: String = "" + private var fullyQualifiedName: String = "" override def visit(version: Int, access: Int, name: String, signature: String, superName: String, interfaces: Array[String]): Unit = { @@ -30,7 +30,7 @@ object ByteCode { _nativeClasses += fullyQualifiedName } - null //do not visit method further + null //return null, do not visit method further } } diff --git a/project/Build.scala b/project/Build.scala index 4991d9e..9c9b917 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -6,7 +6,7 @@ object JniBuild extends Build { val scalaVersions = List("2.11.7", "2.12.0-M3", "2.10.5") val commonSettings = Seq( - version := "0.2-SNAPSHOT", + version := "0.3-SNAPSHOT", organization := "ch.jodersky", scalacOptions ++= Seq("-deprecation", "-feature") ) -- cgit v1.2.3