diff options
Diffstat (limited to 'project/native.scala')
-rw-r--r-- | project/native.scala | 188 |
1 files changed, 128 insertions, 60 deletions
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 } |