This post was updated on .
Hello. I wrote an application using JOGL, compiled it and run, and everything works fine. Now I want to make a JAR file out of it, so that I could put it on my website for download, and it needs to run even if the user doesn't have JOGL libraries installed on his machine. Is there any way to supply the JOGL libraries within the JAR file itself to make it a self-contained app? I tried to make a JAR file, but it doesn't seem to work: it prints an error about missing libraries: Exception in thread "main" java.lang.NoClassDefFoundError: javax/media/opengl/GLEventListener at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClass(ClassLoader.java:643) at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142) at java.net.URLClassLoader.defineClass(URLClassLoader.java:277) at java.net.URLClassLoader.access$000(URLClassLoader.java:73) at java.net.URLClassLoader$1.run(URLClassLoader.java:212) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:205) at java.lang.ClassLoader.loadClass(ClassLoader.java:323) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:294) at java.lang.ClassLoader.loadClass(ClassLoader.java:268) Caused by: java.lang.ClassNotFoundException: javax.media.opengl.GLEventListener at java.net.URLClassLoader$1.run(URLClassLoader.java:217) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:205) at java.lang.ClassLoader.loadClass(ClassLoader.java:323) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:294) at java.lang.ClassLoader.loadClass(ClassLoader.java:268) ... 11 more Could not find the main class: Main. Program will exit. How can I supply the JOGL libraries within my JAR file? Edit: Oh, and I don't use any IDE, just plain old text editor and command line on Linux. |
Administrator
|
Hi
I assume that you would like to create a fat JAR. I advise you to look at this: http://jogamp.org/wiki/index.php/JogAmp_JAR_File_Handling#Fat-Jar http://forum.jogamp.org/Fat-jar-deployment-method-ant-recipe-td4032213.html https://gouessej.wordpress.com/2014/11/22/ardor3d-est-mort-vive-jogamps-ardor3d-continuation-ardor3d-is-dead-long-life-to-jogamps-ardor3d-continuation/#executablejar
Julien Gouesse | Personal blog | Website
|
I don't know. Would I? I see that this technique is "discouraged" at the website you provided. What I want is to make a self-contained JAR file available for download and run by the user which doesn't have any JOGL libraries installed on his machine. If the "fat JAR" solution allows this, then yes, I would like to create one. On that website they also say: Could this be avoided by providing a set of several self-contained JAR files, each for one supported platform? This way, they would together still be weighty, but the user could select the one compatible with his platform, which is a fraction of the whole set and less weighty to download, I guess? Which files should I put into that JAR and in what structure to make it work? I'd like to put there only a minimal set of files which are actually used by my application. How can I figure out which are needed? Oh, I forgot to add in my original post that I don't use Ant, Eclipse, NetBeans, nor any other IDE, just plain old text editor and a command line on Linux. I'll try to add it to my original post... Hmm, but it turns out that I have the ant command on my system, so perhaps I could use it somehow... I'll need to dig into the post you linked, but for now it's black magic to me. Can you advice me any additional URLs I should check out to find more about what is this ant and how to use it? |
Administrator
|
What you described in your first post made me think that you want to make a single fat JAR with everything in it, i.e a fat JAR (or any variant doing almost the same thing). It's discouraged mainly because it mixes up everything (your code + third party libraries) together, making it hard to indicate that what was in a JAR came from a specific organization. It's the most commonly used solution to achieve that even though I go a little further in my case. It's possible but is it worth the effort? It depends on the size of your application. I advise you to make it work first with the JARs mentioned in the second post. Then, you will be able to reduce the size of the JAR by using some atomic JARs but again is it worth the effort? You don't need an IDE to make a fat JAR. There are some softwares for that but they are a lot less flexible than the build tools like Ant. I advise you to read this tutorial: https://ant.apache.org/manual/tutorial-HelloWorldWithAnt.html See Ant as a cross-platform make :) I advise you to read carefully my post, it mentions tons of fat JAR limitations.
Julien Gouesse | Personal blog | Website
|
Couldn't I just make separate directories in the JAR file, one for my package, and one for the libraries?
For the production version, I guess it might be worth the effort (smaller downloads, less traffic for the server etc.). Expecially if generating of these files could be automated through some makefiles. That's why I'm trying to do by now. OK, so I decided to go with ant, since I already have it on my system. (Thanks for the tutorial! It was very helpful). Here's my first attempt at writing the build file: <project name="TextureQuads" basedir="." default="main"> <property name="src.dir" value="src"/> <property name="lib.dir" value="lib"/> <property name="assets.dir" value="assets"/> <property name="build.dir" value="build"/> <property name="classes.dir" value="${build.dir}/classes"/> <property name="jar.dir" value="${build.dir}/jar"/> <property name="main-class" value="texQuads.Main"/> <path id="classpath"> <fileset dir="/usr/share/gluegen-2/lib" includes="gluegen-rt.jar"/> <fileset dir="/usr/share/jogl-2/lib" includes="jogl.all.jar jogl.test.jar"/> <fileset dir="${lib.dir}" includes="**/*.jar"/> </path> <presetdef name="javac"> <javac includeantruntime="false"/> </presetdef> <target name="clean"> <delete dir="${build.dir}"/> </target> <target name="compile"> <mkdir dir="${classes.dir}"/> <javac srcdir="${src.dir}" destdir="${classes.dir}" classpathref="classpath"/> </target> <target name="jar" depends="compile"> <mkdir dir="${jar.dir}"/> <jar destfile="${jar.dir}/${ant.project.name}.jar" basedir="${classes.dir}"> <manifest> <attribute name="Main-Class" value="${main-class}"/> <attribute name="Class-Path" value="/usr/share/gluegen-2/lib /usr/share/jogl-2/lib"/> </manifest> </jar> </target> <target name="run" depends="compile"> <java classname="${main-class}" classpath="${classes.dir}" classpathref="classpath" fork="true"/> </target> <target name="run-jar" depends="jar"> <java jar="${jar.dir}/${ant.project.name}.jar" fork="true"/> </target> <target name="clean-build" depends="clean,jar"/> <target name="main" depends="clean,jar"/> </project> Note that I deliberately set the Compilation goes without any errors. I can run the final app from the command line by using java -cp {path to the JOGL libs} texQuads/main and it runs fine. So it seems to work. But... When I try to run the JAR file, it throws up: run-jar: [java] Exception in thread "main" java.lang.NoClassDefFoundError: javax/media/opengl/GLEventListener [java] at java.lang.ClassLoader.defineClass1(Native Method) [java] at java.lang.ClassLoader.defineClass(ClassLoader.java:643) [java] at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142) [java] at java.net.URLClassLoader.defineClass(URLClassLoader.java:277) [java] at java.net.URLClassLoader.access$000(URLClassLoader.java:73) [java] at java.net.URLClassLoader$1.run(URLClassLoader.java:212) [java] at java.security.AccessController.doPrivileged(Native Method) [java] at java.net.URLClassLoader.findClass(URLClassLoader.java:205) [java] at java.lang.ClassLoader.loadClass(ClassLoader.java:323) [java] at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:294) [java] at java.lang.ClassLoader.loadClass(ClassLoader.java:268) [java] Caused by: java.lang.ClassNotFoundException: javax.media.opengl.GLEventListener [java] at java.net.URLClassLoader$1.run(URLClassLoader.java:217) [java] at java.security.AccessController.doPrivileged(Native Method) [java] at java.net.URLClassLoader.findClass(URLClassLoader.java:205) [java] at java.lang.ClassLoader.loadClass(ClassLoader.java:323) [java] at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:294) [java] at java.lang.ClassLoader.loadClass(ClassLoader.java:268) [java] ... 11 more [java] Could not find the main class: texQuads.Main. Program will exit. [java] Java Result: 1 So it seems that I cannot even run it with the class path set to my system libs :-/ What did I do wrong? |
Administrator
|
The value of the attribute Class-Path is probably wrong. Please read the documentation:
http://docs.oracle.com/javase/7/docs/technotes/guides/jar/jar.html#Main_Attributes It uses relative URLs, not absolute URLs. You should write "jogl.all.jar,jogl.test.jar" instead of "jogl.all.jar jogl.test.jar" (even though both should work). Using the packages provided with various GNU Linux distributions is a bad idea, they are often obsolete and they don't contain the JARs containing the native libraries. If you use them, you'll have to modify a lot the example I gave you.
Julien Gouesse | Personal blog | Website
|
I know. I just set it that way to test if the JAR file would run on my machine at the very least. As the next step I planned to put the libraries into the JAR file itself, as my original requirement is. I just need to know which of these JOGL library files need to be packed in the JAR for it to work on other machines, too. Thanks for the suggestion, I'll try with the relative paths too. |
Administrator
|
Actually, using the packages provided with various GNU Linux distributions is a bad idea even for development purposes.
Maybe you can simply copy the JOGL JARs into the same directory than the JAR you created (modify the Class-Path attribute too) and you will need to set the Java library path for the native libraries. The example I gave you shows which JARs are necessary. If you create one JAR per platform, you'll have to hope that the end user won't pick the wrong one and actually, it will occupy much more space on your server than a single JAR. Moreover, some people install the 32-bit JVM on a 64-bit system which is another source of confusion...
Julien Gouesse | Personal blog | Website
|
Perhaps, but it's not the issue right now, as long as the non-JAR version of my test app runs fine.
Yeah, this way it works fine. Now how to make it run with these JARs embedded into my app's JAR file? When I put the library JARs inside the app's JAR, they are not seen by the class loader.
OK, I pretty much learned how to find out which JARs are needed by opening them and looking through them for the class from the error message. Maybe later I'll figure out how to repackage them. But for now, the main issue is how to run the app with all the libraries supplied in the JARs packed inside the main single stand-alone JAR file instead of supplying them separately and having the user copy it into the same directory with my app's main JAR. Unfortunately, this quote from the Java docs doesn't seem very promising :-/
Unfortunately, they don't explain much about how such "custom code" should look like and how to do it.
Perhaps I could pre-select it for him by detecting the platform supplied by his browser. That's how many corporate websites do it (such as Oracle with Java, Adobe with Flash etc.). This is not an issue right now. So let's not get into unnecessary details for now, OK? The main issue right now is how to make the JAR self-contained. |
Administrator
|
Which version of JOGL does Gentoo provide? As far as I know, only Debian (thanks to Sylvestre) provides JOGL 2.2.4 right now, other distros provide only JOGL 2.0.2 and JOGL 1.1.1. If something goes wrong, we'll be able to help you only if you use the very latest version. Then, you're not creating a fat JAR, you're trying to apply the method called "Jar-in-Jar", it's doable with or without Eclipse: http://jogamp.org/wiki/index.php/JogAmp_JAR_File_Handling#Eclipse However, I haven't tested it, I can't help you and if you have a very old version of JOGL 2, it won't work. Please look at the bottom of the page above, it shows how to set the classpath in this case. This problem is already solved in JOGL but please use the example I gave you as is first. If you complicate things now, you will never succeed in making it work. Don't get into unnecessary complications and you'll make your self-contained JAR very soon. The very first example I gave you is rudimentary, it makes a single JAR from GlueGen, JOGL and your application JAR: http://forum.jogamp.org/Fat-jar-deployment-method-ant-recipe-tp4032213p4032229.html
Julien Gouesse | Personal blog | Website
|
You're right. The version I installed a couple of days ago (which I thought will be the most recent), is jogl-2.0_rc8-r1
OK, so I downloaded the 2.2.4, branch "origin/b22x" commit "78f641de80d1c37cd61e5300eeba369c6aa9b1a1" version from the JOGL website and put the selected JAR libs into my project's
So that's where we misunderstood each other: when you were speaking about "fat JAR", I thought it means putting the library JARs into my app's JAR and loading them from there. Now I see that I was supposed to repack all the particular files into a directory structure into my app's JAR. So I did, and it seems to work now. Starting up the application. Exception in thread "main" java.lang.UnsatisfiedLinkError: Can't load library: C:\Users\drott_000\Documents\gluegen-rt.dll at java.lang.ClassLoader.loadLibrary(Unknown Source) at java.lang.Runtime.load0(Unknown Source) at java.lang.System.load(Unknown Source) at com.jogamp.common.jvm.JNILibLoaderBase.loadLibraryInternal(JNILibLoaderBase.java:596) at com.jogamp.common.jvm.JNILibLoaderBase.access$000(JNILibLoaderBase.java:64) at com.jogamp.common.jvm.JNILibLoaderBase$DefaultAction.loadLibrary(JNILibLoaderBase.java:96) at com.jogamp.common.jvm.JNILibLoaderBase.loadLibrary(JNILibLoaderBase.java:459) at com.jogamp.common.os.DynamicLibraryBundle$GlueJNILibLoader.loadLibrary(DynamicLibraryBundle.java:388) at com.jogamp.common.os.Platform$1.run(Platform.java:209) at java.security.AccessController.doPrivileged(Native Method) at com.jogamp.common.os.Platform. from one user, and the following from another user: c:\> java -jar TextureQuads.jar Starting up the application. Exception in thread "main" java.lang.UnsatisfiedLinkError: Can't load library: c:\\gluegen-rt.dll at java.lang.ClassLoader.loadLibrary(Unknown Source) at java.lang.Runtime.load0(Unknown Source) at java.lang.System.load(Unknown Source) at com.jogamp.common.jvm.JNILibLoaderBase.loadLibraryInternal(JNILibLoaderBase.java:596) at com.jogamp.common.jvm.JNILibLoaderBase.access$000(JNILibLoaderBase.java:64) at com.jogamp.common.jvm.JNILibLoaderBase$DefaultAction.loadLibrary(JNILibLoaderBase.java:96) at com.jogamp.common.jvm.JNILibLoaderBase.loadLibrary(JNILibLoaderBase.java:459) at com.jogamp.common.os.DynamicLibraryBundle$GlueJNILibLoader.loadLibrary(DynamicLibraryBundle.java:388) at com.jogamp.common.os.Platform$1.run(Platform.java:209) at java.security.AccessController.doPrivileged(Native Method) at com.jogamp.common.os.Platform. The "Starting up the application." piece comes from my app's I have the Windows native libs in my JAR, I checked. Here's how it looks like: $ jar tf build/jar/TextureQuads.jar natives natives/ natives/linux-amd64/ natives/linux-amd64/libjogl_desktop.so natives/linux-amd64/libjogl_mobile.so natives/linux-amd64/libnativewindow_awt.so natives/linux-amd64/libnativewindow_x11.so natives/linux-amd64/libnewt.so natives/linux-amd64/libgluegen-rt.so natives/windows-amd64/ natives/windows-amd64/jogl_desktop.dll natives/windows-amd64/jogl_mobile.dll natives/windows-amd64/nativewindow_awt.dll natives/windows-amd64/nativewindow_win32.dll natives/windows-amd64/newt.dll natives/windows-amd64/gluegen-rt.dll natives/macosx-universal/ natives/macosx-universal/libjogl_desktop.jnilib natives/macosx-universal/libjogl_mobile.jnilib natives/macosx-universal/libnativewindow_awt.jnilib natives/macosx-universal/libnativewindow_macosx.jnilib natives/macosx-universal/libnewt.jnilib natives/macosx-universal/libgluegen-rt.jnilib so everything seems to be in place. But Windows says otherwise...
Well, though I'm an experienced programmer in C++ and a bunch of other languages, I'm still a noob when it comes to Java. I learn how to use JOGL since a week so far, and it's still hard to me to grasp some of these things. I already wasted a couple of days trying to make it compile, and then run, by setting up all these classpaths & stuff and testing lots of different combinations. Then I wasted another couple of days trying to figure out how to make those JARs according to your directions. Although I appreciate your help, I must admit that the information you provide is very scarce and enigmatic, it is all very poorly documented (otherwise I would figure it out myself from reading the docs & wikis). I know that you have a clear picture in your head of how it all works, but I don't, and I have a hard time having to actively dig these informations out from your head, and figuring out what question should I ask next to get another important piece you forgot to tell me.
That's the point: I don't know which solutions are "unnecessary complications" as long as I don't have a clear picture of how it all works internally. I must do a lot of experimenting in a trial-and-error approach and waste a lot of time – a time which could be saved if you state all these little details clearly from the very beginning instead of waiting for me to dig them up from your head. I'm not clairvoyant yet. One of these important details I missed was the fact that JOGL has its own fancy mechanism for loading these native libraries from the JAR file, and that they have to be put in special locations with hard-coded names to make it work. Not knowing that, I must have to go through several failed attempts with different combinations of these paths before it finally worked. I did read the links you provided, and I did use the <zipfileset> trick, just in a wrong way, with wrong paths. But that was only after you enlightened me that you didn't mean putting entire JARs into my app's JAR under the "fat JAR" name, but repacking them as plain files in a particular directory structure. You could tell me that a couple of posts ago, when I first asked about putting these JARs into my app's JAR, and you would save me some random erring then. So is it me who makes unnecessary complications? Or is it the whole jumping through hoops I experience here which actually makes it harder? |
Administrator
|
Oh my .... Both Jar-in-Jar, multi-Jar and fat JARs aren't supported by JOGL 2.0. It's far better. You forgot to put the 32-bit native libraries into your fat JAR. Keep in mind that it is possible to use a 32-bit JRE on a 64-bit operating system. A 32-bit JRE on a 64-bit machine requires 32-bit binaries. I just put a link to 20 lines of XML (Ant script) that you could simply copy/paste/modify to do the job. I think that our misunderstanding (fat JAR vs Jar-in-Jar) made us waste some time. Be patient, you're close to the aim. I warned you several times but you'll probably have some other troubles even with a fat JAR. For example, imagine that an end user tries to use an old Apple JRE 1.6 whereas your code uses Java 1.7... I hope that the extraction of the native libraries doesn't fail in some cases under Windows.
Julien Gouesse | Personal blog | Website
|
Then again, this is an important piece of information, so it should be documented. If I saw it in the documentation, I would upgrade my libs right away, because then I'd have a reason for doing it and going around my distro's package manager. Good point. And it actually solved the problem! :) Now my phat JAR runs correctly on all platforms I planned supporting. But I'll need to think on a way to make it somewhat less phat, since it already weighs nearly 5 MB There's a strange bug on one of my friend's Macs, though: It starts correctly with all the libs, it loads a texture, but then it doesn't display on the color background. The background quad with just some colors is rendering fine, just not the textured quad. I wonder why, since it doesn't print any errors nor throws any exceptions, so I guess the image is loaded correctly, and it is probably something on the side of OpenGL. But it's not related to JAR files, so I should perhaps save it for another day, another thread, unless I figure out the cause myself... Yeah, and that's what I did, and it pretty much worked. But only after I understood that this is what I actually need to do. Yes, I keep your warnings in mind. I just decided to save it for later, until the problems will actually occur. For now, after the last correction with win32 libs, it seems to work as I expected. So thanks for all your help. |
Administrator
|
Keep in mind that the JogAmp community is NOT responsible for the GNU Linux distros packaging management. I always ask the developers to use our very latest version, always. This is one of the first things I asked you to do. Maybe we should fix your other problems first, shouldn't we? Look at how you generate the identifiers of your textures. It's just a guess. That's why I'm here, I'm here to transmit you some of my knowledge so that you get more and more autonomous. I have nothing to sell. You're welcome and I'll be there to help you. If you aim only recent Macs and if your user base isn't very silly, it should be ok.
Julien Gouesse | Personal blog | Website
|
Free forum by Nabble | Edit this page |