Pytanie Powiązanie rodzimych zależności w działającym .jar z Mavenem


Mam projekt zarządzany w Maven, który ma kilka natywne zależności (LWJGL).

Wszystko działa dobrze w fazie rozwoju, ale teraz chcę skonfigurować Mavena, aby zbudował działający plik .jar, który mogę rozprowadzić. W szczególności chcę, aby użytkownicy byli bardzo łatwi w uruchamianiu aplikacji bez konieczności rozmyślania przy ścieżkach bibliotecznych lub rozpakowywaniu bibliotek natywnych itp.

Obecnie jestem w stanie skompilować plik .jar, który zawiera wszystkie zależności, ale jeśli uruchomię to (nieoczekiwanie) otrzymam niezadowalający błąd łącza:

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)

Oczywiście mogę sprawić, by działał przez ręczne instalowanie natywnych bibliotek i uruchamianie słoika java -Djava.library.path=/path/to/libs ale nie jest to coś, czego mogę oczekiwać od moich użytkowników.

Oto pom.xml w przypadku, gdy jest to istotne: https://github.com/mikera/glaze/blob/master/pom.xml

Możliwe jest skonfigurowanie Mavena tak, aby utworzył działający plik .jar, który zawiera natywne zależności i zostanie uruchomiony po dwukrotnym kliknięciu?


16
2017-08-20 11:10


pochodzenie


Czym dokładnie jest problem? Czy możesz pokazać przykłady, które nie działają zgodnie z oczekiwaniami? - khmarbaise
Żaden system operacyjny, który znam, nie obsługuje plików jar (lub zip) jako odpowiedników systemów plików. Więc będziesz potrzeba wyodrębnić tę natywną bibliotekę przed jej załadowaniem. - Joachim Sauer
W przeszłości zdecydowałem się na zebranie JAR - oddzielonego od jego zasobów - i włączając skrypt startowy, który ustawia niezbędne właściwości systemu (tj. java.library.path na resources informator). - Duncan Jones


Odpowiedzi:


To jest kod, którego użyłem do załadowania dll lub so biblioteki dołączone do słoika.

Biblioteki muszą zostać dodane jako zasoby. Użyliśmy maven i umieściliśmy je w tej hierarchii:

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>

Współużytkowane biblioteki zostaną rozpakowane do katalogu tmp dla platformy, a także będą miały tymczasową nazwę po rozpakowaniu. Dzięki temu kilka procesów może załadować dll / so bez dzielenia się rzeczywistą rozpakowaną biblioteką dll / so, ponieważ rozpakowanie może nadpisać istniejące, jeśli ma tę samą nazwę (z bardzo dziwnym zachowaniem na niektórych platformach, gdy plik został wymieniony).

Plik również ma mieć deleteOnExit ustawione, ale to nie działa w systemie 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) {
                }
            }
        }
    }
}

Biblioteki są ładowane przez utworzenie instancji NativeLoader a następnie dzwoniąc loadLibrary("thelibrary") bez prefiksów i rozszerzeń os.

To zadziałało dobrze, ale będziesz musiał ręcznie dodać biblioteki współdzielone do różnych katalogów zasobów, a następnie zbudować słoik.

Zdaję sobie sprawę, że niektóre kody z tej klasy mogą być dziwne lub przestarzałe, ale należy pamiętać, że jest to kod napisany kilka lat temu i działa on naprawdę dobrze.


12
2017-08-20 15:12



Używam języka Java od wersji 1.0.2 i nigdy tego nie zauważyłem System.load przed. Miły! Zamierzałem napisać odpowiedź, która wymagała manipulowania java.library.path (który jest nie ma żadnego wyczynu), ale nie musisz tego robić w ogóle, ponieważ możesz po prostu załadować plik bezpośrednio. - Tom Anderson


Czy próbowałeś użyć? maven-assembly-plugin Oto przykład :

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

I dla twoich natywnych zależności możesz chcieć użyć Bundle-NativeCode w twoim pliku manifestu. Widzieć http://wiki.osgi.org/wiki/Bundle-NativeCode.

Możesz również chcieć rzucić okiem na maven-bundle-plugin : http://felix.apache.org/site/apache-felix-maven-bundle-plugin-bnd.html wygenerować go za pomocą Mavena.


1
2017-08-20 11:34



Tak, mam coś takiego w moim pom.xml. Pomyślnie pakuje zależności, ale nie pozwala uruchomić słoika - mikera
jaki jest błąd podczas wykonywania java -jar your_jar_with_deps.jar dowództwo ? - Y__
Otrzymuję UnsatisfiedLinkError jak w pytaniu - mikera


Oto wtyczka, której potrzebujesz w swoim pom.xml uruchomić kompilację przy wymaganym uruchomieniu Parametr, o którym wspominałeś:

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

Następnie uruchom program LWJGL przy użyciu

mvn exec:exec

0
2018-03-23 12:59