JFormDesigner / FlatLaf

FlatLaf - Swing Look and Feel (with Darcula/IntelliJ themes support)
https://www.formdev.com/flatlaf/
Apache License 2.0
3.46k stars 273 forks source link

FlatLaf and Proguard ? #648

Closed x9ware-llc closed 1 year ago

x9ware-llc commented 1 year ago

Has anyone used Proguard against FlatLaf? I currently just keep everything since I was unable to successfully apply any optimization or obfuscation. Here is the keep everything that in my existing definition: -keep class com.formdev.* { ; }. I can obviously drop the line number table and then I also use -injars '....[filereference]....\flatlaf-2.6.jar'(!com/formdev/flatlaf/natives/**) to drop the embedded DLLs. However, it would be great to be able to do more if possible.

MetalAZ commented 1 year ago

I was playing around with Swing for a while last year and combined a few things together into an example project, which included FlatLaf for the look and feel, light/dark theme detector on startup, packaging for deployment, and ProGuard obfuscation. If I remember correctly, I was unable to get obfuscation working with making a fat jar but I didn't know ProGuard at all or Swing or Java that well. Not sure if that'll help you or not but you can check out the POM file in the project to see what I did. https://github.com/MetalAZ/swing-app

DevCharly commented 1 year ago

It doesn't make much sense to obfuscate an open-source library... 😉

Maybe it makes the library a little bit smaller because of shorter names. So maybe you save 100-200 kB.

Swing uses reflection to create UI delegates. FlatLaf also uses reflection to instantiate borders/icons/etc defined in properties files. FlatLaf styling also uses reflection to access fields. Maybe obfuscation works if you keep all public and protected classes/methods/fields...

ProGuard optimization (without obfuscation) could work. But I doubt that this will make a noticeable difference...

x9ware-llc commented 1 year ago

I hear you and agree on all points. We reduced the FlatLaf jar size by about 140kb when we dropped the DLL and line numbers. I asked the question really just to see if anyone else is using ProGuard with FlatLaf. We use it against our entire application fatjar, with some overrides needed for things like Apache POI and JAXB. Completely excluding FlatLaf from the obfuscation makes it a bit of an outlier; it would be nice if it could be included. I realize that there is no intellectual property requirement, and that there is probably no more than a 100kb reduction to be obtained. I also concur that a first step would be to enable optimization with obfuscation turned off.

itsRacmo commented 1 year ago

How would I make sure I exclude flatlaf from obfuscation? I tried with: -keep class com.formdev.flatlaf.* { ; } But that did not seem to resolve the problem.

x9ware-llc commented 1 year ago

I am using these Proguard statements. There is not much to be gained here, since obviously FlatLaf is small in the first place. I am also preprocessing the FlatLaf jar to drop line numbers and the DLL, since I would rather have the DLL excluded. Dropping the DLL works with 2.6 and then thanks to the FlatLaf support team it will work again in the next release. It seems the Proguard problems revolve around that the design that calls in the various UI components by name via the properties file, which makes then unreferenced within the analyzed class usage tree. This causes Proguard to believe they are not used and hence drops those classes from the resulting output jar. As a resolution for this, I have seen other applications explicitly reference their defaults and then use properties as an override mechanism. This will be a future issue if we ever move to native image, such as GraalVM, since it similarly inspects for classes that are explicitly used by the application and by default drops everything else.

-keep class com.formdev.flatlaf., com.formdev.flatlaf.ui., com.formdev.flatlaf.resources., com.formdev.flatlaf.icons., com.formdev.*..properties { *; }

itsRacmo commented 1 year ago

Thanks a lot for your reply, I appreciate this a lot. I tried using proguard with those properties in mind, but I think it still deletes some "unused" methods.

This is what my proguard.conf looks like:

-injars 'C:\Users\Andy\IdeaProjects\Racmo\target\Racmo-1.0-SNAPSHOT-jar-with-dependencies.jar'
-outjars OBFRACMO

-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\java.base.jmod'
-libraryjars 'C:\Users\Andy\IdeaProjects\Racmo\target\libs\flatlaf-3.1.1.jar'
-libraryjars 'C:\Users\Andy\IdeaProjects\Racmo\target\libs\flatlaf-intellij-themes-3.1.1.jar'
-libraryjars 'C:\Users\Andy\IdeaProjects\Racmo\target\libs\gson-2.10.1.jar'
-libraryjars 'C:\Users\Andy\IdeaProjects\Racmo\target\libs\jama-1.0.2.jar'
-libraryjars 'C:\Users\Andy\IdeaProjects\Racmo\target\libs\javafx-base-21-ea+23-win.jar'
-libraryjars 'C:\Users\Andy\IdeaProjects\Racmo\target\libs\javafx-base-21-ea+23.jar'
-libraryjars 'C:\Users\Andy\IdeaProjects\Racmo\target\libs\jna-5.8.0.jar'
-libraryjars 'C:\Users\Andy\IdeaProjects\Racmo\target\libs\jna-platform-5.8.0.jar'
-libraryjars 'C:\Users\Andy\IdeaProjects\Racmo\target\libs\jnativehook-2.1.0.jar'
-libraryjars 'C:\Users\Andy\IdeaProjects\Racmo\target\libs\json-20230618.jar'
-libraryjars 'C:\Users\Andy\IdeaProjects\Racmo\target\libs\lombok-1.18.28.jar'
-libraryjars 'C:\Users\Andy\IdeaProjects\Racmo\target\libs\naturalmouse-2.0.3.jar'
-libraryjars 'C:\Users\Andy\IdeaProjects\Racmo\target\libs\opencv-4.7.0-0.jar'
-libraryjars 'C:\Users\Andy\IdeaProjects\Racmo\target\libs\pca_transform-1.0.2.jar'
-libraryjars 'C:\Users\Andy\IdeaProjects\Racmo\target\libs\slf4j-api-1.7.25.jar'
-libraryjars 'C:\Users\Andy\IdeaProjects\Racmo\target\libs\slf4j-nop-1.7.30.jar'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\java.compiler.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\java.datatransfer.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\java.desktop.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\java.instrument.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\java.logging.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\java.management.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\java.management.rmi.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\java.naming.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\java.net.http.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\java.prefs.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\java.rmi.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\java.scripting.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\java.se.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\java.security.jgss.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\java.security.sasl.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\java.smartcardio.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\java.sql.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\java.sql.rowset.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\java.transaction.xa.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\java.xml.crypto.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\java.xml.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.accessibility.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.attach.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.charsets.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.compiler.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.crypto.cryptoki.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.crypto.ec.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.crypto.mscapi.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.dynalink.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.editpad.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.hotspot.agent.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.httpserver.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.incubator.foreign.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.incubator.vector.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.internal.ed.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.internal.jvmstat.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.internal.le.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.internal.opt.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.internal.vm.ci.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.internal.vm.compiler.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.internal.vm.compiler.management.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.jartool.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.javadoc.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.jcmd.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.jconsole.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.jdeps.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.jdi.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.jdwp.agent.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.jfr.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.jlink.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.jpackage.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.jshell.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.jsobject.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.jstatd.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.localedata.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.management.agent.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.management.jfr.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.management.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.naming.dns.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.naming.rmi.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.net.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.nio.mapmode.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.random.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.sctp.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.security.auth.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.security.jgss.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.unsupported.desktop.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.unsupported.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.xml.dom.jmod'
-libraryjars 'C:\Program Files\Java\jdk-18.0.1.1\jmods\jdk.zipfs.jmod'

-keep class com.formdev.flatlaf.,
com.formdev.flatlaf.ui.,
com.formdev.flatlaf.resources.,
com.formdev.flatlaf.icons.,
com.formdev.**.*.properties,
com.formdev.***** { *; }

# Keep - Applications. Keep all application classes, along with their 'main' methods.
-keepclasseswithmembers public class * {
    public static void main(java.lang.String[]);
}

# Also keep - Enumerations. Keep the special static methods that are required in
# enumeration classes.
-keepclassmembers enum  * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

# Also keep - Database drivers. Keep all implementations of java.sql.Driver.
-keep class * extends java.sql.Driver

# Also keep - Swing UI L&F. Keep all extensions of javax.swing.plaf.ComponentUI,
# along with the special 'createUI' method.
-keep class * extends javax.swing.plaf.ComponentUI {
    public static javax.swing.plaf.ComponentUI createUI(javax.swing.JComponent);
}

# Keep - Native method names. Keep all native class/method names.
-keepclasseswithmembers,includedescriptorclasses,allowshrinking class * {
    native <methods>;
}

# Keep - Applications. Keep all application classes, along with their 'main' methods.
-keepclasseswithmembers public class * {
    public static void main(java.lang.String[]);
}

# Also keep - Enumerations. Keep the special static methods that are required in
# enumeration classes.
-keepclassmembers enum  * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

# Also keep - Database drivers. Keep all implementations of java.sql.Driver.
-keep class * extends java.sql.Driver

# Also keep - Swing UI L&F. Keep all extensions of javax.swing.plaf.ComponentUI,
# along with the special 'createUI' method.
-keep class * extends javax.swing.plaf.ComponentUI {
    public static javax.swing.plaf.ComponentUI createUI(javax.swing.JComponent);
}

# Keep - Native method names. Keep all native class/method names.
-keepclasseswithmembers,includedescriptorclasses,allowshrinking class * {
    native <methods>;
}

I still get this warning:

Warning: com.formdev.flatlaf.FlatLaf: can't find referenced method 'java.lang.Object invoke(javax.swing.JComponent)' in library class java.lang.invoke.MethodHandle
Warning: com.formdev.flatlaf.ui.FlatPopupFactory: can't find referenced method 'javax.swing.Popup invoke(com.formdev.flatlaf.ui.FlatPopupFactory,java.awt.Component,java.awt.Component,int,int,boolean)' in library class java.lang.invoke.MethodHandle
Warning: com.formdev.flatlaf.ui.FlatPopupFactory: can't find referenced method 'javax.swing.Popup invoke(com.formdev.flatlaf.ui.FlatPopupFactory,java.awt.Component,java.awt.Component,int,int,int)' in library class java.lang.invoke.MethodHandle
Warning: com.formdev.flatlaf.ui.FlatStylingSupport: can't find referenced method 'java.lang.Object invoke(java.lang.Object)' in library class java.lang.invoke.MethodHandle
Warning: com.formdev.flatlaf.ui.FlatStylingSupport: can't find referenced method 'void invoke(java.lang.Object,java.lang.Object)' in library class java.lang.invoke.MethodHandle
Warning: com.formdev.flatlaf.util.JavaCompatibility: can't find referenced method 'void invoke(javax.swing.JComponent,java.awt.Graphics2D,java.lang.String,int,float,float)' in library class java.lang.invoke.MethodHandle
Warning: com.formdev.flatlaf.util.JavaCompatibility: can't find referenced method 'void invoke(javax.swing.JComponent,java.awt.Graphics,java.lang.String,int,int,int)' in library class java.lang.invoke.MethodHandle
Warning: com.formdev.flatlaf.util.JavaCompatibility: can't find referenced method 'java.lang.String invoke(javax.swing.JComponent,java.awt.FontMetrics,java.lang.String,int)' in library class java.lang.invoke.MethodHandle

If I would ignore the warning and run the jar i get:

Exception in thread "main" java.lang.NoSuchMethodError: fireStateChangedLaterOnce
        at com.formdev.flatlaf.ui.FlatWindowsNativeWindowBorder$WndProc.installImpl(Native Method)
        at com.formdev.flatlaf.ui.FlatWindowsNativeWindowBorder$WndProc.<init>(Unknown Source)
        at com.formdev.flatlaf.ui.FlatWindowsNativeWindowBorder.setHasCustomDecoration(Unknown Source)
        at com.formdev.flatlaf.ui.FlatNativeWindowBorder.setHasCustomDecoration(Unknown Source)
        at com.formdev.flatlaf.ui.FlatNativeWindowBorder.a(Unknown Source)
        at com.formdev.flatlaf.ui.FlatNativeWindowBorder.a(Unknown Source)
        at java.desktop/java.beans.PropertyChangeSupport.fire(PropertyChangeSupport.java:343)
        at java.desktop/java.beans.PropertyChangeSupport.firePropertyChange(PropertyChangeSupport.java:336)
        at java.desktop/java.beans.PropertyChangeSupport.firePropertyChange(PropertyChangeSupport.java:268)
        at java.desktop/java.awt.Component.firePropertyChange(Component.java:8712)
        at java.desktop/javax.swing.JComponent.addNotify(JComponent.java:4847)
        at java.desktop/javax.swing.JRootPane.addNotify(JRootPane.java:721)
        at java.desktop/java.awt.Container.addNotify(Container.java:2804)
        at java.desktop/java.awt.Window.addNotify(Window.java:791)
        at java.desktop/java.awt.Dialog.addNotify(Dialog.java:770)
        at java.desktop/java.awt.Window.pack(Window.java:829)
        at java.desktop/javax.swing.JOptionPane.initDialog(JOptionPane.java:998)
        at java.desktop/javax.swing.JOptionPane.createDialog(JOptionPane.java:979)
        at java.desktop/javax.swing.JOptionPane.showInputDialog(JOptionPane.java:590)
        at java.desktop/javax.swing.JOptionPane.showInputDialog(JOptionPane.java:534)
        at java.desktop/javax.swing.JOptionPane.showInputDialog(JOptionPane.java:482)
        at java.desktop/javax.swing.JOptionPane.showInputDialog(JOptionPane.java:446)
        at com.andy.racmo.i.b.b(Unknown Source)
        at com.andy.racmo.i.b.<init>(Unknown Source)
        at com.andy.racmo.i.a.a(Unknown Source)
        at com.andy.racmo.Main.main(Unknown Source)

So I'm not sure why it doesn't keep everything as I specified in the conf, if you would have any other ideas or see a mistake I made, I appreciate it a lot, thanks.

itsRacmo commented 1 year ago

Update: I fixed it using: "-keep class com.formdev.flatlaf. { *; } -keep class com.formdev.flatlaf.ui.* { ; } -keep class com.formdev.flatlaf.resources. { *; } -keep class com.formdev.flatlaf.icons. { ; } -keep class java.lang.invoke.MethodHandle { ; } -keepclassmembers class { invoke*(...); }"

x9ware-llc commented 1 year ago

Glad you now have this working. As I had said earlier, I am not using the native DLL support, which is where you encountered this problem. This is why I never tested the presence of those native methods. Also FYI that your definition appears to have duplicate entries for application, enumerations, database drivers, swing, and native methods.