mabe02 / lanterna

Java library for creating text-based GUIs
GNU Lesser General Public License v3.0
2.3k stars 243 forks source link

GraalVM: ClassNotFoundException: com.googlecode.lanterna.gui2.WindowShadowRenderer #584

Open damnms opened 1 year ago

damnms commented 1 year ago

Hi, i have a little application and would love to create native images with graalvm. Unfortunately, when i run the built native-image, i get:

Exception in thread "main" java.lang.ExceptionInInitializerError
        at com.googlecode.lanterna.gui2.AbstractTextGUI.<init>(AbstractTextGUI.java:60)
        at com.googlecode.lanterna.gui2.MultiWindowTextGUI.<init>(MultiWindowTextGUI.java:169)
        at com.googlecode.lanterna.gui2.MultiWindowTextGUI.<init>(MultiWindowTextGUI.java:76)
        at com.googlecode.lanterna.gui2.MultiWindowTextGUI.<init>(MultiWindowTextGUI.java:65)
        at jsfdl.adapters.CursesWindow.<init>(CursesWindow.java:52)
        at jsfdl.ApplicationConfiguration.getGui(ApplicationConfiguration.java:62)
        at jsfdl.adapters.GuiApplication.main(GuiApplication.java:10)
        at java.base@21.0.1/java.lang.invoke.LambdaForm$DMH/sa346b79c.invokeStaticInit(LambdaForm$DMH)
Caused by: java.lang.RuntimeException: java.lang.ClassNotFoundException: com.googlecode.lanterna.gui2.WindowShadowRenderer
        at com.googlecode.lanterna.graphics.AbstractTheme.instanceByClassName(AbstractTheme.java:154)
        at com.googlecode.lanterna.graphics.PropertyTheme.<init>(PropertyTheme.java:66)
        at com.googlecode.lanterna.bundle.DefaultTheme.<init>(DefaultTheme.java:11)
        at com.googlecode.lanterna.bundle.LanternaThemes.<clinit>(LanternaThemes.java:52)
        ... 8 more
Caused by: java.lang.ClassNotFoundException: com.googlecode.lanterna.gui2.WindowShadowRenderer
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:122)
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:86)
        at java.base@21.0.1/java.lang.Class.forName(DynamicHub.java:1346)
        at java.base@21.0.1/java.lang.Class.forName(DynamicHub.java:1309)
        at java.base@21.0.1/java.lang.Class.forName(DynamicHub.java:1302)
        at com.googlecode.lanterna.graphics.AbstractTheme.instanceByClassName(AbstractTheme.java:152)
        ... 11 more

line 52 of CursesWindow is: final WindowBasedTextGUI textGUI = new MultiWindowTextGUI(screen);

I assume its because of the dynamic loading in AbstractTheme.java:152, wondering if i can somehow manually "add" those classes that are required (for linux/windows/mac) to graalvm, so they are packaged.

avl42 commented 1 year ago

Renderer classes are not directly accessed in Java-code, but dynamically loaded "by name" at runtime.

If you have a build-tool that will only pick classes that are "accessed", then these tools will not see the necessity of those extra classes, and thus not include them.

One workaround is, to directly use those Renderer-classes that you appear to need(based on thrown exceptions), in your own code, or maybe you can tell the build tool to add a given list of classes always - regardless of any analyzed need for those classes.

On Wed, Oct 25, 2023 at 10:17 AM damnms @.***> wrote:

Hi, i have a little application and would love to create native images with graalvm. Unfortunately, when i run the built native-image, i get:

Exception in thread "main" java.lang.ExceptionInInitializerError at com.googlecode.lanterna.gui2.AbstractTextGUI.(AbstractTextGUI.java:60) at com.googlecode.lanterna.gui2.MultiWindowTextGUI.(MultiWindowTextGUI.java:169) at com.googlecode.lanterna.gui2.MultiWindowTextGUI.(MultiWindowTextGUI.java:76) at com.googlecode.lanterna.gui2.MultiWindowTextGUI.(MultiWindowTextGUI.java:65) at jsfdl.adapters.CursesWindow.(CursesWindow.java:52) at jsfdl.ApplicationConfiguration.getGui(ApplicationConfiguration.java:62) at jsfdl.adapters.GuiApplication.main(GuiApplication.java:10) at @./java.lang.invoke.LambdaForm$DMH/sa346b79c.invokeStaticInit(LambdaForm$DMH) Caused by: java.lang.RuntimeException: java.lang.ClassNotFoundException: com.googlecode.lanterna.gui2.WindowShadowRenderer at com.googlecode.lanterna.graphics.AbstractTheme.instanceByClassName(AbstractTheme.java:154) at com.googlecode.lanterna.graphics.PropertyTheme.(PropertyTheme.java:66) at com.googlecode.lanterna.bundle.DefaultTheme.(DefaultTheme.java:11) at com.googlecode.lanterna.bundle.LanternaThemes.(LanternaThemes.java:52) ... 8 more Caused by: java.lang.ClassNotFoundException: com.googlecode.lanterna.gui2.WindowShadowRenderer at org.graalvm.nativeimage.builder/com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:122) at org.graalvm.nativeimage.builder/com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:86) at @./java.lang.Class.forName(DynamicHub.java:1346) at @./java.lang.Class.forName(DynamicHub.java:1309) at @./java.lang.Class.forName(DynamicHub.java:1302) at com.googlecode.lanterna.graphics.AbstractTheme.instanceByClassName(AbstractTheme.java:152) ... 11 more

