Skip to content

Commit

Permalink
Add support of multiple native outputs (#186)
Browse files Browse the repository at this point in the history
* Add support of multiple native outputs

* Make the new behavior configurable and compatible with the previous release so there is no implicit behavior change
  • Loading branch information
pomadchin authored Dec 2, 2023
1 parent a2677b8 commit 035b96e
Show file tree
Hide file tree
Showing 8 changed files with 61 additions and 52 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,12 +153,18 @@ Since this plugin is basically a command-line wrapper, native build tools must f

An initial, compatible build template can be obtained by running `sbt nativeInit <tool>`. Once the native build tool initialised, projects are built by calling the `sbt nativeCompile` task.

Source and output directories are configurable
Source and output directories are configurable:
```scala
nativeCompile / sourceDirectory := sourceDirectory.value / "native"
nativeCompile / target := target.value / "native" / nativePlatform.value
```

Some JNI projects may produce more than a single output. If that's an intended behavior it's possible to include all of the produced
binaries into the native package:
```scala
nativeMultipleOutputs := true
```

#### CMake

A regular `CMake` native project definition usually looks this following way:
Expand Down
31 changes: 29 additions & 2 deletions plugin/src/main/scala/com/github/sbt/jni/build/BuildTool.scala
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,42 @@ trait BuildTool {
*/
def library(
targetDirectory: File
): File
): List[File]

}

/**
* Get an instance (build configuration) of this tool, in the specified directory.
*/
def getInstance(baseDirectory: File, buildDirectory: File, logger: Logger): Instance
def getInstance(baseDirectory: File, buildDirectory: File, logger: Logger, multipleOutputs: Boolean): Instance

/**
* At least one produced library is expected.
*/
def validate(list: List[File], multipleOutputs: Boolean, logger: Logger): List[File] = {
list match {
case Nil =>
sys.error(
s"No files were created during compilation, " +
s"something went wrong with the $name configuration."
)
case list @ _ :: Nil =>
list

case head :: _ if !multipleOutputs =>
logger.warn(
s"""
|More than one file was created during compilation, only the first one (${head.getAbsolutePath}) will be used.
|Consider setting nativeMultipleOutputs := true.
|""".stripMargin
)
List(head)

case list =>
logger.info("More than one file was created during compilation.")
list
}
}
}

object BuildTool {
Expand Down
3 changes: 2 additions & 1 deletion plugin/src/main/scala/com/github/sbt/jni/build/CMake.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ class CMake(protected val configuration: Seq[String]) extends BuildTool with Con
"/com/github/sbt/jni/templates/CMakeLists.txt" -> "CMakeLists.txt"
)

