7

I'm struggling with obfuscation of JavaFX application. Using this project as a base:

https://github.com/openjfx/samples/tree/master/IDE/IntelliJ/Non-Modular/Gradle

Proguard throws this error:

java.io.IOException: Can't write [Path\infile.jar] (Can't read [Path\outfile.jar] (Duplicate jar entry [a.class]))

Proguard config file: -dontoptimize -dontshrink

-libraryjars 'E:\Prog\jdk-11.0.2\jmods'
-libraryjars 'E:\Prog\javafx-sdk\lib'

# Save meta-data for stack traces
-renamesourcefileattribute SourceFile
-keepattributes SourceFile,LineNumberTable

# Rename FXML files together with related views
-adaptresourcefilenames **.fxml,**.png,**.css
-adaptresourcefilecontents **.fxml
-adaptclassstrings

# Keep all annotations and meta-data
-keepattributes *Annotation*,Signature,EnclosingMethod

# Keep entry-point class
-keep classpackage.App {
    public static void main(java.lang.String[]);
}

# Keep all classes inside application
-keep,allowobfuscation class package.** {
}

# Keep names of fields marked with @FXML attribute
-keepclassmembers class * {
    @javafx.fxml.FXML *;
}

Anybody have experience with JavaFX obfuscation?

José Pereda
  • 44,311
  • 7
  • 104
  • 132
orzel
  • 176
  • 2
  • 9

1 Answers1

10

To get Proguard working with Java 11 we need:

  • The latest Proguard beta version, for Gradle in this case.

  • Modify the build gradle file to include the proguard task.

  • Add a proguard config file including the required changes for Java 11.

Build.gradle

Starting from the HelloFX sample, the build will be modified to:

// 1. Include proguard dependency
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'net.sf.proguard:proguard-gradle:6.1.0beta2'
    }
}

plugins {
  id 'application'
  id 'org.openjfx.javafxplugin' version '0.0.7'
}

repositories {
    mavenCentral()
}

dependencies {
}

javafx {
    modules = [ 'javafx.controls', 'javafx.fxml' ]
}

mainClassName = 'org.openjfx.MainApp'

Instead of just adding the proguard task, I'll add a few more tasks to replace the default build/classes with the proguarded ones. This helps inspecting the result.

// 2. Add tasks

// 2.1 Clean buildDir before running proguard
task cleanClasses(type: Delete) {
    delete "${buildDir}/classes/java/main"
    delete "${buildDir}/resources/java/main"
}

classes.dependsOn(cleanClasses)

// 2.2 Add proguard task
task proguard(type: proguard.gradle.ProGuardTask, dependsOn: classes) {
    injars project.sourceSets.main.output
    outjars "${buildDir}/proguard/output.jar"

    libraryjars project.sourceSets.main.compileClasspath

    configuration 'proguard.conf'
}

// 2.3 Clean after proguard task
task cleanAfterProguard(type: Delete, dependsOn: proguard) {
    delete "${buildDir}/classes/java/main"
    delete "${buildDir}/resources/java/main"
}

// 2.4 Extract output jar to buildDir 
task unpackProguardOutput (type: Copy, dependsOn: cleanAfterProguard) {
    from zipTree("${buildDir}/proguard/output.jar")
    into file("${buildDir}/classes/java/main")
}

Finally, add a task to run the application with the proguarded classes.

// 3. Create a task to run the app with the proguarded buildDir
task runProguard(type: JavaExec, dependsOn: unpackProguardOutput) {
    classpath = sourceSets.main.runtimeClasspath
    jvmArgs = ['--module-path', classpath.asPath,
               '--add-modules', 'javafx.controls,javafx.fxml' ]
    main = 'a.a.b' // <-- this name will depend on the proguard result
}

proguard.conf

The key on how to get it working with Java 9+ can be found in this comment. If you download the source code, and check the examples folder, there are different configuration files.

Checking applications.pro, you can read:

# Before Java 9, the runtime classes were packaged in a single jar file.
#-libraryjars <java.home>/lib/rt.jar

# As of Java 9, the runtime classes are packaged in modular jmod files.
-libraryjars <java.home>/jmods/java.base.jmod(!**.jar;!module-info.class)

So that's it!

This is the config file I've used with the HelloFX sample (of course it could be extended with other many options):

-dontoptimize
-dontshrink

