Skip to content

Integrating JniGen

gershnik edited this page Sep 30, 2019 · 14 revisions

On the most basic level to use JniGen you need to

  1. Configure its annotation processor to run. This can be either during Java compilation or separately but must happen before C++ compilation
  2. Use its annotations in your Java code
  3. Include generated headers in your C++ code

Maven packages

  • Repository: JCenter

  • Annotation processor
    Group Id: smjni.jnigen
    Artifact Id: processor
    Version: corresponds to releases on GitHub

  • Annotations
    Group Id: smjni.jnigen
    Artifact Id: annotations
    Version: corresponds to releases on GitHub

Configuring annotation processor

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.

Android

repositories {
    jcenter()
}

dependencies {
    //JNI annotations
    compileOnly("smjni.jnigen:annotations:2.0")
    //JNI code generator
    annotationProcessor("smjni.jnigen:processor:2.0@jar")
}

//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 JNIGEN_GENERATED_PATH = "src/main/cpp/generated"
def JNIGEN_OUTPUT_LIST_NAME = "outputs.txt"

android {

    //Use libraryVariants if you are building a library
    applicationVariants.all {
        //This tells Gradle about outputs from annotation processor so it knows to run compilation
        //if it is missing
        javaCompileProvider.get().outputs.file("$JNIGEN_GENERATED_PATH/$JNIGEN_OUTPUT_LIST_NAME")
    }

    javaCompileOptions {
        annotationProcessorOptions {
            arguments = [
                    "smjni.jnigen.dest.path" : file(JNIGEN_GENERATED_PATH).path,
                    "smjni.jnigen.output.list.name": JNIGEN_OUTPUT_LIST_NAME,
                    "smjni.jnigen.expose.extra": [
                            //Put here names of all the system classes your JNI code needs access to 
                            "android.graphics.Bitmap"
                    ].join(";").toString()
            ]
        }
    }
}

//This task will remove any stale headers (left from classes that are no longer exposed)
//Without it your C++ code can continue to include them with various bad results
task cleanStaleJNIHeaders {

    inputs.files file("$JNIGEN_GENERATED_PATH/$JNIGEN_OUTPUT_LIST_NAME")

    doLast {
        def generated = new HashSet<String>()
        file("$JNIGEN_GENERATED_PATH/$JNIGEN_OUTPUT_LIST_NAME").eachLine { line ->
            generated.add(line)
        }
        def existing = project.fileTree(dir: file("$JNIGEN_GENERATED_PATH"), include: "*.h").files
        def toDelete = []
        for (file in existing) {
            if (!generated.contains(file.name))
                toDelete.add(file)
        }

        project.delete { delete toDelete }
    }
}

tasks.whenTaskAdded { theTask ->

    //Making cleanStaleJNIHeaders run before native build
    if (theTask.name.startsWith("externalNativeBuild")) {
            theTask.dependsOn cleanStaleJNIHeaders
    }

}

Plain Java

repositories {
    jcenter()
}

dependencies {
    compileOnly 'smjni.jnigen:annotations:2.0'
    annotationProcessor("smjni.jnigen:processor:2.0@jar") {
        transitive true
}

//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 JNIGEN_GENERATED_PATH = "src/main/cpp/generated"
def JNIGEN_OUTPUT_LIST_NAME = "outputs.txt"

tasks.withType(JavaCompile) {
    options.compilerArgs = [
                "-Asmjni.jnigen.dest.path=" + file(JNIGEN_GENERATED_PATH).path,
                "-Asmjni.jnigen.output.list.name=" + JNIGEN_OUTPUT_LIST_NAME,
                "-Asmjni.jnigen.expose.extra=" + [
                       //Put here names of all the system classes your JNI code needs access to
                       "java.lang.AssertionError"
                ].join(";").toString()
        ]
    outputs.file("$JNIGEN_GENERATED_PATH/$JNIGEN_OUTPUT_LIST_NAME")
}

//This task will remove any stale headers (left from classes that are no longer exposed)
//Without it your C++ code can continue to include them with various bad results
task cleanStaleJNIHeaders(dependsOn: compileJava) {

    inputs.files file("$JNIGEN_GENERATED_PATH/$JNIGEN_OUTPUT_LIST_NAME")

    doLast {
        def generated = new HashSet<String>()
        def outputs = file("$JNIGEN_GENERATED_PATH/$JNIGEN_OUTPUT_LIST_NAME")
        if (outputs.exists()) {
            outputs.eachLine { line ->
                generated.add(line)
            }
        }
        def existing = project.fileTree(dir: file("$JNIGEN_GENERATED_PATH"), include: "*.h").files
        def toDelete = []
        for (file in existing) {
            if (!generated.contains(file.name))
                toDelete.add(file)
        }

        project.delete { delete toDelete }
    }
}

task buildNative(dependsOn: cleanStaleJNIHeaders) {
    ... build native code in whatever way you do it ...
}