java 使用 Maven 在可运行的 .jar 中捆绑本机依赖项

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12036607/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 07:22:23  来源:igfitidea点击:

Bundle native dependencies in runnable .jar with Maven

javamavendeploymentjarnative

提问by mikera

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

我有一个在 Maven 中管理的项目,它有一些本机依赖项LWJGL)。

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.

在开发中一切正常,但现在我想设置 Maven,以便它构建一个可以重新分发的可运行 .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:

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

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

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

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

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

这是 pom.xml 以防万一它是相关的:https: //github.com/mikera/glaze/blob/master/pom.xml

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?

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

采纳答案by maba

This is some code I used to use to load dllor solibraries that are bundled in the jar.

这是我用来加载dllso捆绑在 jar 中的库的一些代码。

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

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

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>

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

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

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

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

NativeLoader.java

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) {
                }
            }
        }
    }
}

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

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

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.

这对我们很有效,但您必须手动将共享库添加到不同的资源目录,然后构建 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.

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

回答by Y__

Did you try to use maven-assembly-pluginhere is an example :

您是否尝试使用maven-assembly-plugin这里的示例:

<build>
   <plugins>
      <plugin>
         <artifactId>maven-assembly-plugin</artifactId>
         <configuration>
         <archive>
             <manifest>
                <mainClass>your.main.Class</mainClass>
             </manifest>
         </archive>
         <descriptorRefs>
             <descriptorRef>jar-with-dependencies</descriptorRef>
         </descriptorRefs>
         </configuration>
       </plugin>
   </plugins>
</build>

And for your native dependencies you may want to use Bundle-NativeCodein your manifest file. See http://wiki.osgi.org/wiki/Bundle-NativeCode.

对于您的本机依赖项,​​您可能希望Bundle-NativeCode在清单文件中使用。请参阅http://wiki.osgi.org/wiki/Bundle-NativeCode

You also may want to have a look at the maven-bundle-plugin: http://felix.apache.org/site/apache-felix-maven-bundle-plugin-bnd.htmlto generate it with Maven.

您可能还想查看maven-bundle-pluginhttp: //felix.apache.org/site/apache-felix-maven-bundle-plugin-bnd.html以使用 Maven 生成它。

回答by Christopher Stock

Here is the plugin you need in your pom.xmlto run your build with the required run Parameter you mentioned:

这是pom.xml您使用您提到的所需运行参数运行构建所需的插件:

    <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <version>1.6.0</version>
        <configuration>
            <executable>java</executable>
            <arguments>
                <argument>-Djava.library.path=target/natives</argument>
                <argument>-classpath</argument>
                <classpath />
                <argument>my.main.package.MainClass</argument>
            </arguments>
        </configuration>
    </plugin>

Then run your LWJGL-Program using

然后使用运行您的 LWJGL 程序

mvn exec:exec