How can I sign and deploy a JavaFX application into a single .JAR?

独自空忆成欢 提交于 2020-01-14 12:44:15

问题


I'm still learning the ins and outs of JavaFX. One major difference is that in the dist folder (in addition to the library) I find besides the .jar file, an HTML file and a JNLP file, neither nor of which is any use to me (this will be a desktop application).

I found the following (Properties omitted because of sensitive/irrelevant information):

<delete dir="${store.dir}"/>
<mkdir dir="${store.dir}"/>

<jar destfile="${store.dir}/temp_final.jar" filesetmanifest="skip">
    <zipgroupfileset dir="dist" includes="*.jar"/>
    <zipgroupfileset dir="dist/lib" includes="*.jar"/>

    <manifest>
        <attribute name="Main-Class" value="${main.class}"/>
    </manifest>
</jar>

<zip destfile="${store.jar}">
    <zipfileset src="${store.dir}/temp_final.jar"
    excludes="META-INF/*.SF, META-INF/*.DSA, META-INF/*.RSA"/>
</zip>
    <delete file="${store.dir}/temp_final.jar"/>

    <delete     dir = "${build.output.dir}"/>
    <mkdir      dir = "${build.output.dir}"/>

    <signjar
                jar = "${store.jar}"
          signedjar = "${build.output.dir}/${FileName}"
              alias = "${comodo.key.alias}"
          storepass = "${comodo.key.storepass}"
           keystore = "${comodo.key.store}"
          storetype = "PKCS12"
            keypass = "${comodo.key.pass}"
             tsaurl = "${timestamp.url}"/> 

This was intended to build both the "executable" MAIN JAR and all dependent libraries into a single .JAR that could be run from any location, and then take and sign that JAR and move it into a "signed" directory.

This works fine for any JAR library or Swing GUI application JAR, but when I tried the same thing with a JavaFX application it borked:

Error: Could not find or load main class com.javafx.main.Main

I'm not entirely surprised to find it NOT work, but it is a bit of a problem. I did some research on possibly deploying a "self-contained" application but that will not suit our needs. My employer procured at some expense a Comodo certificate, and I quickly ran into a wall in which it appears that because of Apple shenanigans (unless I am mistaken in this presumption) you have to join their developer club for the low low cost of $100 a year (not happening). I don't really do development for the apple platform SPECIFICALLY. I do Java development. Anyway, if I am correct that will not work for us so a self-contained deployment for Windows, Linux and Mac is out (again, if I am correct).

I'm hoping that this is something simple.

So how do I apply the ANT script I created for compiling a JAR containing all dependent LIBs into a code-signed JAR to a JavaFX application?

EDIT 1: Okay so definitely spoke too soon. Not very close to an answer. I've tried to frankenstein what I know about compiling and signing regular JAR files so that all libs are included and that the sign is successful into what I've been able to pick out of the manual. What I have is a hodgepodge and a failure. I get a .JAR file with everything in it except for the main .JAR file. Anyway this is the ANT script code:

Properties:

<property  name = "name"
          value = "APPNAME"/>

<property  name = "file"
          value = "APPJAR"/>

<property  name = "MC" 
          value = "MAINPACKAGE.MAINCLASS" />

<property  name = "released.dir"
          value = "released"/>

<property  name = "compiled.dir"
          value = "${released.dir}/compiled"/>

<property  name = "stored.dir"
          value = "${released.dir}/stored"/>

<property  name = "signed.dir"
          value = "${released.dir}/signed"/>

Everything else:

<delete dir = "${released.dir}"/>
        <mkdir dir = "${compiled.dir}"/>

        <fx:jar destfile = "dist/compiled.jar">
            <fx:platform javafx = "8.0+" j2se = "8.0"/>
            <fx:application      name = "${name}"
                            mainClass = "${MC}"/>

            <fileset dir = "build/classes"/>

            <fx:resources>
                <fx:fileset dir = "dist" includes = "lib/*.jar"/>
            </fx:resources>
        </fx:jar>

        <fx:signjar keystore = "${comodo.key.store}"
                       alias = "${comodo.key.alias}"
                   storetype = "PKCS12"
                     keypass = "${comodo.key.pass}"
                   storepass = "${comodo.key.storepass}"
                         jar = "dist/compiled.jar"
                     destdir = "${compiled.dir}"/>

        <mkdir dir = "${stored.dir}"/>

        <jar       destFile = "${stored.dir}/temp_final.jar"
            filesetmanifest = "skip" >
            <zipgroupfileset      dir = "${compiled.dir}"
                             includes = "compiled.jar"/>

            <zipgroupfileset      dir = "dist/lib"
                             includes = "*.jar"/>

            <manifest>
                <attribute name = "Main-Class"
                          value = "${main.class}"/>
            </manifest>
        </jar>

        <zip destfile = "${stored.dir}/${file}">
            <zipfileset src = "${stored.dir}/temp_final.jar"
                   excludes = "META-INF/*.SF, META-INF/*.DSA, META-INF/*.RSA"/>
        </zip>

        <delete file="${stored.dir}/temp_final.jar"/>

        <mkdir dir = "${signed.dir}"/>

        <signjar
            keystore = "${comodo.key.store}"
               alias = "${comodo.key.alias}"
           storetype = "PKCS12"
              tsaurl = "${timestamp.url}"
             keypass = "${comodo.key.pass}"
           storepass = "${comodo.key.storepass}"
                 jar = "${stored.dir}/${file}"
             destdir = "${signed.dir}"/>

