Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating a runnable jar. Various problems

Tags:

java

jar

ant

I've been working on a command line executable Java program. It's in the testing phase, and packaging it up has proved a bit of an issue. The crux of this problem revolves around the MANY dependencies my application has. My lib folder (containing jars) is close on 500mb.

At the moment I have been using the Eclipse Runnable Jar Export wizard. Works flawlessly, except it has to be done from Eclipse. It generates a Jar about 500mb in size (don't ask... suffice to say there need to be a lot of COBOL programs being packaged inside this program). This process takes <30s.

Ideally I would like this to be an Ant task of some kind, runnable via Jenkins and published to a repository. That way a user can just grab a Jar and run it.

Options I have investigated:

Custom classloader (FatJar, OneJar etc).

  • Works
  • Slow to execute at runtime as it's required to unpack the jar.
  • File structure is basically a Jar full of jars and the main class is delegated through the custom classloader.
  • Fast build time (5 mins)

    <target name="hello" depends="compile">
        <property name="classes.dir" value="onejar" />
        <property name="build.dir" value="bin" />
    
        <one-jar destfile="hello.jar">
            <manifest>
                <attribute name="One-Jar-Main-Class" value="mainclass" />
                <attribute name="Class-Path" value="." />
                <attribute name="One-Jar-Show-Expand" value="true" />
            </manifest>
            <main>
                <fileset dir="${build.dir}"/>
            </main>
            <lib>
                <fileset dir="lib">
                    <include name="**/*.jar"/>
                </fileset>
    
            </lib>
    
    
        </one-jar>
    </target>
    

Manually collecting dependencies, unpacking them into a folder, then jarring them all up with a custom MANIFEST file.

  • Can't get it to work
  • LARGE (1500mb+)
  • Slow build time (20min +)

    <target name="compile" depends="resolve">
        <javac srcdir="src" destdir="bin" debug="true" deprecation="on">
            <classpath>
                <path refid="ivy.path" />
            </classpath>
        </javac>
    </target>
    
    
    
    <target name="jar" depends="compile" description="Create one big jarfile.">
        <jar jarfile="${dist}/deps.jar">
            <zipgroupfileset dir="lib">
                <include name="**/*.jar" />
            </zipgroupfileset>
        </jar>
        <sleep seconds="1" />
        <jar jarfile="${dist}/myjar.jar" basedir="bin">
            <zipfileset src="${dist}/deps.jar" excludes="META-INF/*.SF" />
            <manifest>
                <attribute name="Main-Class" value="mymainclassishere" />
            </manifest>
        </jar>
    </target>
    

So yeah, does anyone have any suggestions? I am interested to hear peoples thoughts.

Edit: Specifically.... WHY the Eclipse Runnable Jar Export Wizard can export my Jar in less then 30s, yet my build times are > 30 minutes.

like image 636
Dominic Bou-Samra Avatar asked Apr 19 '12 23:04

Dominic Bou-Samra


1 Answers

I know responding to your question can be frowned upon, but I did manage to solve this problem yesterday. Of all the options I tried I found the fastest solution was to replicate the Eclipse Runnable Jar Export Wizard. It requires the jar-in-jar-loader.zip which can be acquired using the Runnable Jar Export Wizard... or you can find it via Google/Eclipse install.

I found this solution was fast to build (30s) and much faster to run (5s bootup cost). It also left the jar structure very neat with no exploded jars.

<target name="compile" depends="resolve">
    <mkdir dir="bin"/>
    <!-- Copy all non java resources since jar javac will exclude them by default. Needed for xmls, properties etc --->
    <copy todir="bin">
        <fileset dir="src" excludes="**/*.java" />
    </copy>

    <javac srcdir="src" destdir="bin" debug="true" deprecation="on">
        <classpath>
            <path refid="ivy.path" />
        </classpath>
    </javac>
</target>


<!-- Creates the runnable jar. Copies the dependencies as jar files, into the top level of a new jar. 
     This means nothing without a custom classloader and manifest with a jar listing -->
<target name="jar" depends="compile" description="Create one big jarfile.">

    <path id="dependencies.path">
        <fileset dir="lib">
            <include name="**/*.jar" />
        </fileset>
    </path>

    <pathconvert property="manifest.classpath" pathsep=" ">
        <path refid="dependencies.path" />
        <mapper>
            <chainedmapper>
                <flattenmapper />
                <globmapper from="*.jar" to="*.jar" />
            </chainedmapper>
        </mapper>
    </pathconvert>

    <mkdir dir="${dist}"/>
    <jar destfile="${dist}/jar/Runnable.jar" filesetmanifest="mergewithoutmain">
        <manifest>
            <attribute name="Main-Class" value="org.eclipse.jdt.internal.jarinjarloader.JarRsrcLoader" />
            <attribute name="Rsrc-Main-Class" value="mymainclass" />
            <attribute name="Class-Path" value="." />
            <attribute name="Rsrc-Class-Path" value="./ ${manifest.classpath}" />
        </manifest>
        <fileset dir="bin" />
        <zipfileset src="jar-in-jar-loader.zip" />
        <zipfileset dir="lib" includes="**/*.jar*" />
    </jar>
</target>
like image 95
Dominic Bou-Samra Avatar answered Oct 16 '22 14:10

Dominic Bou-Samra