javajarsignerjava-11jar-signing

Signing java 11 jar with jarsigner duplicate entry module-info.class


Hi I am new with java modules so this might be a dumb question.

I was trying to sign my jar file with keystore and got the following error.

user@Ubuntu:libs(master)$ jarsigner -keystore keyStoreFileName Test.jar alias
Enter Passphrase for keystore: 
jarsigner: unable to sign jar: java.util.zip.ZipException: duplicate entry: module-info.class

I couldn't find any documentation of how to avoid this.

So I did jar -tf to check the content of the jar and yes, it does have multiple module-info.class files

Is there any option to combine them? and how?

my module-info.java contains the following.

module module_name {
    requires java.desktop;
    requires java.prefs;
    requires javafx.controls;
    requires javafx.fxml;
    requires javafx.web;
    requires org.jsoup;
    opens com.test.apps to javafx.fxml;
    exports com.test.apps;
}

and I am creating jar with gradle like this

jar {
    manifest {
        attributes 'Main-Class': 'com.test.apps.Main'
        attributes 'Application-Name': 'Test'
    }

    from {
        configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
    }

}

I know I shouldn't post screenshots, but here is the jar opened with archive manager Archive manager screenshot

there are 7 module-info.class files, 1st one generated on 23-Dec-18 (today) rest all are from 05-Nov-18 which I remember upgrading to java 11 that day,

so those are added from my dependencies, how can I integrate them into one class? or are there other option to sign a jar?

again, this is coming from a noob.

[Edit] if you are looking for complete source code, it is in GitHub -> https://github.com/CodingOtaku/Animu-Downloaderu


Solution

  • Based on what you have posted of your build.gradle, when you run:

    ./gradlew jar
    

    you are creating a fat/shadow jar, that bundles all of your dependencies, including the modular ones in one single big jar. This process extracts all the files from each jar (classes and resources) into the libs folder, and finally zips it into the shadow jar of your project.

    The modular dependencies (at least JavaFX jars) include a module-info.class file in their jar file. So these will be added to the libs folder.

    As a result of the jar task, and depending on your platform, you could end up with only one of these files (the first or the last one added, if files with same name are pasted into one single file), or with all of them (if all files even with the same name are kept, as this seems to be your case).

    Either way, since you are creating a fat jar, you will run it as:

    java -jar my-fat-jar.jar
    

    and for this you don't need modules at all.

    Solution

    So one simple solution is to exclude the module-info files from your fat jar:

    jar {
        manifest {
            attributes 'Main-Class': 'your.main.class'
        }
        from {
            exclude '**/module-info.class'
            configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
        }
    }
    

    Cross-platform jar

    Note that the jar you are releasing is not cross-platform, as it only contains the native libraries for Linux.

    One option to create a cross-platform jar can be found here, section Non-modular application -> Gradle -> Cross-platform jar.

    jlink

    Alternatively you might consider using jlink for distribution, since your app is already modular.

    In this case you will generate a custom image for a given platform. You can consider creating a one for each platform.

    See this doc, section Modular with Gradle, and use the jlink task, or try the badass-jlink-plugin instead.

    jpackage

    There is a preview of the jpackage tool for Java 12. With it, you could create an installer for each platform.