-
Notifications
You must be signed in to change notification settings - Fork 6
Integrating JniGen
Eugene Gershnik edited this page Apr 19, 2023
·
14 revisions
On the most basic level to use JniGen you need to
- Configure its annotation processor to run. This can be either during Java or Kotlin compilation or separately but must happen before C++ compilation
- Use its annotations in your Java code
- Include generated headers in your C++ code
- Repository: Maven Central
-
Group Id:
io.github.gershnik
- Version: corresponds to Releases on GitHub
-
Annotations Artifact Id:
smjni-jnigen-annotations
-
Java/KAPT Annotation processor Artifact Id:
smjni-jnigen-processor
-
KSP Annotation processor Artifact Id:
smjni-jnigen-kprocessor
The instructions below are for Gradle. If you use something else to build your Java code you will need to figure out the equivalent steps.
Code (Groovy)
For a complete example see build.gradle in samples/android/java
repositories {
google()
mavenCentral()
}
dependencies {
//JNI annotations
compileOnly("io.github.gershnik:smjni-jnigen-annotations:3.7")
//JNI code generator
annotationProcessor("io.github.gershnik:smjni-jnigen-processor:3.7")
}
//JniGen settings
def jniGenProps = new Object() {
//Where to put the generated files
//Make sure there is nothing else in that folder (it shouldn't even exist).
//This will allow removal of stale files
def generatedPath = file("src/main/cpp/generated").absolutePath
//Name of the file listing all other generated files
def outputListName = "outputs.txt"
//Additional classes to expose
def additionalClasses = ["java.lang.Byte"]
}
android {
//Pass options for JniGen
javaCompileOptions {
annotationProcessorOptions {
arguments = [
"smjni.jnigen.dest.path" : file(jniGenProps.generatedPath).path,
"smjni.jnigen.own.dest.path" : "true",
"smjni.jnigen.output.list.name": jniGenProps.outputListName,
"smjni.jnigen.expose.extra" : jniGenProps.additionalClasses.join(";").toString()
]
}
}
}
//This makes Gradle rebuild Java compilation (and so run annotation processor)
//when any of the generated files are missing
//Use libraryVariants if you are building a library
android.applicationVariants.all { variant ->
variant.javaCompileProvider.get().outputs.upToDateWhen {
def jniGenOutputList = file("${jniGenProps.generatedPath}/${jniGenProps.outputListName}")
if (!jniGenOutputList.exists()) {
return false
}
for(line in jniGenOutputList) {
if (!file("${jniGenProps.generatedPath}/$line").exists()) {
return false
}
}
return true
}
}
//Clean generated headers on project clean
task cleanJNIHeaders(type: Delete) {
delete file("${jniGenProps.generatedPath}")
}
clean.dependsOn cleanJNIHeaders
//Make Java compilation (and JniGen code generation) run before CMake build
tasks.whenTaskAdded { theTask ->
def match = theTask.name =~ ~/^buildCMake(.*)$/
if (match) {
def config
switch(match.group(1)) {
case "RelWithDebInfo": config = "Release"; break
default: config = match.group(1); break
}
theTask.dependsOn "compile${config}JavaWithJavac"
}
}
Code (Groovy)
For a complete example see build.gradle in samples/android/kotlin-kapt
repositories {
google()
mavenCentral()
}
dependencies {
//JNI annotations
compileOnly("io.github.gershnik:smjni-jnigen-annotations:3.7")
//JNI code generator
kapt("io.github.gershnik:smjni-jnigen-processor:3.7")
}
//JniGen settings
def jniGenProps = new Object() {
//Where to put the generated files
//Make sure there is nothing else in that folder (it shouldn't even exist).
//This will allow removal of stale files
def generatedPath = "src/main/cpp/generated"
//Name of the file listing all other generated files
def outputListName = "outputs.txt"
//Additional classes to expose
def additionalClasses = ["java.lang.Byte"]
}
//Pass options for JniGen via KAPT
kapt {
useBuildCache = false
arguments {
arg("smjni.jnigen.dest.path", jniGenProps.generatedPath)
arg("smjni.jnigen.own.dest.path", "true")
arg("smjni.jnigen.output.list.name", jniGenProps.outputListName)
arg("smjni.jnigen.expose.extra", jniGenProps.additionalClasses.join(";").toString())
}
}
//This makes Gradle rebuild Kotlin compilation (and so run annotation processor)
//when any of the generated files are missing
//Note: use org.jetbrains.kotlin.gradle.tasks.KotlinCompile instead of
// org.jetbrains.kotlin.gradle.internal.KaptWithoutKotlincTask for older
// versions of kotlin-gradle-plugin plugin
tasks.withType(org.jetbrains.kotlin.gradle.internal.KaptWithoutKotlincTask).all {
outputs.upToDateWhen {
def jniGenOutputList = file("${jniGenProps.generatedPath}/${jniGenProps.outputListName}")
if (!jniGenOutputList.exists()) {
return false
}
for(line in jniGenOutputList) {
if (!file("${jniGenProps.generatedPath}/$line").exists()) {
return false
}
}
return true
}
}
//Clean generated headers on project clean
task cleanJNIHeaders(type: Delete) {
delete file("${jniGenProps.generatedPath}")
}
clean.dependsOn cleanJNIHeaders
//Make KAPT (and so JniGen code generation) run before CMake build
tasks.whenTaskAdded { theTask ->
def match = theTask.name =~ ~/^buildCMake(.*)$/
if (match) {
def config
switch(match.group(1)) {
case "RelWithDebInfo": config = "Release"; break
default: config = match.group(1); break
}
theTask.dependsOn "kapt${config}Kotlin"
}
}
Groovy
For a complete example see build.gradle in samples/android/kotlin-ksp
repositories {
google()
mavenCentral()
}
dependencies {
//JNI annotations
compileOnly("io.github.gershnik:smjni-jnigen-annotations:3.7")
//JNI code generator
ksp("io.github.gershnik:smjni-jnigen-kprocessor:3.7")
}
//JniGen settings
def jniGenProps = new Object() {
//Where to put the generated files
//Make sure there is nothing else in that folder (it shouldn't even exist).
//This will allow removal of stale files
def generatedPath = file("src/main/cpp/generated").absolutePath
//Name of the file listing all other generated files
def outputListName = "outputs.txt"
//Additional classes to expose
def additionalClasses = ["java.lang.Byte"]
}
//Pass options for JniGen via KSP
ksp {
arg("smjni.jnigen.dest.path", jniGenProps.generatedPath)
arg("smjni.jnigen.own.dest.path", "true")
arg("smjni.jnigen.output.list.name", jniGenProps.outputListName)
arg("smjni.jnigen.expose.extra", jniGenProps.additionalClasses.join(";").toString())
}
//This makes Gradle rebuild Kotlin compilation (and so run annotation processor)
//when any of the generated files are missing
//Note: use org.jetbrains.kotlin.gradle.tasks.KotlinCompile instead of
// com.google.devtools.ksp.gradle.KspTaskJvm for older
// versions of kotlin-gradle-plugin
tasks.withType(com.google.devtools.ksp.gradle.KspTaskJvm).all {
outputs.upToDateWhen {
def jniGenOutputList = file("${jniGenProps.generatedPath}/${jniGenProps.outputListName}")
if (!jniGenOutputList.exists()) {
return false
}
for(line in jniGenOutputList) {
if (!file("${jniGenProps.generatedPath}/$line").exists()) {
return false
}
}
return true
}
}
//Clean generated headers on project clean
task cleanJNIHeaders(type: Delete) {
delete file("${jniGenProps.generatedPath}")
}
clean.dependsOn cleanJNIHeaders
//Make KSP (and so JniGen code generation) run before CMake build
tasks.whenTaskAdded { theTask ->
def match = theTask.name =~ ~/^buildCMake(.*)$/
if (match) {
def config
switch(match.group(1)) {
case "RelWithDebInfo": config = "Release"; break
default: config = match.group(1); break
}
theTask.dependsOn "ksp${config}Kotlin"
}
}
Kotlin
For a complete example see build.gradle.kts in samples/android/kotlin-ksp-kts
repositories {
google()
mavenCentral()
}
dependencies {
//JNI annotations
compileOnly("io.github.gershnik:smjni-jnigen-annotations:3.7")
//JNI code generator
ksp("io.github.gershnik:smjni-jnigen-kprocessor:3.7")
}
//JniGen settings
class JniGenProps{
//Where to put the generated files
//Make sure there is nothing else in that folder (it shouldn't even exist).
//This will allow removal of stale files
val generatedPath: String = file("src/main/cpp/generated").absolutePath
//Name of the file listing all other generated files
val outputListName = "outputs.txt"
//Additional classes to expose
val additionalClasses = arrayOf("java.lang.Byte")
}
val jniGenProps = JniGenProps()
//Pass options for JniGen via KSP
ksp {
arg("smjni.jnigen.dest.path", jniGenProps.generatedPath)
arg("smjni.jnigen.own.dest.path", "true")
arg("smjni.jnigen.output.list.name", jniGenProps.outputListName)
arg("smjni.jnigen.expose.extra", jniGenProps.additionalClasses.joinToString(";"))
}
//This makes Gradle rebuild Kotlin compilation (and so run annotation processor)
//when any of the generated files are missing
//Note: use org.jetbrains.kotlin.gradle.tasks.KotlinCompile instead of
// com.google.devtools.ksp.gradle.KspTaskJvm for older
// versions of kotlin-gradle-plugin
tasks.withType<com.google.devtools.ksp.gradle.KspTaskJvm> {
outputs.upToDateWhen utd@{
val jniGenOutputList = file("${jniGenProps.generatedPath}/${jniGenProps.outputListName}")
if (!jniGenOutputList.exists()) {
return@utd false
}
for(line in jniGenOutputList.readLines()) {
if (!file("${jniGenProps.generatedPath}/$line").exists()) {
return@utd false
}
}
return@utd true
}
}
//Clean generated headers on project clean
tasks.register<Delete>("cleanJNIHeaders") {
delete(file(jniGenProps.generatedPath))
}
tasks.named("clean") {
dependsOn("cleanJNIHeaders")
}
//Make KSP (and so JniGen code generation) run before CMake build
tasks.whenTaskAdded {
val match = Regex("""^buildCMake([^\[]*).*$""").matchEntire(name)
if (match != null) {
val config = when(match.groupValues[1]) {
"RelWithDebInfo" -> "Release"
else -> match.groupValues[1]
}
dependsOn("ksp${config}Kotlin")
}
}
- Building
-
User's Guide
Declaring Java Types
Accessing Methods and Fields
Representing Java Classes
Implementing Native Methods
Smart References
Error Handling
Obtaining JNIEnv
Initialization
Strings
Arrays
Direct Buffers
Booleans
Sizes -
JniGen Code Generator
Integrating JniGen
Annotations
Processor Options