1

I've got a maven project that includes a JNI lib. It works great in testing within its own project directory. When I run mvn install the following files are installed to ~/.m2/repository/cam/narzt/getargv/cam_narzt_getargv_Getargv:

~/.m2/repository/cam/narzt/getargv/cam_narzt_getargv_Getargv
├── 1.0-SNAPSHOT
│   ├── _remote.repositories
│   ├── cam_narzt_getargv_Getargv-1.0-SNAPSHOT-javadoc.jar
│   ├── cam_narzt_getargv_Getargv-1.0-SNAPSHOT-javadoc.jar.asc
│   ├── cam_narzt_getargv_Getargv-1.0-SNAPSHOT.jar
│   ├── cam_narzt_getargv_Getargv-1.0-SNAPSHOT.jar.asc
│   ├── cam_narzt_getargv_Getargv-1.0-SNAPSHOT.pom
│   ├── cam_narzt_getargv_Getargv-1.0-SNAPSHOT.pom.asc
│   └── maven-metadata-local.xml
└── maven-metadata-local.xml

2 directories, 9 files

and the (non-javadoc) jar includes the following files:

        0  02-20-2023 21:23   META-INF/
       81  02-20-2023 21:23   META-INF/MANIFEST.MF
        0  02-20-2023 12:31   cam/
        0  02-20-2023 12:31   cam/narzt/
        0  02-20-2023 21:23   cam/narzt/getargv/
        0  02-20-2023 21:23   META-INF/maven/
        0  02-20-2023 21:23   META-INF/maven/cam.narzt.getargv/
        0  02-20-2023 21:23   META-INF/maven/cam.narzt.getargv/cam_narzt_getargv_Getargv/
     1608  02-20-2023 21:23   cam/narzt/getargv/Main.class
     4441  02-20-2023 21:23   cam/narzt/getargv/Getargv.class
     9323  02-20-2023 12:08   META-INF/maven/cam.narzt.getargv/cam_narzt_getargv_Getargv/pom.xml
       84  02-16-2023 22:48   META-INF/maven/cam.narzt.getargv/cam_narzt_getargv_Getargv/pom.properties

When I add this package to the pom.xml as a dependency in a separate test app, I can depend on the class just fine, but the native lib/methods are missing:

 <dependency>
   <groupId>cam.narzt.getargv</groupId>
   <artifactId>cam_narzt_getargv_Getargv</artifactId>
   <version>1.0-SNAPSHOT</version>
 </dependency>
package cam.narzt.testapp;
import java.io.IOException;
import cam.narzt.getargv.Getargv;

public class App {
    public static void main(String[] args) {
        try {
            Getargv.asArray(ProcessHandle.current().pid());
        } catch (IOException e) {
            // this is just a test, ignore
        }
    }
}

this gives:

Caused by: java.lang.UnsatisfiedLinkError: 'byte[][] cam.narzt.getargv.Getargv.get_argv_and_argc_of_pid(long)'
    at cam.narzt.getargv.Getargv.get_argv_and_argc_of_pid (Native Method)
    at cam.narzt.getargv.Getargv.asArray (Getargv.java:52)
    at cam.narzt.testapp.App.main (App.java:14)
    at org.codehaus.mojo.exec.ExecJavaMojo$1.run (ExecJavaMojo.java:279)
    at java.lang.Thread.run (Thread.java:1589)

The original project's pom.xml has the instructions for building the jni lib, but the test project doesn't do so, and it doesn't seem to be included into the jar by default. So, what is the preferred way to fix this, and what does that look like in the original project's pom.xml?

Camden Narzt
  • 2,271
  • 1
  • 23
  • 42
  • See [**Bundle native dependencies in runnable .jar with Maven**](https://stackoverflow.com/questions/12036607/bundle-native-dependencies-in-runnable-jar-with-maven) and [**Adding a JNI library to the local Maven Repository**](https://stackoverflow.com/questions/10071058/adding-a-jni-library-to-the-local-maven-repository) to start. – Andrew Henle Feb 23 '23 at 01:14
  • @AndrewHenle that was enough for me to make a working solution. If you want to make an answer go ahead or I'll do one with the code I wound up with. – Camden Narzt Feb 26 '23 at 20:48
  • @AndrewHenle actually, that doesn't seem to add the resource when the jar is published to maven central... and maven doesn't compile the dep when it pulls it down for some reason. – Camden Narzt Mar 03 '23 at 06:33

1 Answers1

0

Add JNI lib to jar as a resource:

<build>
    <resources>
      <resource>
        <directory>${project.build.directory}/native/lib</directory>
        <includes>
          <include>**</include>
        </includes>
      </resource>
    </resources>
...
</build>

replace System.loadLibrary with a custom NativeLoader.loadLibrary:

package cam.narzt.getargv;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.FileOutputStream;
import java.io.File;
import java.util.logging.Logger;

/**
 * Class to assist in loading a JNI native lib, tries to load from jar but then
 * falls back to java.library.path if not found.
 */
public final class NativeLoader {

    private static final Logger LOG = Logger.getLogger(NativeLoader.class.toString());

    private NativeLoader() {
        throw new AssertionError();
    }

    /**
     * extended loadLibrary method that looks in the jar as well as the
     * java.library.path
     *
     * @param library the name of the library to load
     * @since 0.1
     * @throws UnsatisfiedLinkError when lib not found or cannot load due to linker
     *                              errors (missing libgetargv on system or bad
     *                              rpath)
     */
    public static void loadLibrary(String library) {
        String libraryName = "lib" + library + ".dylib";
        Class<?> callingClass = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE)
                .getCallerClass();
        LOG.fine("called by class: " + callingClass);
        try (InputStream in = callingClass.getClassLoader().getResourceAsStream(libraryName)) {
            if (in != null) {
                LOG.fine("trying to load: " + libraryName);
                System.load(saveLibrary(libraryName, in));
            } else {
                throw new IOException("No Resource Found");
            }
        } catch (IOException e) {
            LOG.fine("Could not find library " + library
                    + " as resource, trying fallback lookup through System.loadLibrary");
            System.loadLibrary(library);
        }
    }

    private static String saveLibrary(String libraryName, InputStream in) throws IOException {
        File tmpDir = new File(System.getProperty("java.io.tmpdir"));
        if (!tmpDir.exists()) {
            tmpDir.mkdir();
        }

        File file = File.createTempFile(libraryName + ".", ".tmp", tmpDir);
        file.deleteOnExit();

        try (OutputStream out = new FileOutputStream(file)) {
            LOG.fine("trying to save to: " + file.getAbsolutePath());
            in.transferTo(out);
            LOG.fine("Saved libfile: " + file.getAbsoluteFile());
            return file.getAbsolutePath();
        }
    }
}
Camden Narzt
  • 2,271
  • 1
  • 23
  • 42