使用Maven在runnable .jar中捆绑本机依赖项

时间:2012-08-20 11:10:45

标签: java maven deployment jar native

我在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,并在双击时成功运行?

3 个答案:

答案 0 :(得分:14)

这是我用来加载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 /所以因为如果具有相同的名称,解包可以覆盖现有的(在替换文件时在某些平台上有非常奇怪的行为)。

该文件也设置为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 :(得分:1)

您是否尝试使用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-pluginhttp://felix.apache.org/site/apache-felix-maven-bundle-plugin-bnd.html以使用Maven生成它。

答案 2 :(得分:0)

以下是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