compiling and making jar files with Ant

When I first saw Java I was surprised at exactly how embarrassingly simple it was to compile a program. Well, with the caveat that the file or files are all in the same directory.

javac *.java

When the number of source files is tiny and the number of other files is equally small a simple directory with two or three files seems like an ok solution – even for production. It turns out that wasn’t really a ok solution.  The fact was my previous boss had set the bar pretty low for what was deemed to be acceptable.

When the standards went up, I had to up my game a bit. One of the easier ways to do this is to create java libraries instead of just having a loose confederation of files.

Manual jar creation

A jar file is actually just a simple archive of files in the given directory with the addition of a manifest file.  Manually creating a jar file can be done with the following command.

jar cvfm hello.jar manifest.mf de/

The manifest file actually contains some information about the contents of the jar file.

Manifest-Version: 1.0
Built-By: chdock
Main-Class: de.companyname.helloworld.HelloWorld
Implementation-Version: 3.15.047 build #6
Built-Date: 2016-09-01 10:01:14

As you can see most of this is version information, who built it and when. The “Main-Class” file field actually points to the main program in the jar file. This entry allows the jar file to be run directly without giving the name of the program. This does assume that no other external jar files are required.

That isn’t to say that you cannot have an runnable jar when external jar files are necessary. When that happens simply add them to the classpath in the manifest file.

Class-Path: ..\lib\log4j-core-2.3.jar ..\lib\log4j-api-2.3.jar ..\resources\log4j2.xml

The classpath is relative to the location of the jar file you are running.

Running program

Running the jar directly is done using the -jar argument.

java -jar yourlibraryname.jar

Of course this doesn’t actually prevent running the program or other programs directly from the jar in the normal manner.

java -cp lib\lib1.jar;lib\lib2.jar;.\yourlibraryname.jar ProgramNameHere

Of course I could have written a shell script to take the jar command to create the libraries, but rather than try and force jar and bash shell together I opted for a more proper solution which was to use Apache Ant instead.

Apache Ant is somewhat similar to the Unix make command. You define targets that need to be performed. When creating each of these targets you can make them dependent on another target. However, the actual order of the execution is not set other than to obey the dependencies.

Below is a sample Ant file to compile the source code and create a jar file once that is done. The default name for the ant configuration file is called “build.xml”, however, it is possible to run Ant with a build file of any name.  This is done by passing the name in on the command line when starting ant.

Simple build file

build.xml

<project default="world" basedir=".">

        <property name="version.num" value="3.15.047"/>
        <property name="build.num" value="6"/>
        <property name="mainclass" value="de.companyname.helloworld.HelloWorld"/>
        <property name="lib.dir" value="${basedir}/libs"/>
        <property name="src.dir" value="${basedir}/src"/>
        <property name="build.dir" value="${basedir}/bin"/>
        <property name="jarname" value="hello"/>
        <property name="jar.dir" value="${basedir}/final"/>

        <path id="classpath">
                <fileset dir="${lib.dir}" includes="**/*.jar"/>
        </path>
 
        <target name="compile" description="compile source code">
                <echo>"compiling ..."</echo>
                <mkdir dir="${build.dir}"/>
                <javac srcdir="${src.dir}" destdir="${build.dir}" classpathref="classpath"/>
        </target>
 
        <target name="jarfile" depends="compile" description="generate jar file">
                <echo>"build jar ..."</echo>
                <mkdir dir="${jar.dir}"/>
 
                <tstamp>
                        <format property="today.timestamp" pattern="yyyy-MM-dd HH:mm:ss" />
                </tstamp>
 
                <jar destfile="${jar.dir}/${jarname}.jar" basedir="${build.dir}">
                <manifest>
                 <attribute name="Built-By" value="${user.name}" />
                 <attribute name="Main-Class" value="${mainclass}" />
                 <attribute name="Implementation-Version" value="${version.num} build #${build.num}"/>
                 <attribute name="Built-Date" value="${today.timestamp}"/>
                </manifest>
                </jar>
        </target>
 
        <target name="world" depends="jarfile">
                 <echo>"build script version 1.00"</echo>
        </target>
</project>

This particular Ant script is probably one of the smallest which will compile our code and also create a runnable jar file. It could be made a bit smaller by removing the properties at the top of the script and embedding those values where they are used.

This script can either be used by Eclipse to build your programs or using Ant from the command prompt.

> ant world
Buildfile: /tmp/simplejar/build.xml                                                                                                                       
                                                                                                                                                          
compile:                                                                                                                                                  
     echo "compiling ..."                                                                                                                               
    javac /tmp/simplejar/build.xml:19: warning: 'includeantruntime' was not set, defaulting to build.sysclasspath=last; set to false for repeatable builds
    javac Compiling 1 source file to /tmp/simplejar/bin

jarfile:
     echo "build jar ..."
    mkdir Created dir: /tmp/simplejar/final
      jar Building jar: /tmp/simplejar/final/hello.jar

world:
     echo "build script version 1.00"

BUILD SUCCESSFUL
Total time: 3 seconds

