aboutsummaryrefslogtreecommitdiff
path: root/project/native.scala
blob: 5fb87ce90efb7cdf54dff9b38aa31776b2aa04fa (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
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."
  )

}

//windows, as usual, needs special treatment
object CygwinUtil {

  def onCygwin: Boolean = {
    val uname = Process("uname").lines.headOption
    uname map {
      _.toLowerCase.startsWith("cygwin")
    } getOrElse {
      false
    }
  }

  def toUnixPath(path: String): String = if (onCygwin) {
    Process(s"cygpath ${path}").lines.head
  } else {
    path
  }
  
}

/** 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 = CygwinUtil.toUnixPath(out.getAbsolutePath)

    val configure = if ((src / "config.status").exists) {
      Process("sh ./config.status", src)
    } else {
      Process(
        //Disable producing versioned library files, not needed for fat jars.
        s"sh ./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

}