aboutsummaryrefslogblamecommitdiff
path: root/project/native.scala
blob: b6e4c194f27ffb0796c75fa0c4fc63f4d62dd435 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
              
 



                     
 


                                                                           

 

















                                                                                                  
     
   
 
 
 
                   
 
                               
 
                                                                                           
 





                                                                                                             
 
 
 

























                                                                                      

     
                                   
 
                                                  
 


                                                                                     
 
                                                                                   
 










                                                                        
     












                                                  
 

































                                                                                            


 
package native

import java.io.File
import sbt._
import sbt.Keys._
import scala.util.Try

/** A platform is a 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").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))
      }
    }
  }

}

object NativeKeys {

  val Native = config("native")

  val platform = settingKey[Platform]("Platform of the system this build is being run on.")

  //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."
  )

}

/** 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 make = Process("make", src)

    val makeInstall = Process("make install", src)

    val ev = configure #&& make #&& makeInstall ! log
    if (ev != 0)
      throw new RuntimeException(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
    }
  }

  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
    }
  )
}

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

}