Thursday, October 28, 2010

Automating Java Application Packaging with Eclipse

The fun part is over: you've written your app and now it's time to think about packaging and deployment. Software development would be so much easier except for those pesky users!

Fortunately, recent versions of Eclipse include a wizard to ease the burden of packaging applications into executable jars. While the wizard provides a useful starting point for Java application deployment, it provides limited options for customizing and automating the distribution process. An ideal solution would allow us to automate the packaging process to support push-button packaging and continuous integration.

In this post, I'll describe the process I use to create a build script for push-button executable packaging. My goals are to
  • create a single executable jar that can be run without setting a class path:
    java -jar myjar.jar
    
  • automate the creation of the jar using a build script
A common problem with Java packaging and deployment concerns library handling: how can we easily deploy our application with all of its dependencies? There are several common solution patterns employed by Java developers:
  • Java Web Start provides a flexible mechanism for deploying an application and its dependencies to a web server. The Web Start framework uses the application description to reconcile any missing dependencies when the application is started.
  • Several solutions exist for packaging Java applications using traditional desktop installers. Users install the application and its dependencies using the familiar setup wizard.
  • The "fat jar" approach packages the application and its dependencies into a single jar archive. This approach has the advantage of providing a single application archive that can be run directly but requires a custom class loader to resolve the library dependencies.

Eclipse's Runnable Jar Wizard

Eclipse's Runnable Jar Wizard (File → Export… → Java → Runnable Jar File) allows developers to create executable jars from an existing run configuration:



The wizard includes 3 options for handling dependencies:
  1. Extract required libraries into generated jar: unarchives library dependencies and repackages them into your executable jar. This option has the advantage of simplicity and does not require a custom class loader. However repackaging library jars can cause other problems and does not preserve the signatures of signed jars. This option may also violate the license terms of the libraries you are using.
  2. Package required libraries into generated jar: creates a "fat jar" with a custom class loader. The resultant jar contains
    • the application's classes and resources
    • library jars required to launch the application
    • a small custom class loader that knows how to find jar libraries inside another jar archive
  3. Copy required libraries…: creates the application archive and copies any required library dependencies to the destination folder.
The second option suits my present purposes, but using a wizard hinders automation. Fortunately the wizard includes an option to save a build script of the steps needed to create the executable jar using the selected settings.

9000 Ways to Not Build a Light Bulb

There is only one small problem: the generated script does not work! While the executable jar archive will work just fine, recreating it using the script does not. For reference, I'm currently using Eclipse Helios Build id: 20100617-1415. Here is the ant script it generates:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project default="create_run_jar" name="Create Runnable Jar for Project…">
    <!--this file was created by Eclipse Runnable JAR Export Wizard-->
    <!--ANT 1.7 is required                                        -->
    <target name="create_run_jar">
        <jar destfile="C:/documents/development/java/bananagrams/bananaounces/dist/bo_server.jar">
            <manifest>
                <attribute name="Main-Class" value="org.eclipse.jdt.internal.jarinjarloader.JarRsrcLoader"/>
                <attribute name="Rsrc-Main-Class" value="bananaounces.remoting.services.game.GameService"/>
                <attribute name="Class-Path" value="."/>
                <attribute name="Rsrc-Class-Path" value="./ junit.jar org.hamcrest.core_1.1.0.v20090501071000.jar…"/>
            </manifest>
            <zipfileset src="jar-in-jar-loader.zip"/>
            <fileset dir="C:/documents/development/java/bananagrams/bananaounces/bin"/>
            <zipfileset dir="C:\Users\dan\Documents\downloads\development\eclipse\plugins\org.junit_4.8.1.v4_8_1_v20100427-1100" includes="junit.jar"/>
            <zipfileset dir="C:\Users\dan\Documents\downloads\development\eclipse\plugins" includes="org.hamcrest.core_1.1.0.v20090501071000.jar"/>
            <fileset dir="C:/documents/development/java/bananagrams/bananaounces/greenfoot/standalone"/>
            <zipfileset dir="C:\documents\development\java\bananagrams\bananaounces\lib" includes="org.freemarker.jar"/></jar>
    </target>
</project>

If you try to run the created script (right click it → Run As → Ant Build), Eclipse records a build failure:
Buildfile: C:\documents\development\java\bananagrams\bananaounces\package_server.xml
create_run_jar:

BUILD FAILED
C:\documents\development\java\bananagrams\bananaounces\package_server.xml:6: the archive jar-in-jar-loader.zip doesn't exist

Total time: 366 milliseconds
In the example file, the problem occurs at line 13:
<zipfileset src="jar-in-jar-loader.zip"/>

This archive contains the custom class loader Eclipse packages with your application to make the Jar-in-Jar packaging work. Unfortunately this file is buried within the Eclipse install and is not copied to the destination directory by the wizard.

"I'll put that box inside of another box and mail that box to myself"

It turns out that this zip file is in a jar file in the Eclipse installation:
eclipse_install_dir/plugins/org.eclipse.jdt.ui_3.6.0.v20100602-1600.jar
Extracting jar-in-jar-loader.zip from this jar archive and placing it next to the build script enables the script to run successfully:
Buildfile: C:\documents\development\java\bananagrams\bananaounces\package_server.xml
create_run_jar:
      [jar] Building jar: C:\documents\development\java\bananagrams\bananaounces\dist\bo_server.jar
BUILD SUCCESSFUL
Total time: 3 seconds
With the script working, we can now begin working on customizations: removing absolute paths, deleting extraneous libraries from the package, and integrating packaging into the rest of the build process. When customizing the build script, keep in mind that the custom class loader requires a slightly modified manifest declaration:
<manifest>
    <attribute name="Main-Class" value="org.eclipse.jdt.internal.jarinjarloader.JarRsrcLoader"/>
    <attribute name="Rsrc-Main-Class" value="bananaounces.remoting.services.game.GameService"/>
    <attribute name="Class-Path" value="."/>
    <attribute name="Rsrc-Class-Path" value="./ junit.jar org.hamcrest.core_1.1.0.v20090501071000.jar…"/>
</manifest>

Conclusion

The Eclipse Runnable Jar Wizard provides a good starting point for automating Java application deployments. Using the wizard, I can create a single jar file that includes everything the application needs to run. The ant build script generated by the wizard provides a starting point for automating the process of creating an executable package.

1 comment: