Bundle native JNI shared libraries with Clojure li

2020-07-18 09:17发布

问题:

I am writing a library for clojure which involves native code. How can I bundle the shared library (aka native dependencies) when I deploy the clojure libraries to public repositories (like clojars)?

Further Info:

My project structure looks roughly like:

src/
    native/     - C code , C Object files and compiled shared libs
    java/       - Java stuff
    clojure/    - Clojure stuff

I am currently using leineingen. I have tried doing:

:jvm-opts [~(str "-Djava.library.path=src/native/:"
          (System/getenv "$LD_LIBRARY_PATH"))]

It works if I am in the project. However, if I include this project as a dependency, I will get a UnsatisfiedLink error.

回答1:

The answer depends on your exact use-case. In the simplest situation, you need to:

  • bundle the native lib inside the library jar, for example by including the native/ folder in :resource-paths in your project.clj.
  • when you use your library, you can specify a :native-prefix option to indicate a path inside your library jar from which native libraries should be extracted. For example, if your library contains a resource "mylib.so" in the root folder, you can specify it like this: [com.foo/bar "1.0.1" :native-prefix ""]
  • you should also specify where the extracted libs should go, using the :native-path option in your project.clj.
  • finally, you should add the :native-path you have specified to java.library.path, using :jvm-opts like you said.

These options are documented in the sample leiningen project.clj.

Now the reason I said it depends on your use-case is that if you want to create an uberjar that contains native libs, things start getting more messy. The main reason is that you can't directly link to a lib that is zipped inside your jar. If you're lucky, you'll be able to use the loadLibraryFromJar method in the NativeUtils class. However, I remember having ClassLoader-related issues that prevented me from using System/load. Instead I had to make sure the library was present in one of the paths that the JVM looks for, so that System/loadLibrary finds it correctly. Here is what I ended up doing:

  • manually extract the native lib from the uberjar at runtime to a temporary folder, with (-> my-lib io/resource io/input-stream (io/copy my-temp-file))
  • update the java.library.path system property at runtime to add the temporary folder to it, using System/setProperty
  • finally, use this reflection trick to force the library path to be updated

This is painful to setup, but after that it works pretty well.