Hi,
I am trying to create a native app using DL4J and GraalVM. My objective is to create a distributable executable with all the dependencies included so that Users can just download and run it with installing JRE, gcc, etc.
I am using the 1.0.0-SNAPSHOT versions of ND4J and DL4J since they contain the 1.5.5 version of JavaCPP which has GraalVM support.
This are how my dependencies look like :
* Maven: org.nd4j:nd4j-api:1.0.0-SNAPSHOT
* Maven: org.nd4j:nd4j-common:1.0.0-SNAPSHOT
* Maven: org.nd4j:nd4j-native:1.0.0-SNAPSHOT
* Maven: org.nd4j:nd4j-native:linux-x86_64:1.0.0-SNAPSHOT
* Maven: org.nd4j:nd4j-native:macosx-x86_64:1.0.0-SNAPSHOT
* Maven: org.nd4j:nd4j-native-api:1.0.0-SNAPSHOT
* Maven: org.nd4j:nd4j-native-platform:1.0.0-SNAPSHOT
* Maven: org.nd4j:nd4j-native-platform:linux-x86_64:1.0.0-SNAPSHOT
* Maven: org.nd4j:nd4j-native-preset:1.0.0-SNAPSHOT
* Maven: org.nd4j:nd4j-native-preset:linux-x86_64:1.0.0-SNAPSHOT
* Maven: org.nd4j:protobuf:1.0.0-SNAPSHOT
* Maven: org.bytedeco:hdf5:linux-x86_64:1.12.0-1.5.5
* Maven: org.bytedeco:hdf5-platform:1.12.0-1.5.5
* Maven: org.bytedeco:javacpp:1.5.5
* Maven: org.bytedeco:javacpp:linux-x86_64:1.5.5
* Maven: org.bytedeco:javacpp:macosx-x86_64:1.5.5
* Maven: org.bytedeco:javacpp-platform:1.5.5
* Maven: org.bytedeco:leptonica:linux-x86_64:1.80.0-1.5.5
* Maven: org.bytedeco:leptonica-platform:1.80.0-1.5.5
* Maven: org.bytedeco:mkl:2021.1-1.5.5
* Maven: org.bytedeco:mkl:linux-x86:2021.1-1.5.5
* Maven: org.bytedeco:mkl:linux-x86_64:2021.1-1.5.5
* Maven: org.bytedeco:mkl:macosx-x86_64:2021.1-1.5.5
* Maven: org.bytedeco:mkl:windows-x86:2021.1-1.5.5
* Maven: org.bytedeco:mkl:windows-x86_64:2021.1-1.5.5
* Maven: org.bytedeco:mkl-platform:2021.1-1.5.5
* Maven: org.bytedeco:openblas:0.3.13-1.5.5
* Maven: org.bytedeco:openblas:linux-x86_64:0.3.13-1.5.5
* Maven: org.bytedeco:openblas:macosx-x86_64:0.3.13-1.5.5
* Maven: org.bytedeco:openblas-platform:0.3.13-1.5.5
* Maven: org.bytedeco:opencv:linux-x86_64:4.5.1-1.5.5
* Maven: org.bytedeco:opencv-platform:4.5.1-1.5.5
* Maven: org.deeplearning4j:deeplearning4j-common:1.0.0-SNAPSHOT
* Maven: org.deeplearning4j:deeplearning4j-core:1.0.0-SNAPSHOT
* Maven: org.deeplearning4j:deeplearning4j-datasets:1.0.0-SNAPSHOT
* Maven: org.deeplearning4j:deeplearning4j-datavec-iterators:1.0.0-SNAPSHOT
* Maven: org.deeplearning4j:deeplearning4j-modelimport:1.0.0-SNAPSHOT
* Maven: org.deeplearning4j:deeplearning4j-nn:1.0.0-SNAPSHOT
* Maven: org.deeplearning4j:deeplearning4j-ui-components:1.0.0-SNAPSHOT
* Maven: org.deeplearning4j:deeplearning4j-utility-iterators:1.0.0-SNAPSHOT
This is how my resource config looks like :
{
"resources":{
"includes":[
{"pattern":"\\QMETA-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat\\E"},
{"pattern":"\\QMETA-INF/services/org.apache.logging.log4j.core.util.ContextDataProvider\\E"},
{"pattern":"\\QMETA-INF/services/org.apache.logging.log4j.spi.Provider\\E"},
{"pattern":"\\QMETA-INF/services/org.apache.logging.log4j.util.PropertySource\\E"},
{"pattern":"\\QMETA-INF/services/org.eclipse.jetty.http.HttpFieldPreEncoder\\E"},
{"pattern":"\\QMETA-INF/services/org.eclipse.jetty.websocket.api.extensions.Extension\\E"},
{"pattern":"\\QMETA-INF/services/org.nd4j.common.base.PreconditionsFormat\\E"},
{"pattern":"\\QMETA-INF/services/org.nd4j.linalg.compression.NDArrayCompressor\\E"},
{"pattern":"\\QMETA-INF/services/org.nd4j.linalg.env.EnvironmentalAction\\E"},
{"pattern":"\\QMETA-INF/services/org.nd4j.linalg.factory.Nd4jBackend\\E"},
{"pattern":"\\Qlog4j2.xml\\E"},
{"pattern":"\\Qnd4j-native.properties\\E"},
{"pattern":"\\Qorg/bytedeco/javacpp/linux-x86_64/libjnijavacpp.so\\E"},
{"pattern":"\\Qorg/bytedeco/javacpp/properties/linux-x86_64.properties\\E"},
{"pattern":"\\Qorg/bytedeco/openblas/linux-x86_64/libgcc_s.so.1\\E"},
{"pattern":"\\Qorg/bytedeco/openblas/linux-x86_64/libgfortran.so.4\\E"},
{"pattern":"\\Qorg/bytedeco/openblas/linux-x86_64/libjniopenblas.so\\E"},
{"pattern":"\\Qorg/bytedeco/openblas/linux-x86_64/libjniopenblas_nolapack.so\\E"},
{"pattern":"\\Qorg/bytedeco/openblas/linux-x86_64/libopenblas.so.0\\E"},
{"pattern":"\\Qorg/bytedeco/openblas/linux-x86_64/libquadmath.so.0\\E"},
{"pattern":"\\Qorg/eclipse/jetty/http/encoding.properties\\E"},
{"pattern":"\\Qorg/eclipse/jetty/http/mime.properties\\E"},
{"pattern":"\\Qorg/eclipse/jetty/version/build.properties\\E"},
{"pattern":"\\Qorg/nd4j/nativeblas/linux-x86_64/libjnind4jcpu.so\\E"},
{"pattern":"\\Qorg/nd4j/nativeblas/linux-x86_64/libnd4jcpu.so\\E"},
{"pattern":"\\Qorg/slf4j/impl/StaticLoggerBinder.class\\E"}
]},
"bundles":[
{"name":"javax.servlet.LocalStrings"},
{"name":"javax.servlet.http.LocalStrings"}
]
}
I am able to build a native image using GraalVM for my project in a linux debian 8 docker image with libstdc+±8-dev installed. Following is the docker file :
RUN apt-get update && \
apt-get install -y --force-yes gcc libz-dev upx libstdc++-8-dev
ARG VERSION=21.1.0
RUN wget https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-$VERSION/graalvm-ce-java11-linux-amd64-$VERSION.tar.gz && \
tar -xvzf graalvm-ce-java11-linux-amd64-$VERSION.tar.gz && \
rm graalvm-ce-java11-linux-amd64-$VERSION.tar.gz && \
/graalvm-ce-java11-$VERSION/bin/gu install native-image
RUN wget http://www.eu.apache.org/dist/maven/maven-3/3.3.9/binaries/apache-maven-3.3.9-bin.tar.gz && \
tar -xzvf apache-maven-3.3.9-bin.tar.gz -C /var/lib/ && \
rm apache-maven-3.3.9-bin.tar.gz
RUN apt-get update && \
apt-get install -y --force-yes build-essential
ARG RESULT_LIB="/var/lib/static"
RUN mkdir ${RESULT_LIB} && \
curl -L -o musl.tar.gz https://musl.libc.org/releases/musl-1.2.1.tar.gz && \
mkdir musl && tar -xvzf musl.tar.gz -C musl --strip-components 1 && cd musl && \
./configure --disable-shared --prefix=${RESULT_LIB} && \
make && make install && \
cd / && rm -rf /muscl && rm -f /musl.tar.gz && \
cp /usr/lib/gcc/x86_64-linux-gnu/8/libstdc++.a ${RESULT_LIB}/lib/
ENV PATH="$PATH:${RESULT_LIB}/bin"
ENV CC="musl-gcc"
RUN curl -L -o zlib.tar.gz https://zlib.net/zlib-1.2.11.tar.gz && \
mkdir zlib && tar -xvzf zlib.tar.gz -C zlib --strip-components 1 && cd zlib && \
./configure --static --prefix=${RESULT_LIB} && \
make && make install && \
cd / && rm -rf /zlib && rm -f /zlib.tar.gz
ENV CC="musl-gcc"
ENV M2_HOME=/var/lib/apache-maven-3.3.9
ENV PATH=/graalvm-ce-java11-$VERSION/bin:/var/lib/apache-maven-3.3.9/bin:$PATH
However, when I try to execute the native binary on my production linux server without libstdc installed, I am getting UnsatisfiedLinkError
This is inspite of the file being present in the specified location and in the jar resources
root@04af621a9001:~/.javacpp/cache/linux-x86_64# ls -lah
total 269M
drwxr-xr-x 2 root root 4.0K Apr 23 19:16 .
drwxr-xr-x 3 root root 4.0K Apr 23 19:16 ..
lrwxrwxrwx 1 root root 13 Apr 23 19:16 libgcc_s.so -> libgcc_s.so.1
-rw-r--r-- 1 root root 89K Jan 1 1970 libgcc_s.so.1
lrwxrwxrwx 1 root root 16 Apr 23 19:16 libgfortran.so -> libgfortran.so.4
-rw-r--r-- 1 root root 1.5M Jan 1 1970 libgfortran.so.4
-rw-r--r-- 1 root root 55K Jan 1 1970 libjnijavacpp.so
-rw-r--r-- 1 root root 3.2M Jan 1 1970 libjnind4jcpu.so
-rw-r--r-- 1 root root 25M Jan 1 1970 libjniopenblas.so
-rw-r--r-- 1 root root 300K Jan 1 1970 libjniopenblas_nolapack.so
-rw-r--r-- 1 root root 181M Jan 1 1970 libnd4jcpu.so
lrwxrwxrwx 1 root root 16 Apr 23 19:16 libopenblas.so -> libopenblas.so.0
-rw-r--r-- 1 root root 30M Jan 1 1970 libopenblas.so.0
lrwxrwxrwx 1 root root 25 Apr 23 19:16 libopenblas_nolapack.so -> libopenblas_nolapack.so.0
-rw-r--r-- 1 root root 30M Jan 1 1970 libopenblas_nolapack.so.0
lrwxrwxrwx 1 root root 16 Apr 23 19:16 libquadmath.so -> libquadmath.so.0
-rw-r--r-- 1 root root 253K Jan 1 1970 libquadmath.so.0
root@04af621a9001:~/.javacpp/cache/linux-x86_64#
Then I tried creating native image using --static flag with musl as libc ( details mentioned in the docker file ), assuming issues were due to CPP libraries not being present on the machine and native image relying on dynamic linking.
I was following this blog : GraalVM Native Image Tips & Tricks - James Ward
After doing this change, the application started giving the same error about libopenblas_nolapack.so library, again which is present in the given path
Error :
Caused by: java.lang.UnsatisfiedLinkError: Can’t load library: /root/.javacpp/cache/linux-x86_64/libjniopenblas_nolapack.so
So I wanted to know what I am missing. I am able to see the missing files in my fat jar, in my resource config, in my external dependencies so my thought is that it would not be due to some misconfigured pom.