aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob Odersky <jodersky@gmail.com>2015-11-16 21:24:45 -0800
committerJakob Odersky <jodersky@gmail.com>2015-11-16 23:02:14 -0800
commit9328cb2b61751472b6abe9c57502ecef79d3d069 (patch)
treedef4f3f3cf6b652b035d5d7b3c08f72caccaddbd
parentc3b825b45753d8a82930d85cfc2abd81f30cb54a (diff)
downloadakka-serial-9328cb2b61751472b6abe9c57502ecef79d3d069.tar.gz
akka-serial-9328cb2b61751472b6abe9c57502ecef79d3d069.tar.bz2
akka-serial-9328cb2b61751472b6abe9c57502ecef79d3d069.zip
Improved determination of platform and loading of native libraries.
-rw-r--r--flow-main/src/main/java/com/github/jodersky/flow/internal/NativeSerial.java2
-rw-r--r--flow-main/src/main/scala/com/github/jodersky/flow/internal/NativeLoader.scala81
-rw-r--r--project/Build.scala26
-rw-r--r--project/native.scala188
4 files changed, 205 insertions, 92 deletions
diff --git a/flow-main/src/main/java/com/github/jodersky/flow/internal/NativeSerial.java b/flow-main/src/main/java/com/github/jodersky/flow/internal/NativeSerial.java
index c159058..81bc9d5 100644
--- a/flow-main/src/main/java/com/github/jodersky/flow/internal/NativeSerial.java
+++ b/flow-main/src/main/java/com/github/jodersky/flow/internal/NativeSerial.java
@@ -25,7 +25,7 @@ import com.github.jodersky.flow.PortInterruptedException;
final class NativeSerial {
static {
- NativeLoader.load("flow3");
+ NativeLoader.load("flow3", "/com/github/jodersky/flow/native");
}
final static int PARITY_NONE = 0;
diff --git a/flow-main/src/main/scala/com/github/jodersky/flow/internal/NativeLoader.scala b/flow-main/src/main/scala/com/github/jodersky/flow/internal/NativeLoader.scala
index a5fdc40..4254c5c 100644
--- a/flow-main/src/main/scala/com/github/jodersky/flow/internal/NativeLoader.scala
+++ b/flow-main/src/main/scala/com/github/jodersky/flow/internal/NativeLoader.scala
@@ -1,19 +1,48 @@
package com.github.jodersky.flow
package internal
-import java.io.{ File, FileOutputStream, InputStream, OutputStream }
+import java.io.{File, FileOutputStream, InputStream, OutputStream}
+import scala.io.Source
+import scala.sys.process.Process
+import scala.util.Try
/** Handles loading of the current platform's native library for flow. */
object NativeLoader {
- private final val BufferSize = 4096
+ /** A platform is the representation of an os-architecture combination */
+ case class Platform(kernel: String, arch: String) {
+ val id = kernel + "-" + arch
+ }
+
+ object Platform {
+
+ /** Create a platform with spaces stripped and case normalized. */
+ def normalize(kernel: String, arch: String) = Platform(
+ kernel.toLowerCase.filter(!_.isWhitespace),
+ arch
+ )
+
+ /** Run 'uname' to determine current platform. Returns None if uname does not exist. */
+ lazy val uname: Option[Platform] = {
+ val lines = Try { Process("uname -sm").lineStream.head }.toOption
+ lines.map { line =>
+ val parts = line.split(" ")
+ if (parts.length != 2) {
+ sys.error("Could not determine platform: 'uname -sm' returned unexpected string: " + line)
+ } else {
+ Platform.normalize(parts(0), parts(1))
+ }
+ }
+ }
+
+ }
- private def os = System.getProperty("os.name").toLowerCase.replaceAll("\\s", "")
+ private final val BufferSize = 4096
- private def arch = System.getProperty("os.arch").toLowerCase
+ private final val LibraryManifest = "library"
/** Extract a resource from this class loader to a temporary file. */
- private def extract(path: String, prefix: String): Option[File] = {
+ private def extract(path: String): Option[File] = {
var in: Option[InputStream] = None
var out: Option[OutputStream] = None
@@ -21,7 +50,7 @@ object NativeLoader {
in = Option(NativeLoader.getClass.getResourceAsStream(path))
if (in.isEmpty) return None
- val file = File.createTempFile(prefix, "")
+ val file = File.createTempFile(path, "")
out = Some(new FileOutputStream(file))
val buffer = new Array[Byte](BufferSize)
@@ -38,21 +67,41 @@ object NativeLoader {
}
}
- private def loadFromJar(library: String) = {
- val fqlib = System.mapLibraryName(library) //fully qualified library name
- val path = s"/native/${os}-${arch}/${fqlib}"
- extract(path, fqlib) match {
- case Some(file) => System.load(file.getAbsolutePath)
- case None => throw new UnsatisfiedLinkError("Cannot extract flow's native library, " +
- "the native library does not exist for your specific architecture/OS combination." +
- "Could not find " + path + ".")
+ private def loadError(msg: String): Nothing = throw new UnsatisfiedLinkError(
+ "Error during native library extraction " +
+ "(this can happen if your platform is not supported by flow): " +
+ msg
+ )
+
+ private def loadFromJar(libraryPrefix: String): Unit = {
+ val platformDir: String = libraryPrefix + "/" + Platform.uname.map(_.id).getOrElse {
+ loadError("Cannot determine current platform.")
+ }
+
+ val manifest: File = {
+ val path = platformDir + "/" + LibraryManifest
+ extract(path) getOrElse {
+ loadError(s"Manifest file $path does not exist.")
+ }
+ }
+
+ Source.fromFile(manifest, "utf-8").getLines foreach { libname =>
+ val path = platformDir + "/" + libname
+ val lib = extract(path) getOrElse loadError(
+ s"Library $path not found."
+ )
+ System.load(lib.getAbsolutePath())
}
}
- def load(library: String) = try {
+ /**
+ * Load 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, libraryPrefix: String): Unit = try {
System.loadLibrary(library)
} catch {
- case ex: UnsatisfiedLinkError => loadFromJar(library)
+ case ex: UnsatisfiedLinkError => loadFromJar(libraryPrefix)
}
}
diff --git a/project/Build.scala b/project/Build.scala
index 47ead93..3e5ba6f 100644
--- a/project/Build.scala
+++ b/project/Build.scala
@@ -1,15 +1,15 @@
-import sbt._
-import Keys._
import JniKeys._
-import NativeKeys._
-
+import native.NativeDefaults
+import native.NativeKeys._
+import sbt._
+import sbt.Keys._
object FlowBuild extends Build {
val scalaVersions = List("2.11.7", "2.12.0-M2")
-
+
lazy val commonSettings: Seq[Setting[_]] = Seq(
- version := "2.3.1-SNAPSHOT",
+ version := "2.4.0-SNAPSHOT",
scalaVersion in ThisBuild := scalaVersions.head,
crossScalaVersions in ThisBuild := scalaVersions.reverse,
scalacOptions ++= Seq("-deprecation", "-unchecked", "-feature", "-target:jvm-1.8"),
@@ -30,7 +30,7 @@ object FlowBuild extends Build {
</developers>
}
)
-
+
lazy val runSettings: Seq[Setting[_]] = Seq(
fork := true,
connectInput in run := true,
@@ -48,7 +48,7 @@ object FlowBuild extends Build {
publishTo := Some(Resolver.file("Unused transient repository", target.value / "unusedrepo")) // make sbt-pgp happy
)
)
-
+
lazy val main: Project = (
Project("flow-main", file("flow-main"))
settings(commonSettings: _*)
@@ -69,7 +69,8 @@ object FlowBuild extends Build {
settings(
name := "flow-native",
crossPaths := false,
- nativeBuildDirectory := (baseDirectory in ThisBuild).value / "flow-native"
+ libraryPrefix in Compile := "com/github/jodersky/flow/native",
+ sourceDirectory in Native := (baseDirectory in ThisBuild).value / "flow-native"
)
)
@@ -78,12 +79,7 @@ object FlowBuild extends Build {
settings(commonSettings: _*)
settings(runSettings: _*)
dependsOn(main)
-
- //kind of dirty, but it gets the sample to run without installing native libraries
- settings(
- (run in Compile) <<= (run in Compile).dependsOn(nativeBuild in native),
- javaOptions += "-Djava.library.path=" + (nativeOutputDirectory in native).value.getAbsolutePath()
- )
+ dependsOn(native)
)
lazy val samplesWatcher = (
diff --git a/project/native.scala b/project/native.scala
index 342a863..b6e4c19 100644
--- a/project/native.scala
+++ b/project/native.scala
@@ -1,83 +1,151 @@
-import sbt._
-import Keys._
-import java.io.File
-import java.util.jar.Manifest
-
-object NativeKeys {
+package native
- val nativeBuildDirectory = settingKey[File]("Directory containing native build scripts.")
- val nativeTargetDirectory = settingKey[File]("Base directory to store native products.")
- val nativeOutputDirectory = settingKey[File]("Actual directory where native products are stored.")
- val nativePackageUnmanagedDirectory = settingKey[File]("Directory containing external products that will be copied to the native jar.")
+import java.io.File
+import sbt._
+import sbt.Keys._
+import scala.util.Try
- val nativeClean = taskKey[Unit]("Clean native build.")
- val nativeBuild = taskKey[File]("Invoke native build.")
+/** A platform is a the representation of an os-architecture combination */
+case class Platform(kernel: String, arch: String) {
+ val id = kernel + "-" + arch
}
-object NativeDefaults {
- import NativeKeys._
-
- val autoClean = Def.task {
- val log = streams.value.log
- val build = nativeBuildDirectory.value
-
- Process("make distclean", build) #|| Process("make clean", build) ! log
+object Platform {
+
+ /** Create a platform with spaces stripped and case normalized. */
+ def normalize(kernel: String, arch: String) = Platform(
+ kernel.toLowerCase.filter(!_.isWhitespace),
+ arch
+ )
+
+ /** Run 'uname' to determine current platform. Returns None if uname does not exist. */
+ lazy val uname: Option[Platform] = {
+ val lines = Try { Process("uname -sm").lines.head }.toOption
+ lines.map { line =>
+ val parts = line.split(" ")
+ if (parts.length != 2) {
+ sys.error("Could not determine platform: 'uname -sm' returned unexpected string: " + line)
+ } else {
+ Platform.normalize(parts(0), parts(1))
+ }
}
+ }
- val autoLib = Def.task {
- val log = streams.value.log
- val build = nativeBuildDirectory.value
- val out = nativeOutputDirectory.value
+}
- val configure = Process(
- "./configure " +
- "--prefix=" + out.getAbsolutePath + " " +
- "--libdir=" + out.getAbsolutePath + " " +
- "--disable-versioned-lib", //Disable producing versioned library files, not needed for fat jars.
- build)
+object NativeKeys {
- val make = Process("make", build)
+ val Native = config("native")
- val makeInstall = Process("make install", build)
+ val platform = settingKey[Platform]("Platform of the system this build is being run on.")
- val ev = configure #&& make #&& makeInstall ! log
- if (ev != 0)
- throw new RuntimeException(s"Building native library failed. Exit code: ${ev}")
+ //fat jar settings
+ val libraryPrefix = settingKey[String]("A string to be prepended to native products when packaged.")
+ val libraryManifest = settingKey[String]("Name of a file that will contain a list of all native products.")
+ val libraryResourceDirectory = settingKey[File](
+ "Directory that contains native products when they treated as resources."
+ )
- (out ** ("*.la")).get.foreach(_.delete())
+}
- out
+/** Provides implementations of wrapper tasks suitable for projects using Autotools */
+object Autotools {
+ import NativeKeys._
+ import sbt.Def.Initialize
+
+ private val clean: Initialize[Task[Unit]] = Def.task {
+ val log = streams.value.log
+ val src = (sourceDirectory in Native).value
+
+ Process("make distclean", src) #|| Process("make clean", src) ! log
+ }
+
+ private val lib: Initialize[Task[File]] = Def.task {
+ val log = streams.value.log
+ val src = (sourceDirectory in Native).value
+ val out = (target in Native).value
+ val outPath = out.getAbsolutePath
+
+ val configure = if ((src / "config.status").exists) {
+ Process("./config.status", src)
+ } else {
+ Process(
+ //Disable producing versioned library files, not needed for fat jars.
+ s"./configure --prefix=$outPath --libdir=$outPath --disable-versioned-lib",
+ src
+ )
}
- val nativePackageMappings = Def.task {
- val managedDir = nativeTargetDirectory.value
- val unmanagedDir = nativePackageUnmanagedDirectory.value
+ val make = Process("make", src)
- val managed = (nativeBuild.value ** "*").get
- val unmanaged = (unmanagedDir ** "*").get
+ val makeInstall = Process("make install", src)
- val managedMappings: Seq[(File, String)] = for (file <- managed; if file.isFile) yield {
- file -> ("native/" + (file relativeTo managedDir).get.getPath)
- }
+ val ev = configure #&& make #&& makeInstall ! log
+ if (ev != 0)
+ throw new RuntimeException(s"Building native library failed. Exit code: ${ev}")
- val unmanagedMappings: Seq[(File, String)] = for (file <- unmanaged; if file.isFile) yield {
- file -> ("native/" + (file relativeTo unmanagedDir).get.getPath)
- }
+ val products: List[File] = (out ** ("*" -- "*.la")).get.filter(_.isFile).toList
- managedMappings ++ unmanagedMappings
+ //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
}
+ }
+
+ val settings: Seq[Setting[_]] = Seq(
+ Keys.clean in Native := Autotools.clean.value,
+ Keys.compile in Native := {
+ lib.value
+ sbt.inc.Analysis.Empty
+ },
+ Keys.packageBin in Native := {
+ lib.value
+ }
+ )
+}
- def os = System.getProperty("os.name").toLowerCase.filter(c => !c.isWhitespace)
- def arch = System.getProperty("os.arch").toLowerCase
-
- val settings: Seq[Setting[_]] = Seq(
- nativeTargetDirectory := target.value / "native",
- nativeOutputDirectory := nativeTargetDirectory.value / (os + "-" + arch),
- nativeClean := autoClean.value,
- nativeBuild := autoLib.value,
- nativePackageUnmanagedDirectory := baseDirectory.value / "lib_native",
- mappings in (Compile, packageBin) ++= nativePackageMappings.value
- )
+object NativeDefaults {
+ import NativeKeys._
+
+ /** Copy native product to resource directory and create manifest */
+ private val libraryResources = Def.task {
+ val out = (libraryResourceDirectory in Compile).value
+
+ val product = (packageBin in Native).value
+
+ val productResource = out / product.name
+ val manifestResource = out / (libraryManifest in Compile).value
+
+ IO.copyFile(product, productResource)
+ IO.write(manifestResource, productResource.name)
+
+ Seq(productResource, manifestResource)
+ }
+
+ private val fatJarSettings = Seq(
+ libraryPrefix in Compile := "",
+ libraryManifest in Compile := "library",
+ libraryResourceDirectory in Compile := (resourceManaged in Compile).value /
+ (libraryPrefix in Compile).value / (platform in Native).value.id,
+ unmanagedResourceDirectories in Compile += (baseDirectory).value / "lib_native",
+ resourceGenerators in Compile += libraryResources.taskValue
+ )
+
+ val settings: Seq[Setting[_]] = Seq(
+ platform in Native := Platform.uname.getOrElse {
+ System.err.println("Warning: Cannot determine platform! It will be set to 'unknown'.")
+ Platform("unknown", "unknown")
+ },
+ target in Native := target.value / "native" / (platform in Native).value.id
+ ) ++ fatJarSettings ++ Autotools.settings
}