override def getInstance(baseDir: File, buildDir: File, logger: Logger) = new Instance {
override def getInstance(baseDir: File, buildDir: File, logger: Logger, nativeMultipleOutputs: Boolean) = new Instance {

override def log = logger
override def baseDirectory = baseDir
override def buildDirectory = buildDir
override def multipleOutputs = nativeMultipleOutputs

def cmakeProcess(args: String*): ProcessBuilder = Process("cmake" +: args, buildDirectory)

Expand Down
25 changes: 5 additions & 20 deletions plugin/src/main/scala/com/github/sbt/jni/build/Cargo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ class Cargo(protected val configuration: Seq[String]) extends BuildTool {

def release: Boolean = configuration.exists(_.toLowerCase.contains("release"))

def getInstance(baseDirectory: File, buildDirectory: File, logger: sbt.Logger): Instance =
new Instance(baseDirectory, logger)
def getInstance(baseDirectory: File, buildDirectory: File, logger: sbt.Logger, multipleOutputs: Boolean): Instance =
new Instance(baseDirectory, logger, multipleOutputs)

class Instance(protected val baseDirectory: File, protected val logger: sbt.Logger) extends super.Instance {
class Instance(protected val baseDirectory: File, protected val logger: sbt.Logger, protected val multipleOutputs: Boolean) extends super.Instance {
// IntelliJ friendly logger, IntelliJ doesn't start tests if a line is printed as "error", which Cargo does for regular output
protected val log: ProcessLogger = new ProcessLogger {
def out(s: => String): Unit = logger.info(s)
Expand All @@ -31,7 +31,7 @@ class Cargo(protected val configuration: Seq[String]) extends BuildTool {

def clean(): Unit = Process("cargo clean", baseDirectory) ! log

def library(targetDirectory: File): File = {
def library(targetDirectory: File): List[File] = {
val configurationString = (configuration ++ Seq("--target-dir", targetDirectory.getAbsolutePath)).mkString(" ").trim
val ev =
Process(
Expand All @@ -44,22 +44,7 @@ class Cargo(protected val configuration: Seq[String]) extends BuildTool {
val products: List[File] =
(targetDirectory / subdir * ("*.so" | "*.dylib")).get.filter(_.isFile).toList

// only one produced library is expected
products match {
case Nil =>
sys.error(
s"No files were created during compilation, " +
s"something went wrong with the $name configuration."
)
case head :: Nil =>
head
case head :: _ =>
logger.warn(
"More than one file was created during compilation, " +
s"only the first one (${head.getAbsolutePath}) will be used."
)
head
}
validate(products, multipleOutputs, logger)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ trait ConfigureMakeInstall { self: BuildTool =>
trait Instance extends self.Instance {

def log: Logger
def multipleOutputs: Boolean
def baseDirectory: File
def buildDirectory: File

Expand All @@ -25,7 +26,7 @@ trait ConfigureMakeInstall { self: BuildTool =>

def library(
targetDirectory: File
): File = {
): List[File] = {

val ev: Int = (
configure(targetDirectory) #&& make() #&& install()
Expand All @@ -36,22 +37,7 @@ trait ConfigureMakeInstall { self: BuildTool =>
val products: List[File] =
(targetDirectory ** ("*.so" | "*.dylib")).get.filter(_.isFile).toList

// only one produced library is expected
products match {
case Nil =>
sys.error(
s"No files were created during compilation, " +
s"something went wrong with the ${name} 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
}
validate(products, multipleOutputs, log)
}
}

Expand Down
5 changes: 3 additions & 2 deletions plugin/src/main/scala/com/github/sbt/jni/build/Meson.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@ class Meson(protected val configuration: Seq[String]) extends BuildTool with Con
"/com/github/sbt/jni/templates/meson.options" -> "meson.options"
)

override def getInstance(baseDir: File, buildDir: File, logger: Logger) = new Instance {
override def getInstance(baseDir: File, buildDir: File, logger: Logger, nativeMultipleOutputs: Boolean) = new Instance {

override def log = logger
override def baseDirectory = baseDir
override def buildDirectory = buildDir
override def multipleOutputs = nativeMultipleOutputs

def mesonProcess(args: String*): ProcessBuilder = Process("meson" +: args, buildDirectory)

Expand All @@ -30,7 +31,7 @@ class Meson(protected val configuration: Seq[String]) extends BuildTool with Con
override def configure(target: File) = {
mesonProcess(
Seq("setup", "--prefix", target.getAbsolutePath) ++ configuration ++ Seq(
mesonVersion.toString,
mesonVersion,
baseDirectory.getAbsolutePath
): _*
)
Expand Down
17 changes: 11 additions & 6 deletions plugin/src/main/scala/com/github/sbt/jni/plugins/JniNative.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ object JniNative extends AutoPlugin {
object autoImport {

// Main task, inspect this first
val nativeCompile = taskKey[File](
val nativeCompile = taskKey[Seq[File]](
"Builds a native library by calling the native build tool."
)

Expand All @@ -30,6 +30,10 @@ object JniNative extends AutoPlugin {
"Initialize a native build script from a template."
)

val nativeMultipleOutputs = taskKey[Boolean](
"Enable multiple native outputs support. Disabled by default."
)

}
import autoImport._

Expand Down Expand Up @@ -79,8 +83,8 @@ object JniNative extends AutoPlugin {
tools.map(_.name).mkString(",")
)
)

},
nativeMultipleOutputs := false,
nativeBuildToolInstance := {
val tool = nativeBuildTool.value
val srcDir = (nativeCompile / sourceDirectory).value
Expand All @@ -89,7 +93,8 @@ object JniNative extends AutoPlugin {
tool.getInstance(
baseDirectory = srcDir,
buildDirectory = buildDir,
logger = streams.value.log
logger = streams.value.log,
multipleOutputs = nativeMultipleOutputs.value
)
},
nativeCompile / clean := {
Expand All @@ -114,9 +119,9 @@ object JniNative extends AutoPlugin {
IO.createDirectory(targetDir)

log.info(s"Building library with native build tool ${tool.name}")
val lib = toolInstance.library(targetDir)
log.success(s"Library built in ${lib.getAbsolutePath}")
lib
val libs = toolInstance.library(targetDir)
log.success(s"Libraries built in ${libs.map(_.getAbsolutePath).mkString(", ")}")
libs
},

// also clean native sources
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,8 @@ object JniPackage extends AutoPlugin {
.taskDyn[Seq[(File, String)]] {
val enableManaged = enableNativeCompilation.value
if (enableManaged) Def.task {
val library: File = nativeCompile.value
val platform = nativePlatform.value

Seq(library -> s"/native/$platform/${library.name}")
nativeCompile.value.map { library => library -> s"/native/$platform/${library.name}" }
}
else
Def.task {
Expand Down

0 comments on commit 035b96e

Please sign in to comment.