line 52 of CursesWindow is: final WindowBasedTextGUI textGUI = new MultiWindowTextGUI(screen); So i assume its something with MultiWindowTextGUI, but thats just me guessing :)

— Reply to this email directly, view it on GitHub https://github.com/mabe02/lanterna/issues/584, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABIDBMSWK6WMJG2ZHRERL3TYBDDLBAVCNFSM6AAAAAA6O6IQH2VHI2DSMVQWIX3LMV43ASLTON2WKOZRHE3DAOBRHA4DMNI . You are receiving this because you are subscribed to this thread.Message ID: @.***>

damnms commented 1 year ago

all classes thare are instantiated by reflection are: com.googlecode.lanterna.gui2.WindowShadowRenderer com.googlecode.lanterna.gui2.FatWindowDecorationRenderer com.googlecode.lanterna.gui2.Button$DefaultButtonRenderer

any idea how to use the direct renderer classes? guess thats the easiest workaround/solution

avl42 commented 1 year ago

I'd really suggest adding the classes to some "white-list" of the build tool. That would be the cleanest solution.

If you don't want to deal with the specifics of the build-tool, then you can try the following tricks: To a class that is already included in the build, like e.g. the main class of your project, you add a dummy() method like:

public int pullIn() {
   return WindowShadowRenderer.class.toString().length() +
          FatWindowDecorationRenderer.class.toString().length() +
          ...;
}

Try it - I'd think it will already pull in all these classes - don't forget the import for each fully qualified classname.

If some tooling complains about the unused method dummy(), you can call dummy() and compare it's result to be greater-or-equal to 0, which is a pretty safe bet, so you can make some parts of your main method dependent on whether dummy() >= 0 .

Not very nice, sure, so that's why I'd rather suggest manually configuring the build-tool to just unconditionally include those classes.

On Wed, Oct 25, 2023 at 3:04 PM damnms @.***> wrote:

all classes thare are instantiated by reflection are: com.googlecode.lanterna.gui2.WindowShadowRenderer com.googlecode.lanterna.gui2.FatWindowDecorationRenderer com.googlecode.lanterna.gui2.Button$DefaultButtonRenderer

any idea how to use the direct renderer classes? guess thats the easiest workaround/solution

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you commented.Message ID: @.***>

damnms commented 1 year ago

my build tool is gradle, i dont know how i can "whitelist" some classes. i guess that has to be done in the graalvm native image task.

i tried adding those classes that i need in my code, but that did not work: i added System.out.println(GuiApplication.pullIn());//TODO: so nasty... :P in my static main, and the method contains:

       private static String pullIn() {
        return com.googlecode.lanterna.gui2.WindowShadowRenderer.class.toString() +
                com.googlecode.lanterna.gui2.FatWindowDecorationRenderer.class.toString() +
                com.googlecode.lanterna.gui2.Button.DefaultButtonRenderer.class.toString();
    }

when i run the compiled image, it says:

oli@fedora:~/IdeaProjects/jsfdlloader/build/native/nativeCompile$ ./jsfdlloader 
class com.googlecode.lanterna.gui2.WindowShadowRendererclass com.googlecode.lanterna.gui2.FatWindowDecorationRendererclass com.googlecode.lanterna.gui2.Button$DefaultButtonRenderer
Exception in thread "main" java.lang.ExceptionInInitializerError
        at com.googlecode.lanterna.gui2.AbstractTextGUI.<init>(AbstractTextGUI.java:60)
        at com.googlecode.lanterna.gui2.MultiWindowTextGUI.<init>(MultiWindowTextGUI.java:169)
        at com.googlecode.lanterna.gui2.MultiWindowTextGUI.<init>(MultiWindowTextGUI.java:76)
        at com.googlecode.lanterna.gui2.MultiWindowTextGUI.<init>(MultiWindowTextGUI.java:65)
        at org.gitlab.jsfdl.adapters.CursesWindow.<init>(CursesWindow.java:52)
        at org.gitlab.jsfdl.ApplicationConfiguration.getGui(ApplicationConfiguration.java:62)
        at org.gitlab.jsfdl.adapters.GuiApplication.main(GuiApplication.java:16)
        at java.base@21.0.1/java.lang.invoke.LambdaForm$DMH/sa346b79c.invokeStaticInit(LambdaForm$DMH)
