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.
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.