Improve deployment of native libraries

Apologies for the „broadcasting“ at @Mysterion , @RoelVanderPaal , @agibsonccc and @maki , but I think this might be relevant for everybody who posted in https://forum.byte-welt.net/byte-welt-projekte-projects/jcuda/16951-source-code-jcuda-public-scm.html

I just created Improve deployment of native libraries · Issue #8 · jcuda/jcuda-main · GitHub and started working on this: Most likely, the only way to smoothly deploy JCuda (and eventually, bring it into Maven central) is to pack the natives into JARs, declare these JARs as own artifacts, with classifiers indicating the OS/architecture, and load the natives right from the JARs. There certainly will be the usual Maven-related hassles, but hopefully, it will be worth it :slight_smile:

Issues related to this issue (…) can be discussed here.

*** Edit ***

The old (current) approach is based on this layout:


jcuda\
    JCudaDriverJNI\
        CMakeList.txt
        pom.xml
    JCudaRuntimeJNI\
        CMakeList.txt
        pom.xml
    JCudaJava\
        pom.xml

(omitting JNvrtcJNI here)

The CMakeLists generate the subdirectories for the natives:


jcuda\
    JCudaDriverJNI\
        CMakeList.txt
        nativeLibrary\
            JCudaDriver-windows-x86_64.dll
        pom.xml
    JCudaRuntimeJNI\
        CMakeList.txt
        nativeLibrary\
            JCudaRuntime-windows-x86_64.dll
        pom.xml
    JCudaJava\
        pom.xml

These natives are then declared as artifacts (with classifier ${jcuda.os}-${jcuda.arch}) by the POMs in the respective JNI folders. The JCudaJava\pom.xml declares dependecies to these artifacts.


The new approach (or at least, an „intermediate state“) is as follows:

The CMake files are updated to write the natives into a subdirectory that is one level higher, disambiguated by the OS and architecture, and contains the natives in a „lib“ subfolder:


jcuda\
    JCudaDriverJNI\
        CMakeList.txt
        pom.xml
    JCudaRuntimeJNI\
        CMakeList.txt
        pom.xml
    JCudaJava\
        pom.xml
    nativeLibraries\
        windows\
            x86_64\
                lib\
                    JCudaDriver-0.7.5b-windows-x86_64.dll
                    JCudaRuntime-0.7.5b-windows-x86_64.dll

The JCudaJava\pom.xml contains an additional invocation of the „maven-jar-plugin“:
[xml]

org.apache.maven.plugins
maven-jar-plugin
3.0.2


build_cuda_native_jar
package

jar


${project.groupId}
${project.artifactId}
${project.version}
${jcuda.os}-${jcuda.arch}

ativeLibraries${jcuda.os}${jcuda.arch}

lib/JCudaRuntime
lib/JCudaDriver
lib/JNvrtc





[/xml]

This will pick up the natives, and put them into a JAR, which then has the following name and structure


jcuda-0.7.5b-windows-x86_64.jar\
    lib\
        JCudaDriver-0.7.5b-windows-x86_64.dll
        JCudaRuntime-0.7.5b-windows-x86_64.dll

When this JAR is added to the classpath, the natives can be loaded directly from this JAR.

Does this make sense?

Likely: No.
There is another step necessary. The packaging of the natives-JAR should NOT be done by the JCudaJava\pom.xml. (This should ONLY be responsible for the Java JAR).
Instead, the natives-JAR should be created by another POM. This POM should probably refer to the JCudaJava\pom.xml, and be responsible for building the native-JARs.


jcuda\

    pom.xml                <- The new POM

    JCudaDriverJNI\
        CMakeList.txt
        pom.xml
    JCudaRuntimeJNI\
        CMakeList.txt
        pom.xml
    JCudaJava\
        pom.xml

So this new POM should build the Java JAR (using the JCudaJava\pom.xml), and pack all available natives into the respective natives-JARs.

Comments, ideas, suggestions?

(I can imagine that the line
<classesDirectory>.. ativeLibraries\${jcuda.os}\${jcuda.arch}</classesDirectory>
makes Maven purists cry - that’s why I’m asking for feedback here…)

[quote=Marco13](I can imagine that the line

ativeLibraries${jcuda.os}${jcuda.arch}
makes Maven purists cry - that’s why I’m asking for feedback here…)[/quote]
Tell them to make it better :wink:

I think this is fine considering that you’re dealing with native libs and stuff.
As long as it works and the Maven build is idempotent (no matter how often you execute mvn clean install or just mvn install or even mvn clean verify -nsu after deleting your local ~/.m2/repository/) its fine.

If you’d like more control over the contents of the created Jars you can try the assembly plugin, it more flexible but also more complex.

Yes, I considered the assembly plugin as well - particularly because initially, I placed the libraries into a

ativeLibraries\JCuda.dll
folder, and wanted them to end up in an subdirectory in the JAR, as in
JCuda-native.jar**lib**JCuda.dll
but it seems that the jar plugin is too constrained in that sense, and does not allow this.

I solved it by adjusting the output path in CMake. And since you have more freedom in CMake, I also considered writing them into a
\JCudaNative\src\main\resources\lib\JCuda.dll
directory, because then (if I understood correctly), there would be no configuration required for Maven at all - maybe that’s better…?! (Convention over… y’know ;-))

There have been several commits that can be seen in the issue at https://github.com/jcuda/jcuda-main/issues/8

The structure now is roughly as described in the EDIT of the first post. I have added the new POMs (in the “root” directories of the projects) that pack the natives into JARs.

There still are some open questions:

  • How to deploy all this when the natives for Win/Mac/Linux are on ONE system, and they should ALL be packaged? Right now, the new POM only packs the natives for the system that it is invoked on. This could probably be solved with some profile-magic. (But admittedly, I had enough Maven for today ;-))
  • Can the natives be loaded from the JAR on Mac? There are rumours about new constraints that Apple imposes on the library loading mechanism - as far as I understood, it might be the case that the JCuda native library is not allowed to load the CUDA runtime library IF the JCuda native is located in the “temp”-folder…

There is still one inconvenience that I noticed: When doing a plain mvn clean install of the main package (which contains what I think is sometimes called the “aggregator POM”), then this will install all libraries, but it will not install the parent POM. The parent POM has to be installed manually - otherwise, declaring a dependency to one of the libraries will fail, complaining about the missing parent POM. Should this simply be solved by declaring the parent POM as a dedicated module in the aggregator? This sounds reasonable, but … somehow dubious as well…