diff --git a/README.md b/README.md index 1ebf088..3609b32 100644 --- a/README.md +++ b/README.md @@ -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 `. 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: diff --git a/plugin/src/main/scala/com/github/sbt/jni/build/BuildTool.scala b/plugin/src/main/scala/com/github/sbt/jni/build/BuildTool.scala index ffc854b..b6c1dbf 100644 --- a/plugin/src/main/scala/com/github/sbt/jni/build/BuildTool.scala +++ b/plugin/src/main/scala/com/github/sbt/jni/build/BuildTool.scala @@ -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 { diff --git a/plugin/src/main/scala/com/github/sbt/jni/build/CMake.scala b/plugin/src/main/scala/com/github/sbt/jni/build/CMake.scala index cfa88e6..fb46276 100644 --- a/plugin/src/main/scala/com/github/sbt/jni/build/CMake.scala +++ b/plugin/src/main/scala/com/github/sbt/jni/build/CMake.scala @@ -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) diff --git a/plugin/src/main/scala/com/github/sbt/jni/build/Cargo.scala b/plugin/src/main/scala/com/github/sbt/jni/build/Cargo.scala index 8def6fa..8084646 100644 --- a/plugin/src/main/scala/com/github/sbt/jni/build/Cargo.scala +++ b/plugin/src/main/scala/com/github/sbt/jni/build/Cargo.scala @@ -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) @@ -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( @@ -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) } } } diff --git a/plugin/src/main/scala/com/github/sbt/jni/build/ConfigureMakeInstall.scala b/plugin/src/main/scala/com/github/sbt/jni/build/ConfigureMakeInstall.scala index b769073..7fa7848 100644 --- a/plugin/src/main/scala/com/github/sbt/jni/build/ConfigureMakeInstall.scala +++ b/plugin/src/main/scala/com/github/sbt/jni/build/ConfigureMakeInstall.scala @@ -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 @@ -25,7 +26,7 @@ trait ConfigureMakeInstall { self: BuildTool => def library( targetDirectory: File - ): File = { + ): List[File] = { val ev: Int = ( configure(targetDirectory) #&& make() #&& install() @@ -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) } } diff --git a/plugin/src/main/scala/com/github/sbt/jni/build/Meson.scala b/plugin/src/main/scala/com/github/sbt/jni/build/Meson.scala index ad16889..7d67099 100644 --- a/plugin/src/main/scala/com/github/sbt/jni/build/Meson.scala +++ b/plugin/src/main/scala/com/github/sbt/jni/build/Meson.scala @@ -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) @@ -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 ): _* ) diff --git a/plugin/src/main/scala/com/github/sbt/jni/plugins/JniNative.scala b/plugin/src/main/scala/com/github/sbt/jni/plugins/JniNative.scala index 533b670..a9106ef 100644 --- a/plugin/src/main/scala/com/github/sbt/jni/plugins/JniNative.scala +++ b/plugin/src/main/scala/com/github/sbt/jni/plugins/JniNative.scala @@ -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." ) @@ -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._ @@ -79,8 +83,8 @@ object JniNative extends AutoPlugin { tools.map(_.name).mkString(",") ) ) - }, + nativeMultipleOutputs := false, nativeBuildToolInstance := { val tool = nativeBuildTool.value val srcDir = (nativeCompile / sourceDirectory).value @@ -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 := { @@ -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 diff --git a/plugin/src/main/scala/com/github/sbt/jni/plugins/JniPackage.scala b/plugin/src/main/scala/com/github/sbt/jni/plugins/JniPackage.scala index ed12943..2f208dc 100644 --- a/plugin/src/main/scala/com/github/sbt/jni/plugins/JniPackage.scala +++ b/plugin/src/main/scala/com/github/sbt/jni/plugins/JniPackage.scala @@ -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 {