【问题标题】:Bundle native dependencies in runnable .jar with Maven使用 Maven 在可运行的 .jar 中捆绑原生依赖项
【发布时间】:2012-08-15 17:18:24
【问题描述】:

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

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

目前我能够构建一个包含所有依赖项的 .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$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 来使其工作,但这不是我可以期望我的用户做的事情。

如果相关,这里是 pom.xml:https://github.com/mikera/glaze/blob/master/pom.xml

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

【问题讨论】:

  • 到底是什么问题?你能举出没有按预期工作的例子吗?
  • 没有操作系统我知道处理 jar(或 zip)文件等同于文件系统。因此,您需要在加载该本地库之前对其进行提取。
  • 过去我已经决定组装一个 JAR - 与其资源分开 - 并包括一个设置必要系统属性的启动脚本(即指向 java.library.pathresources目录)。

标签: java maven deployment jar native


【解决方案1】:

这是您在 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>

然后使用运行您的 LWJGL 程序

mvn exec:exec

【讨论】:

    【解决方案2】:

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

    这些库必须作为资源添加。我们使用了 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>
    

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

    该文件也设置为设置deleteOnExit,但这在 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") 来加载库,而不使用特定于操作系统的前缀和扩展名。

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

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

    【讨论】:

    • 我从 1.0.2 开始就一直在使用 Java,之前我从未注意到 System.load。好的!我打算写一个涉及操纵java.library.path(即no mean feat)的答案,但您根本不需要这样做,因为您可以直接加载文件。
    【解决方案3】:

    您是否尝试使用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>
    

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

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

    【讨论】:

    • 是的,我的 pom.xml 中有类似的内容。它成功捆绑了依赖项,但没有使 jar 运行
    • 你在执行java -jar your_jar_with_deps.jar命令时遇到了什么错误?
    • 我得到了问题中的 UnsatisfiedLinkError
    猜你喜欢
    • 2014-09-02
    • 2012-11-01
    • 2012-06-28
    • 2013-11-02
    • 2017-02-22
    • 1970-01-01
    • 2011-10-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多