Compose a JNI Library from the Source
You can open this sample inside an IDE using the IntelliJ native importer or Eclipse Buildship. |
This sample shows how a Java Native Interface (JNI) library can be composed of multiple projects with Gradle.
In this sample, we are composing a JNI library with components implemented in Java and C++; however, this applies to other JVM and native languages as well.
Project Structure
You can visualize the project structure and layout using the projects
tasks.
$ ./gradlew projects > Task :projects ------------------------------------------------------------ Root project - The JNI library as the consumer would expect. ------------------------------------------------------------ Root project 'jni-library-composing-from-source' - The JNI library as the consumer would expect. +--- Project ':cpp-greeter' - The C++ implementation, has no knowledge of the JVM. +--- Project ':cpp-jni-greeter' - The JNI shared library, also known as the native bindings. +--- Project ':java-jni-greeter' - The JNI classes, also known as the JVM bindings. \--- Project ':java-loader' - The library for loading the shared library from the classpath or JAR. To see a list of the tasks of a project, run gradlew <project-path>:tasks For example, try running gradlew :cpp-greeter:tasks BUILD SUCCESSFUL 1 actionable task: 1 executed
The JNI library, as the root project, is composed of four projects. The following subsection will go through each one of them.
C++ Library
This library is a pure C++ library. It has no dependency or knowledge of the JVM.
plugins {
id 'cpp-library'
}
description = 'The C++ implementation, has no knowledge of the JVM.'
library {
// Note: it is possible to use a shared library.
// However you will need to write a loader aware of the multiple shared libraries.
linkage = [Linkage.STATIC]
binaries.configureEach {
compileTask.get().positionIndependentCode = true
}
}
plugins {
id("cpp-library")
}
description = "The C++ implementation, has no knowledge of the JVM."
library {
// Note: it is possible to use a shared library.
// However you will need to write a loader aware of the multiple shared libraries.
linkage.set(listOf(Linkage.STATIC))
binaries.configureEach {
compileTask.get().setPositionIndependentCode(true)
}
}
It could be built by another build system altogether. In this case, we are using the Gradle C++ library core plugin.
Java/C++ JNI Bindings
The JNI bindings are split into two separate projects. Each project is fulfilling two sides of the same coin, the JNI bindings on the JVM and the native side, respectively.
The JVM JNI binding project defines the classes with methods marked with the native
keyword.
We are using the Gradle Java library core plugin.
plugins {
id 'java-library'
}
description = 'The JNI classes, also known as the JVM bindings.'
dependencies {
implementation project(':java-loader')
}
plugins {
id("java-library")
}
description = "The JNI classes, also known as the JVM bindings."
dependencies {
implementation(project(":java-loader"))
}
The native JNI binding project is coupled to the JVM.
It uses JVM types provided by the jni.h
header.
In this sample, we are assuming the user would have generated the JNI header manually to be used within the project.
It is considered bad practice to generate the JNI headers manually.
The JNI Library Plugin will automatically generate the JNI headers and wire them properly to the native compilation task when both JNI binding project is inside the same project.
See the Java/C++ JNI library sample for a demonstration of this feature.
We are using the Gradle C++ library core plugin.
plugins {
id 'cpp-library'
}
description = 'The JNI shared library, also known as the native bindings.'
import org.gradle.internal.jvm.Jvm
library {
// The native component of the JNI library needs to be a shared library.
linkage = [Linkage.SHARED]
dependencies {
implementation project(':cpp-greeter')
}
binaries.configureEach {
compileTask.get().getIncludes().from(compileTask.get().targetPlatform.map {
def result = [new File("${Jvm.current().javaHome.absolutePath}/include")]
if (it.operatingSystem.macOsX) {
result.add(new File("${Jvm.current().javaHome.absolutePath}/include/darwin"))
} else if (it.operatingSystem.linux) {
result.add(new File("${Jvm.current().javaHome.absolutePath}/include/linux"))
} else if (it.operatingSystem.windows) {
result.add(new File("${Jvm.current().javaHome.absolutePath}/include/win32"))
}
return result
})
compileTask.get().positionIndependentCode = true
}
}
import org.gradle.internal.jvm.Jvm
plugins {
id("cpp-library")
}
description = "The JNI shared library, also known as the native bindings."
library {
// The native component of the JNI library needs to be a shared library.
linkage.set(listOf(Linkage.SHARED))
dependencies {
implementation(project(":cpp-greeter"))
}
binaries.configureEach {
compileTask.get().includes.from(compileTask.get().targetPlatform.map {
listOf(File("${Jvm.current().javaHome.canonicalPath}/include")) + when {
it.operatingSystem.isMacOsX -> listOf(File("${Jvm.current().javaHome.absolutePath}/include/darwin"))
it.operatingSystem.isLinux -> listOf(File("${Jvm.current().javaHome.absolutePath}/include/linux"))
it.operatingSystem.isWindows -> listOf(File("${Jvm.current().javaHome.absolutePath}/include/win32"))
else -> emptyList()
}
})
compileTask.get().setPositionIndependentCode(true)
}
}
The samples use Jvm class, which is an internal type to Gradle.
This type was used as conveniences and demonstration purposes.
It should not be used in production.
|
Java Library
This library is a pure Java library supporting the JNI binding on the JVM side. It could be any API or implementation dependencies.
plugins {
id 'java-library'
}
description = 'The library for loading the shared library from the classpath or JAR.'
plugins {
id("java-library")
}
description = "The library for loading the shared library from the classpath or JAR."
Assembling the JNI Library
To build the library:
$ ./gradlew assemble BUILD SUCCESSFUL 9 actionable tasks: 9 executed
All the native runtime dependencies will be packaged inside the generated JAR:
$ jar tf ./build/libs/jni-library-composing-from-source.jar META-INF/ META-INF/MANIFEST.MF libcpp-jni-greeter.dylib
The JNI JAR is also registered as an outgoing variant:
$ ./gradlew outgoingVariants > Task :outgoingVariants -------------------------------------------------- Variant apiElements -------------------------------------------------- Description = API elements for main component. Capabilities - :jni-library-composing-from-source:unspecified (default capability) Attributes - org.gradle.libraryelements = jar - org.gradle.usage = java-api -------------------------------------------------- Variant runtimeElements -------------------------------------------------- Description = Runtime elements for main component. Capabilities - :jni-library-composing-from-source:unspecified (default capability) Attributes - org.gradle.libraryelements = jar - org.gradle.usage = java-runtime Artifacts - build/libs/jni-library-composing-from-source.jar (artifactType = jar) BUILD SUCCESSFUL 1 actionable task: 1 executed
For more information, see JNI Library Plugin and C++ Language Plugin reference chapters.