使用 Maven 在可运行的 .jar 中捆绑本机依赖项 [英] Bundle native dependencies in runnable .jar with Maven

查看:28
本文介绍了使用 Maven 在可运行的 .jar 中捆绑本机依赖项的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个在 Maven 中管理的项目,该项目具有一些本机依赖项(LWJGL).

I have a project managed in Maven that has some native dependencies (LWJGL).

在开发过程中一切正常,但现在我想设置 Maven,以便它构建一个可运行的 .jar 文件,我可以重新分发该文件.特别是,我希望用户可以非常轻松地运行应用程序,而不必弄乱库路径或解压本机库等.

Everything works fine in development, but now I want to set up Maven so that it will build a runnable .jar file that I can redistribute. In particular, I want it to be very easy for users to run the app without having to mess around with library paths or unpacking native libraries etc.

目前我能够构建一个包含所有依赖项的 .jar 文件,但是如果我运行它然后(不出所料)我得到一个不满意的链接错误:

Currently I am able to build a .jar file that includes all the dependencies, but if I run it then (unsurprisingly) I get an unsatisfied link error:

Exception in thread "main" java.lang.UnsatisfiedLinkError: no lwjgl in java.libr
ary.path
        at java.lang.ClassLoader.loadLibrary(Unknown Source)
        at java.lang.Runtime.loadLibrary0(Unknown Source)
        at java.lang.System.loadLibrary(Unknown Source)
        at org.lwjgl.Sys$1.run(Sys.java:73)
        at java.security.AccessController.doPrivileged(Native Method)
        at org.lwjgl.Sys.doLoadLibrary(Sys.java:66)
        at org.lwjgl.Sys.loadLibrary(Sys.java:95)
        at org.lwjgl.Sys.<clinit>(Sys.java:112)
        at org.lwjgl.opengl.Display.<clinit>(Display.java:132)
        at glaze.TestApp.start(TestApp.java:10)
        at glaze.TestApp.main(TestApp.java:31)

显然,我可以通过手动安装本机库并使用 java -Djava.library.path=/path/to/libs 运行 jar 来使其工作,但这不是我所期望的我的用户要做.

Obviously I can make it work by manually installing the native libraries and running the jar with java -Djava.library.path=/path/to/libs but that isn't something I can expect my users to do.

这是相关的 pom.xml:https://github.com/mikera/glaze/blob/master/pom.xml

Here's the pom.xml in case it is relevant: https://github.com/mikera/glaze/blob/master/pom.xml

是否可以设置 Maven,以便它创建一个可运行的 .jar,其中包含本机依赖项,​​并在双击时成功运行?

It is possible to set up Maven so that it will create a runnable .jar that includes the native dependencies and will run successfully when double-clicked?

推荐答案

这是我用来加载 dllso 库中的一些代码罐子.

This is some code I used to use to load dll or so libraries that are bundled in the jar.

库必须作为资源添加.我们使用了 maven 并将它们放在这个层次结构中:

The libraries must be added as resources. We used maven and put them in this hierarchy:

src/main/resources/lib/win-x86/<dlls for 32-bit windows>
src/main/resources/lib/linux-x86/<so for 32-bit linux>
src/main/resources/lib/linux-x86_64/<so for 64-bit linux>
src/main/resources/lib/linux-ia64/<so for 64-bit linux on itanium>

共享库将被解压到平台的 tmp 目录中,并且在解压时还有一个临时名称.这是为了让多个进程加载 dll/so 而不共享实际提取的 dll/so,因为如果具有相同的名称,解包可能会覆盖现有的 dll/so(在某些平台上替换文件时会出现非常奇怪的行为).

The shared libraries will be unpacked to the tmp-directory for the platform and also have a temporary name when unpacked. This is to let several processes load the dll/so without sharing the actual extracted dll/so since the unpacking could overwrite existing ones if having the same name (with very strange behavior on some platforms when the file was replaced).

该文件也设置为 deleteOnExit 设置,但这在 Windows AFAIK 上不起作用.

The file is also set to have deleteOnExit set but that does not work on windows AFAIK.

NativeLoader.java

public class NativeLoader {

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

    public NativeLoader() {
    }

    public void loadLibrary(String library) {
        try {
            System.load(saveLibrary(library));
        } catch (IOException e) {
            LOG.warn("Could not find library " + library +
                    " as resource, trying fallback lookup through System.loadLibrary");
            System.loadLibrary(library);
        }
    }


    private String getOSSpecificLibraryName(String library, boolean includePath) {
        String osArch = System.getProperty("os.arch");
        String osName = System.getProperty("os.name").toLowerCase();
        String name;
        String path;

        if (osName.startsWith("win")) {
            if (osArch.equalsIgnoreCase("x86")) {
                name = library + ".dll";
                path = "win-x86/";
            } else {
                throw new UnsupportedOperationException("Platform " + osName + ":" + osArch + " not supported");
            }
        } else if (osName.startsWith("linux")) {
            if (osArch.equalsIgnoreCase("amd64")) {
                name = "lib" + library + ".so";
                path = "linux-x86_64/";
            } else if (osArch.equalsIgnoreCase("ia64")) {
                name = "lib" + library + ".so";
                path = "linux-ia64/";
            } else if (osArch.equalsIgnoreCase("i386")) {
                name = "lib" + library + ".so";
                path = "linux-x86/";
            } else {
                throw new UnsupportedOperationException("Platform " + osName + ":" + osArch + " not supported");
            }
        } else {
            throw new UnsupportedOperationException("Platform " + osName + ":" + osArch + " not supported");
        }

        return includePath ? path + name : name;
    }

    private String saveLibrary(String library) throws IOException {
        InputStream in = null;
        OutputStream out = null;

        try {
            String libraryName = getOSSpecificLibraryName(library, true);
            in = this.getClass().getClassLoader().getResourceAsStream("lib/" + libraryName);
            String tmpDirName = System.getProperty("java.io.tmpdir");
            File tmpDir = new File(tmpDirName);
            if (!tmpDir.exists()) {
                tmpDir.mkdir();
            }
            File file = File.createTempFile(library + "-", ".tmp", tmpDir);
            // Clean up the file when exiting
            file.deleteOnExit();
            out = new FileOutputStream(file);

            int cnt;
            byte buf[] = new byte[16 * 1024];
            // copy until done.
            while ((cnt = in.read(buf)) >= 1) {
                out.write(buf, 0, cnt);
            }
            LOG.info("Saved libfile: " + file.getAbsoluteFile());
            return file.getAbsolutePath();
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException ignore) {
                }
            }
            if (out != null) {
                try {
                    out.close();
                } catch (IOException ignore) {
                }
            }
        }
    }
}

通过创建一个 NativeLoader 的实例,然后通过调用 loadLibrary("thelibrary") 来加载库,而没有特定于操作系统的前缀和扩展名.

The libraries are loaded by creating an instance of the NativeLoader and then by calling loadLibrary("thelibrary") without the os-specific prefixes and extensions.

这对我们很有效,但您必须手动将共享库添加到不同的资源目录,然后构建 jar.

This worked well for us but you will have to add the shared libraries manually to the different resource directories and then build the jar.

我意识到此类中的某些代码可能很奇怪或过时,但请记住,这是我几年前编写的代码,并且运行良好.

I realize that some code in this class may be strange or obsolete but bare in mind that this is code I wrote some years ago and it has been working really well.

这篇关于使用 Maven 在可运行的 .jar 中捆绑本机依赖项的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