Caused by: java.lang.RuntimeException: java.lang.ClassNotFoundException: com.googlecode.lanterna.gui2.WindowShadowRenderer
        at com.googlecode.lanterna.graphics.AbstractTheme.instanceByClassName(AbstractTheme.java:154)
        at com.googlecode.lanterna.graphics.PropertyTheme.<init>(PropertyTheme.java:66)
        at com.googlecode.lanterna.bundle.DefaultTheme.<init>(DefaultTheme.java:11)
        at com.googlecode.lanterna.bundle.LanternaThemes.<clinit>(LanternaThemes.java:52)
        ... 8 more
Caused by: java.lang.ClassNotFoundException: com.googlecode.lanterna.gui2.WindowShadowRenderer
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:122)
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:86)
        at java.base@21.0.1/java.lang.Class.forName(DynamicHub.java:1346)
        at java.base@21.0.1/java.lang.Class.forName(DynamicHub.java:1309)
        at java.base@21.0.1/java.lang.Class.forName(DynamicHub.java:1302)
        at com.googlecode.lanterna.graphics.AbstractTheme.instanceByClassName(AbstractTheme.java:152)
        ... 11 more

so i guess something is not working correctly. maybe the compiler removes that by some sort of optimization algorithm, no idea...

damnms commented 1 year ago

to make sure its not optimized away i used real instances and got the following outpu: oli@fedora:~/IdeaProjects/jsfdlloader/build/native/nativeCompile$ ./jsfdlloader com.googlecode.lanterna.gui2.WindowShadowRenderer@366774a6com.googlecode.lanterna.gui2.FatWindowDecorationRenderer@747e2db8com.googlecode.lanterna.gui2.Button$DefaultButtonRenderer@25ba116b then the exceptions... as above

so the classes are there, they can be instantiated. seems like this is something else thats not working.

damnms commented 1 year ago

seems like https://github.com/mabe02/lanterna/issues/521 is the same problem

avl42 commented 1 year ago

ah, that's why i had that deja-vu...

so, you still get the same classnotfound exception?

what kind of "executable" does gradle produce? is it still essentially a zipfile, or something completely different? Does such a gradle program thing even support dynamic loading of classes in the first place?

damnms @.***> schrieb am So., 29. Okt. 2023, 09:52:

seems like #521 https://github.com/mabe02/lanterna/issues/521 is the same problem

— Reply to this email directly, view it on GitHub https://github.com/mabe02/lanterna/issues/584#issuecomment-1784038100, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABIDBMQ7IISW3I4C2J5WJYLYBYDL5AVCNFSM6AAAAAA6O6IQH2VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTOOBUGAZTQMJQGA . You are receiving this because you commented.Message ID: @.***>

damnms commented 1 year ago

gradle does not really produce an executable, its just like maven. it has some addons/plugins that can e.g. create a shadowjar (aka fatjar). and gradle can handle graalvm - which really produces a standalone application. so the problem is in graalvm's plugin for gradle, not gradle itself. at least for me it looks like that.

oli@fedora:~/IdeaProjects/jsfdlloader/build/native/nativeCompile$ file jsfdlloader 
jsfdlloader: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=c93142e2698f5f940ea91930f8b350512ebecb86, for GNU/Linux 3.2.0, with debug_info, not stripped

its a real executable and yes, graalvm supports reflection: https://www.graalvm.org/22.2/reference-manual/native-image/guides/build-with-reflection/

when i grep that binary or search with vim, i find some of those WindowShadowRenderer, so i am really lost whats going on

damnms commented 1 year ago

i think i got it working... i was not sure if the tutorial also works with jar files, but it does https://www.graalvm.org/22.2/reference-manual/native-image/guides/build-with-reflection/

first i created the shadow jar, which produces the mentioned errors above (classnotfound etc.). so i executed the command like in the tutorial but a bit different. i went into my $PROJECTROOT/build/libs and created a META-INF/native-image directory, then: graalvm-jdk-21.0.1+12.1/bin/java -agentlib:native-image-agent=config-output-dir=META-INF/native-image -jar myapplication-all.jar that produces some files in META-INF/native-image, which i moved then to $PROJECT_ROOT/src/main/resources. With those, i re-created the shadowJar and checked that those files are inside the .jar under META-INF/native-image. Then i was able to run graalvm-jdk-21.0.1+12.1/bin/native-image -jar myapplication-all.jar which produces the native-image. so seems like, its a must have to have those configuration generated by graalvm. not sure if there is another way.