gradle-kotlin-dslproject-panamajava-native-libraryjava-ffm

Gradle Java include native library (Project Panama)


I've a rust library that I want to call from Java. I've managed to create the corresponding .h-file and extract from that the Java bindings. As long as my project structure looks like the following, I can access the c-function when compiling and starting the program with

javac --source=22 -d . src/my/panama/java/project/name/*.java
java --enable-native-access=ALL-UNNAMED Main.java
├── Cargo.lock
├── Cargo.toml
├── Main.java
├── my
│   └── panama
│       └── java
│           └── project
│               └── name
│                   ├── projectPanamaFiles.class
├── libmy_rust.dylib
├── my_rust_lib.h
└── src
    ├── my
    │   └── panama
    │       └── java
    │           └── project
    │               └── name
    │                   ├── projectPanamaFiles.java
    ├── lib.rs
    └── custom_functions.rs

Now I want to integrate the native library to my main project (Micronaut 4.4.2 & Java 22) with a structure looking like:

── src
    └── main
        ├── java
        │   └── my
        │       └── panama
        │           └── java
        │               └── project
        │                   └── name
        │                       └── Application.java
        └── rust
            ├── same As above

When I place all of the .class files of the Panama binding in src/main/java/my/Panama/java/project/name/library, I can use them in IntelliJ as every other library.

When I try to run my app, it fails with java.lang.IllegalArgumentException: Cannot open library: libprocess_dlms.dylib. I've tried adding it via gradle.kts via implementation(fileTree("src/main/rust")) but nothing changed. Also adding it directly via System.load("/absolute/path/to/libmy_rust.dylib"); results in the same error message.

Where do I specify the wrong path? Does the file need to be at a specific place? Is there a way of getting the compiler to output where it would've looked for the file?


Solution

  • How the native library is loaded depends on which SymbolLookup variant you used, respectively which jextract library option you chose:

    SymbolLookup method jextract option Behavior
    libraryLookup
    (String name, ...)
    --library <name>
    (this additionally performs a System.mapLibraryName(name) converting the name to the OS-specific library name, e.g. on Linux foo to libfoo.so)
    --library :<path>
    The library is looked up in an OS-specific way.
    For example on Linux it is looked up in the path specified by LD_LIBRARY_PATH.
    libraryLookup
    (Path path, ...)
    none
    (even the :<path> syntax uses libraryLookup(String name, ...) currently)
    The library is loaded from the specified relative or absolute file.
    loaderLookup()
    no library option specified
    or
    --use-system-load-library

    --library <name>
    --use-system-load-library
    (this additionally calls System.loadLibrary(name))
    --library :<path>
    --use-system-load-library
    (this additionally calls System.load(file))
    The library is looked up from all libraries which have been loaded with System.load(file) or System.loadLibrary(name) for the caller's class loader.

    Note that the java.library.path System property affects only System.loadLibrary(name) (respectively Runtime.loadLibrary(name)), all other forms of loading libraries are unaffected by it (see JDK-8339367).

    So where the native library is loaded from highly depends on which jextract option you used, and possibly also which OS you are using. You can see the relevant jextract generated code by looking for the SymbolLookup SYMBOL_LOOKUP constant in the generated ..._h.java class.

    As mentioned in the jextract documentation, the Hotspot JVM option -Xlog:library can be used to get debug information at runtime about which native libraries are loaded.