#Java 9+
-libraryjars <java.home>/jmods/java.base.jmod(!**.jar;!module-info.class)

# Save meta-data for stack traces
-printmapping out.map
-renamesourcefileattribute SourceFile
-keepattributes SourceFile,LineNumberTable

# Rename FXML files together with related views
-adaptresourcefilenames **.fxml,**.png,**.css,**.properties
-adaptresourcefilecontents **.fxml
-adaptclassstrings

# Keep all annotations and meta-data
-keepattributes *Annotation*,Signature,EnclosingMethod

# Keep entry-point class
-keep class org.openfjx.MainApp {
  public static void main(java.lang.String[]);
}

# Keep names of fields marked with @FXML, @Inject and @PostConstruct attributes
-keepclassmembers class * {
  @javafx.fxml.FXML *;
  @javax.inject.Inject *;
  @javax.annotation.PostConstruct *;
}

Result

If you run ./gradlew proguard, you will get the output.jar.

If you run ./gradlew unpackProguardOutput, you can see the result in build/classes:

main
|____a
| |____a
| | |____styles.css
| | |____scene.fxml
| | |____b.class
| | |____a.class

In this case, b.class is the main class, so this is why in the runProguard task I've set main = 'a.a.b'. This will depend on each case, obviously.

Also, you can check the out.map:

org.openjfx.FXMLController -> a.a.a:
    javafx.scene.control.Label label -> label
    10:10:void <init>() -> <init>
    17:20:void initialize(java.net.URL,java.util.ResourceBundle) -> initialize
org.openjfx.MainApp -> a.a.b:
    11:11:void <init>() -> <init>
    15:23:void start(javafx.stage.Stage) -> start
    26:27:void main(java.lang.String[]) -> a

Finally, ./gradlew runProguard will successfully run the application.

José Pereda
  • 44,311
  • 7
  • 104
  • 132
  • Awesome, thank you, it worked (partially). One thing that did not work is (!**.jar;!module-info.class) - it throws No such file exception. And with that I'm getting gradle warnings such as: can't find referenced method 'void setText(java.lang.String)' and can't find referenced method 'javafx.scene.control.TableView getTableView()' so something is not up with libraries. – orzel Feb 26 '19 at 21:44
  • Ok so I modified the task config, since I need a fatjar, and it turned out it ends with the same error (Duplicate jar entry [a.class]), so I assume it's problem with dependencies now, not with JavaFX and/or Java11... Any ide how to fix that? (It obfuscates regular jar just fine) – orzel Feb 26 '19 at 21:57
  • I've just took the hellofx sample, and modified it as posted. Can you try that first? Once you make it work, start doing modifications. – José Pereda Feb 26 '19 at 22:01
  • I've just modified the input of proguard to fatjar I generate: injars "${buildDir}/libs/out.jar", regular jar obfuscates just fine. – orzel Feb 26 '19 at 22:06
  • Hey Jose, I'm still struggling with this, for a few days straight actually, coming to conclusion that it wont be possible to successfully obfuscate JavaFX code with proguard. Currently I have problem with FXML Files. Actually I have excluded FXML file and corresponding View class from obfuscation, they are in the same directory inside the jar, just as in not obfuscated Jar, and it throws "Unexpected namespace prefix: .". No idea how to proceed with that... – orzel Mar 01 '19 at 11:46
  • You can obfuscate JavaFX code with Proguard, but not all of it. The FXML parts are more tricky as injection/reflection is widely used. As commented before, start from the basics, get something working and start adding options to your proguard.conf file. If you want, create a sample GitHub repo and we can work there... – José Pereda Mar 01 '19 at 11:49
  • 1
    It does not work if you run Proguard at the command line (proguard.sh @proguard.conf). There are errors "can't find referenced class...". How would you fix this please? – Patrick Oct 25 '19 at 09:40
  • @Patrick please post a new question with your details, what you have done and what fails. – José Pereda Oct 25 '19 at 10:17
  • Patrick, did you solve it? Im getting the same error " can't find referenced class" – KenobiBastila Oct 28 '19 at 23:07
  • @Patrick I think I fixed by adding : -dontwarn java.** -keep class java.** { *; } -keep interface java.** { *;} – KenobiBastila Oct 31 '19 at 15:43
  • @JoséPereda I had to add those lines on the proguard.conf. Not sure if its the right thing to do – KenobiBastila Oct 31 '19 at 15:44