And that's what I have to show for my time. If anyone can glean something from this that may put me on the right track (if I am even close, which I don't feel like I am), that would be super awesome.


回答1:


After bashing my skull and the contents therein into a slurry of something hardly recognizable as coherent thought I have managed to compose a script that accomplishes the task I have set out to accomplish. Most of this is taken from other peoples contributions and from consulting the docs. The other major contribution is found here (The last comment on the thread, it links to another SO answer, but that SO answer wasn't much help to me). Thanks go to everything from which I was able to extract the parts of the solution. I hope this is helpful for everyone else who needs/wants to accomplish this task.

Anyway: First things first: Properties:

<property name = "JFXProject.name"
         value = "PROJECT"/> <!-- Put your own project name here -->
<property name = "JFXMainClass"
         value = "MAINPACKAGE.MAINCLASS"/> <!--Put your own main class here -->
    <!-- don't edit below this line -->
<property name = "JFX.src.dir"
         value = "src"/>
<property name = "JFX.lib.dir"
         value = "dist/lib"/>
<property name = "JFX.build.dir"
         value = "release/JFXBuild"/>
<property name = "JFX.sign.dir"
         value = "release/JFXSign"/>
<property name = "store.dir"
         value = "release/store"/>
<property name = "sign.dir"
         value = "release/sign"/>
<property name = "comodo.key.store"
         value = "PATH-TO-YOUR-CERTIFICATE" /> <!-- You can name this what you like, but you want to make sure the value points to your certificate or JKS file -->

<property name = "comodo.key.storepass"
         value = "PASSWORD"/> <!-- Above applies here. Make sure it's the right password -->

<property name = "comodo.key.alias"
         value = "ALIAS"/> <!-- Make sure it's your right alias. You can find out how to find that information from [here][3] -->

<property name = "comodo.key.pass"
         value = "PASSWORD"/> <!-- In my own experience this was the same as the storepass but it may be different for you -->

<property name = "timestamp.url"
         value = "TIMESTAMPURL"/> <!-- This will vary for you depending on your certificate. -->

You can change the property names to something more meaningful to you if you want but make sure that they are consistent throughout the script.

Next, we want to clean out the last build:

<target name = "JFXClean">
    <echo>Cleaning ${JFX.build.dir}...</echo>
    <delete dir = "${JFX.build.dir}"/>
    <delete dir = "${JFX.sign.dir}"/>
    <delete dir = "${store.dir}"/>
    <delete dir = "${sign.dir}"/>
</target>

Then we want to re-create the directories for a new clean build...

<target name = "JFXInit" depends = "JFXClean">
    <echo>Creating the build directory...</echo>
    <mkdir dir = "${JFX.build.dir}"/>
    <mkdir dir = "${JFX.sign.dir}"/>
    <mkdir dir = "${store.dir}"/>
    <mkdir dir = "${sign.dir}"/>
</target>

This part is critical to get a functioning JavaFX JAR file:

<target name = "JFXBuild" depends = "jar, JFXInit">
    <fx:jar destfile = "${JFX.build.dir}/${JFXProject.name}.jar">
        <fx:application name = "${JFXProject.name}"
                   mainClass = "${JFXMainClass}"/>
        <fileset dir = "build/classes"/>
    </fx:jar>
</target>

This will store all parts correctly into their locations in the JAR (including any CSS and JFXML file you may have. If you aren't creating a JFXML application this may not be necessary. I don't know I haven't tested it out.

Then we want to sign the JavaFX JAR:

<target name = "JFXSign" depends = "JFXBuild">
    <fx:signjar  keystore = "${comodo.key.store}"
                    alias = "${comodo.key.alias}"
                storetype = "PKCS12" <!-- This is necessary for me, but may not be for you if you are not using a PFX file -->
                  keypass = "${comodo.key.pass}"
                storepass = "${comodo.key.storepass}"
                      jar = "${JFX.build.dir}/${JFXProject.name}.jar"
                  destdir = "${JFX.sign.dir}"/>
</target>

After that there are 2 more targets that handle sourcing a zip and then setting up that zip, and one final for singing the last jar:

<target name = "build" depends = "JFXSign">
    <jar destfile = "${store.dir}/temp_final.jar"
  filesetmanifest = "skip">
        <zipgroupfileset dir = "${JFX.build.dir}"
                     includes = "*.jar"/>
        <zipgroupfileset dir = "${JFX.lib.dir}"
                    includes = "*.jar"/>
        <manifest>
            <attribute name = "Main-Class"
                      value = "${JFXMainClass}"/>
        </manifest>
    </jar>
</target>

<target name = "store" depends = "build">
    <zip destfile = "${store.dir}/${JFXProject.name}.jar">
        <zipfileset src = "${store.dir}/temp_final.jar"
               excludes = "META-INF/*sf, META-INF/*.DSA, META-INF/*RSA"/>
    </zip>
    <delete file = "${store.dir}/temp_final.jar"/>
</target>

<target name = "BuildStoreAndSign" depends = "store">
    <signjar
         keystore = "${comodo.key.store}"
            alias = "${comodo.key.alias}"
        storetype = "PKCS12" <!-- This is necessary for me, but may not be for you if you are not using a PFX file -->
           tsaurl = "${timestamp.url}"
          keypass = "${comodo.key.pass}"
        storepass = "${comodo.key.storepass}"
              jar = "${store.dir}/${JFXProject.name}.jar"
          destdir = "${sign.dir}"/>
    <delete dir = "${JFX.compile.dir}"/>
    <delete dir = "${JFX.build.dir}"/>
    <delete dir = "${JFX.sign.dir}"/>
    <delete dir = "${store.dir}"/>
</target>

I can't really explain a whole lot of this because I'm nowhere near an expert on this subject. I was able to basically extract the sense of what I was looking at from the example code and the sources I found and put it together to get this: One final bit of information that was semi-helpful was this, but please note this does NOT use one-jar (I tried it with one-jar but it didn't work. It wasn't bringing in the CSS or applying it).

Also, a bit of warning: This worked for me. I can't guarantee it will for you, but I imagine tinkering around with it would produce results similar to mine (a single .JAR JavaFX application that runs where ever you put it).




回答2:


The Oracle speaks: http://www.oracle.com/technetwork/articles/javase/single-jar-141905.html

Need to modify the

<manifest>
    <attribute name="Main-Class" value="${main.class}"/>
</manifest>

and change it to

<manifest>
    CHANGE application.MainClass to 
    <attribute name="JavaFX-Application-Class" value="application.MainClass" />
    <attribute name="Main-Class" value="com/javafx/main/Main" />
</manifest>

But listening to the Oracle above led me to get a single JavaFX with external jars all in one.




回答3:


I tried your build.xml example, but failed to get it to work.

However, I did find an alternative method using Eclipse.

  1. Open your JavaFX project in Eclipse.
  2. Right Click on your project and export it as a runnable JAR file.
  3. Extract required libraries into generated JAR.

  1. Sign your runnable JAR using the standard jar signer.

To do that you need to first create a keystore:

keytool -genkey -keyalg RSA -alias Aubrey -keystore keystore.jks -storepass YourPassword

Then sign it like so:

jarsigner -keystore c:\path\to\your\keystore.jks -storepass YourPassword c:\path\to\your\file.jar Aubrey
  1. Finally, create a javafx jnlp file. And upload your signed jar, plus the jnlp to your server.

For example:

<?xml version="1.0" encoding="utf-8"?>
<jnlp spec="1.0" xmlns:jfx="http://javafx.com" codebase="http://aubreigo.info/java/" href="FX-People.jnlp">
<information>
<title>FX-People</title>
<vendor>aubrey</vendor>
<description>Google Contacts Viewer</description>
<offline-allowed/>
</information>

<security>
<all-permissions/>
</security>

<resources>
<j2se version="1.6+" href="http://java.sun.com/products/autodl/j2se"/>
<jar href="FX-People.jar" size="109545" download="eager" />
</resources>

<applet-desc  width="800" height="600" main-class="com.javafx.main.NoJavaFXFallback"  name="FX-People" >
<param name="requiredFXVersion" value="8.0+"/>
</applet-desc>
<jfx:javafx-desc  width="800" height="600" main-class="fx.contacts.FXContacts"  name="FX-People" />
<update check="always"/>
</jnlp>


来源:https://stackoverflow.com/questions/24727559/how-can-i-sign-and-deploy-a-javafx-application-into-a-single-jar

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!