JNI Library as Uber JAR
You can open this sample inside an IDE using the IntelliJ native importer or Eclipse Buildship. |
This sample shows how to create a single JAR containing the JVM code and all the buildable shared library binaries of a Java Native Interface (JNI) library. The library has no dependencies and targets Windows x64, Linux x86-64 and macOS x86-64.
In this sample, we use a JNI library implemented in Java and C++; however, this applies to other JVM and native languages as well.
JNI libraries producing a single variant, the shared library binary will be included inside the JVM JAR.
The JVM JAR refers to the JAR containing all the JVM classes.
When the library produces multiple variants, Gradle produces an additional, separated, JAR, referred to as the JNI JAR, for each variant. The JNI JAR contains only the shared library binary and a manifest file.
In the event where releasing a single JAR containing both the classes and the shared library binaries for a platform is preferable, it is possible to create an uber JAR.
The following code creates a new JAR task, i.e. uberJar
, that merge the content of the JVM and JNI JARs.
plugins {
id 'java'
id 'dev.nokee.jni-library'
id 'dev.nokee.cpp-language'
}
import dev.nokee.platform.jni.JarBinary
import dev.nokee.platform.jni.JniJarBinary
import dev.nokee.platform.jni.JvmJarBinary
import dev.nokee.platform.nativebase.TargetMachine
library {
targetMachines = [machines.macOS, machines.linux, machines.windows]
}
/**
* Returns a Groovy closure that convert a {@link JniJarBinary} or a {@link JvmJarBinary} to a {@link FileTree}
* representing the JAR content.
*
* @return a transformer of {@link JarBinary} instances to {@link FileTree} of their JAR content.
*/
def asZipTree() {
return { jarBinary ->
jarBinary.jarTask.map { zipTree(it.archiveFile) }
}
}
boolean isHostTargeted(TargetMachine targetMachine) {
String osName = System.getProperty('os.name').toLowerCase().replace(' ', '')
def osFamily = targetMachine.operatingSystemFamily
if (osFamily.windows && osName.contains('windows')) {
return true
} else if (osFamily.linux && osName.contains('linux')) {
return true
} else if (osFamily.macOs && osName.contains('macos')) {
return true
}
return false
}
tasks.register("uberJar", Jar) {
from(library.variants.flatMap { variant ->
def result = []
if (isHostTargeted(variant.targetMachine)) {
result << variant.binaries.withType(JniJarBinary).map(asZipTree())
}
return result
}) { (1)
exclude 'META-INF/**'
}
from(library.binaries.withType(JvmJarBinary).map(asZipTree())) (2)
archiveClassifier = "uber"
}
plugins {
id("java")
id("dev.nokee.jni-library")
id("dev.nokee.cpp-language")
}
import dev.nokee.platform.jni.JarBinary
import dev.nokee.platform.jni.JvmJarBinary
import dev.nokee.platform.jni.JniJarBinary
import dev.nokee.platform.nativebase.TargetMachine
library {
targetMachines.set(listOf(machines.macOS, machines.linux, machines.windows))
}
/**
* Returns a transfomer that convert a list of {@link JniJarBinary} or {@link JvmJarBinary} to a list of {@link FileTree}
* representing the JAR content.
*
* @return a transformer of {@link JarBinary} instances to {@link FileTree} of their JAR content.
*/
fun asZipTree(): (JarBinary) -> Provider<FileTree> {
return { jarBinary: JarBinary ->
jarBinary.jarTask.map { zipTree(it.archiveFile) }
}
}
fun isHostTargeted(targetMachine: TargetMachine): Boolean {
val osName = System.getProperty("os.name").toLowerCase().replace(" ", "")
val osFamily = targetMachine.operatingSystemFamily
if (osFamily.isWindows && osName.contains("windows")) {
return true
} else if (osFamily.isLinux && osName.contains("linux")) {
return true
} else if (osFamily.isMacOs && osName.contains("macos")) {
return true
}
return false
}
tasks.register<Jar>("uberJar") {
from(library.variants.flatMap { variant ->
val result = ArrayList<Provider<List<Provider<FileTree>>>>()
if (isHostTargeted(variant.targetMachine)) {
result.add(variant.binaries.withType(JniJarBinary::class.java).map(asZipTree()))
}
result
}) { (1)
exclude("META-INF/**")
}
from(library.binaries.withType(JvmJarBinary::class.java).map(asZipTree())) (2)
archiveClassifier.set("uber")
}
1 | Filtering all the binaries of the library for JNI JAR binaries. |
2 | Filtering all the binaries of the library for JVM JAR binaries. |
To create the uber JAR:
$ ./gradlew uberJar BUILD SUCCESSFUL 6 actionable tasks: 6 executed
It will create the uber JAR together with the JVM and JNI JARs:
$ ls ./build/libs/*.jar ./build/libs/jni-library-as-uber-jar-macos.jar ./build/libs/jni-library-as-uber-jar-uber.jar ./build/libs/jni-library-as-uber-jar.jar
We can inspect the content of JARs to reveal the intended result:
$ jar tf ./build/libs/jni-library-as-uber-jar-macos.jar META-INF/ META-INF/MANIFEST.MF macos/ macos/libjni-library-as-uber-jar.dylib $ jar tf ./build/libs/jni-library-as-uber-jar.jar META-INF/ META-INF/MANIFEST.MF com/ com/example/ com/example/greeter/ com/example/greeter/NativeLoader.class com/example/greeter/Greeter.class $ jar tf ./build/libs/jni-library-as-uber-jar-uber.jar META-INF/ META-INF/MANIFEST.MF macos/ macos/libjni-library-as-uber-jar.dylib com/ com/example/ com/example/greeter/ com/example/greeter/NativeLoader.class com/example/greeter/Greeter.class
For more information, see JNI Library Plugin reference chapter.