Dealing with the source code

Once we have a working Ant script it is possible to make small changes here and there to create some nice additional effects. To simplify the build script, I have moved some of the properties into a small file called project.properties which is then included.

project.properties

version.num = 3.13.047
build.num = 9
mainclass = de.companyname.helloworld.HelloWorld
jarname = communication
zipfile = communication-src
#copysource = true 

build.xml

<project default="world" basedir=".">

        <property file="project.properties"/>

        <property name="mainclass" value="en.companyname.helloworld.HelloWorld"/>
        <property name="lib.dir" value="${basedir}/libs"/>
        <property name="src.dir" value="${basedir}/src"/>
        <property name="build.dir" value="${basedir}/bin"/>
        <property name="jarname" value="hello"/>
        <property name="jar.dir" value="${basedir}/final"/>

        <path id="classpath">
                <fileset dir="${lib.dir}" includes="**/*.jar"/>
        </path>

        <target name="copysrc" description="copy source code" if="copysource">
                <copy todir="${build.dir}">
                        <fileset dir="${src.dir}" includes="**/*.java"/>
                </copy>
        </target>

        <target name="compile" description="compile source code">
                <echo>"compiling ..."</echo>
                <mkdir dir="${build.dir}"/>
                <javac srcdir="${src.dir}" destdir="${build.dir}" classpathref="classpath"/>
        </target>

        <target name="jarfile" depends="compile" description="generate jar file">
                <echo>"build jar ..."</echo>
                <mkdir dir="${jar.dir}"/>

                <tstamp>
                        <format property="today.timestamp" pattern="yyyy-MM-dd HH:mm:ss" />
                </tstamp>

                <jar destfile="${jar.dir}/${jarname}.jar" basedir="${build.dir}">
                <manifest>
                        <attribute name="Built-By" value="${user.name}" />
                        <attribute name="Main-Class" value="${mainclass}" />
                        <attribute name="Implementation-Version" value="${version.num} build #${build.num}"/>
                        <attribute name="Built-Date" value="${today.timestamp}"/>
                </manifest>
                </jar>
        </target>

        <target name="clean" description="clean up after ourselves">
                <echo>"cleaning ..."</echo>

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

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

                <mkdir dir="${basedir}/${jarname}" />
                <delete dir="${basedir}/${jarname}" />
        </target>

        <target name="package" depends="compile" description="make a source zip file ">
                <echo>"packaging ..."</echo>
                <mkdir dir="${basedir}/${jarname}/${jarname}-v${version.num}" />

                <copy todir="${basedir}/${jarname}/${jarname}-v${version.num}">
                        <fileset dir="${src.dir}" includes="**/*.java"/>
                </copy>

                <zip destfile="${jar.dir}/${jarname}-v${version.num}.zip" basedir="${basedir}/${jarname}/${jarname}-v${version.num}"/>
        </target>

        <target name="world" depends="jarfile">
                <echo>"build script version 1.00"</echo>
        </target>
</project>

This build file is not significantly more complex than the simple example. The main difference is it can also create a zip file containing the source code, or it can even place the source code into the jar file itself.

Why would you want to put the source into the jar file? Well, this would be mainly for dealing with the situation of someone who is fairly thick and who cannot be trusted to keep both the program and the source code in sync. When it is done in this manner then the person who has the program also has the source by default. This is probably not the best idea depending on the size of the codebase – but that is a discussion for another time.

This build script can also create a zip file containing the source code is created at the same time as the jar file and placed into the final directory.

How to enable putting the source code into the jar file? Simply remove the hash symbol from in front of the property “copysource” in the properties file. This will cause the copysource property to be set and thus cause the copy source target to be invoked. Invoked how? Simple, this target has one additional parameter if=”copysource” added to it. This property acts as a Boolean that will execute the target only when it is set.

For anyone with a really sharp eye for detail will notice that the properties jarname and mainclass exist in both the project.properties file and as properties in the build script. Curiously both are different values. How does Ant decide which value to use? It is actually quite easy to see what values will be used while checking through the script.

Once Ant assigns a value to a property it cannot be changed. So, in this case the values from the project.properties file will be assigned first and thus the values in the build script will be ignored (for jarname and mainclass) as they are already defined.

A more common configuration would be to have a property file for the program (ie default.properties) and to have another property file (build.properties) that could be created to alter from the default behavior. Both files would be read into the build script and if the developer wished to use some slightly different values in the build (ie turn on debugging) it could be done at that point.

Below is my awesome “hello world” application in java as well as how to run it directly from it’s jar file.

HelloWorld.java

 
package de.companyname.helloworld; 
public class HelloWorld 
{ 
        public static void main(String args[]) 
        { 
                System.out.println("hello world"); 
        } 
} 

runInterface.sh

#!/bin/bash  

CMD="java -jar final/hello.jar"
echo $CMD
$CMD

If you have liked this blog entry about Ant you might also like the follow up where I do more than just build a jar file.

Note: The example was compiled with java 1.7.

This entry was posted in programming and tagged , . Bookmark the permalink.