How to build JNI library (with Java and Native parts) with modern Gradle?

I need to build some Java wrappers for native library, via old skool JNI (not JNA!).

Most guides on the Net (and edu.wpi.first.GradleJni plugin) uses deprecated Native plugins and deprecated Model, I don’t want to start new project by using deprecated features.

I can not use java-library (or even java) and cpp-library in same project, they conflict, so I extracted native part to sub-project.

Like this:

build.gradle

plugins {
    id 'java-library'
}

ext.jniHeaders = new File(buildDir, 'generated/jni-headers')

tasks.withType(JavaCompile) {
  options.compilerArgs += [ '-h', project.ext.jniHeaders ];
}

native/build.gradle

plugins {
    id 'cpp-library'
}

def jniHeaders = rootProject.ext.jniHeaders

// Settings for C++ library
library {
  dependencies {
    implementation rootProject
  }

  binaries.configureEach { CppBinary binary ->
    def compileTask = binary.compileTask.get()
    def javaHeaders = "${org.gradle.internal.jvm.Jvm.current().javaHome}/include";

    compileTask.includes.from(javaHeaders)

    def osFamily = binary.targetPlatform.targetMachine.operatingSystemFamily
    if (osFamily.macOs) {
      compileTask.includes.from(javaHeaders + '/darwin')
    } else if (osFamily.linux) {
      compileTask.includes.from(javaHeaders + '/linux')
    } else if (osFamily.windows) {
      compileTask.includes.from(javaHeaders + '/win32')
    } else {
      // How should I support FreeBSD?!
      logger.warn('Unknown osFamily: ' + osFamily.name + ', consider extending build script');
      compileTask.includes.from(javaHeaders + '/' + osFamily.name.toLowerCase())
    }
    compileTask.includes.from(jniHeaders.absolutePath)
  }
}

Sub-project (:native) logically depends on Java library project, as JNI headers are generated by it. But gradle refuses to support this dependency:


FAILURE: Build failed with an exception.

* What went wrong:
Could not determine the dependencies of task ':native:linkDebug'.
> Could not resolve all task dependencies for configuration ':native:nativeLinkDebug'.
   > Could not resolve project <redacted>.
     Required by:
         project :native
      > No matching variant of project <redacted> was found. The consumer was configured to find attribute 'org.gradle.usage' with value 'native-link', attribute 'org.gradle.native.debuggable' with value 'true', attribute 'org.gradle.native.optimized' with value 'false', attribute 'org.gradle.native.operatingSystem' with value 'windows', attribute 'org.gradle.native.architecture' with value 'x86-64' but:
          - Variant 'apiElements' capability <redacted>:unspecified:
              - Incompatible because this component declares attribute 'org.gradle.usage' with value 'java-api' and the consumer needed attribute 'org.gradle.usage' with value 'native-link'
              - Other compatible attributes:
                  - Doesn't say anything about org.gradle.native.architecture (required 'x86-64')
                  - Doesn't say anything about org.gradle.native.debuggable (required 'true')
                  - Doesn't say anything about org.gradle.native.operatingSystem (required 'windows')
                  - Doesn't say anything about org.gradle.native.optimized (required 'false')
          - Variant 'runtimeElements' capability <redacted>:unspecified:
              - Incompatible because this component declares attribute 'org.gradle.usage' with value 'java-runtime' and the consumer needed attribute 'org.gradle.usage' with value 'native-link'
              - Other compatible attributes:
                  - Doesn't say anything about org.gradle.native.architecture (required 'x86-64')
                  - Doesn't say anything about org.gradle.native.debuggable (required 'true')
                  - Doesn't say anything about org.gradle.native.operatingSystem (required 'windows')
                  - Doesn't say anything about org.gradle.native.optimized (required 'false')

I’ve tried cppCompileRelease / cppCompileDebug dependency type, but they are not recognized at all.

What is «modern» way to build JNI libraries with Gradle?

Additional question is, how other projects should depend on such library? It looks natural to depend on root project (java-library) as native library is implementation detail. But then native library will not be in dependency graph and could be omitted. And Java part of library can not depend on Native part, as it creates circular dependency.

2 Likes