Monday, 7 February 2011

Building an executable jar with Ant

An executable jar file has a defined default class, allowing the default class to be invoked without explicitly identifying it via the command line. On a supporting OS (such as Mac OS/X or Microsoft Windows), this allows the class to be invoked by double-clicking the file.
I know that making a jar file executable requires an entry in the manifest file. I can usually remember that the attribute in question is Main-Class (although I'm usually fuzzy about the proper capitalization). The problem is remembering the correct command line options to insert my own MANIFEST.MF file into a jar file.
All in all, it is much simpler to just use ant to handle it for me. Let's start with the example from my Ant Hello World Revisited. The new ant build file follows, with the new lines shown in bold. Note that I've also designated the "jar" task as the default task.

$ cat hello.xml
<project default="jar">
<target name="compile">
<javac srcdir="." />
</target>
<target name="jar" depends="compile">
<jar destfile="hello.jar"
basedir="."
includes="**/*.class">
<manifest>
<attribute name="Main-Class" value="hello" />
</manifest>
</jar>
</target>
</project>

$ cat hello.java
public class hello {
public static void main( String[] args )
{
System.out.println( "Hello World" );
}
}
Now we simply build our jar file:

$ rm *.jar
$ ant -f hello.xml
Buildfile: hello.xml

compile:

jar:
[jar] Building jar: /Tutorial/Ant/Jar/hello.jar

BUILD SUCCESSFUL
Total time: 2 seconds
And execute the default class by using the -jar command line option to java:

$ java -jar hello.jar
Hello World

Using Ant Properties

In this Ant tutorial installment, we will use ant Properties to specify the work directory structure that will contain our .class and .jar files. The work directory structure tends to be a personal preference, and properties will allow us to centralize the structure definition so that any change will be relatively pain-free. We specify the directories with a location attribute, thereby binding the property to a file system location. This allows us to pass the property to another ant process without any local directory side effects.

<project default="all">
<property name="obj-dir" location="obj" />
<property name="lib-dir" location="lib" />

<target name="init">
<mkdir dir="${obj-dir}" />
<mkdir dir="${lib-dir}" />
</target>
<target name="clean-init">
<delete dir="${obj-dir}" />
<delete dir="${lib-dir}" />
</target>

<target name="all" depends="init"/>
<target name="clean" depends="clean-init"/>
</project>
This build file features:
  • 4 targets:
    1. init: to build the work directory structure.
    2. clean-init: to remove the work directory structure.
    3. all: the build roll-up target.
    4. clean: to clean roll-up target.
    5. Note that my target naming convention is to pair a target with a clean-target. I reserve the all and the clean targets as roll-up targets for all build and clean targets respectively.
  • 2 properties:
    1. obj-dir: the root directory for our .class files.
    2. lib-dir: the root directory for our .jar files.
We can now execute the init target to create the work directories, and the clean-init target to remove the work directories.

$ ls
build.xml
$ ant
Buildfile: build.xml

init:
[mkdir] Created dir: /Tutorial/Ant/Properties/obj
[mkdir] Created dir: /Tutorial/Ant/Properties/lib

all:

BUILD SUCCESSFUL
Total time: 3 seconds
$ ls
build.xml obj/ lib/
$ ant clean
Buildfile: build.xml

clean-init:
[delete] Deleting directory /Tutorial/Ant/Properties/obj
[delete] Deleting directory /Tutorial/Ant/Properties/lib

clean:

BUILD SUCCESSFUL
Total time: 2 seconds
$ ls
build.xml
Now, let's add targets to compile, jar and execute a java class. We'll just copy over the hello.java class from our previous installment and place it in the src directory. The compile and jar targets from before remain largely the same; but we add the destdir attribute to the javac task to specify where the .class files are to be stored and we can remove the includes attribute from the jar task. Our new build.xml looks like this:

<project default="all">
<property name="obj-dir" location="obj" />
<property name="lib-dir" location="lib" />
<property name="src-dir" location="src" />

<target name="init">
<mkdir dir="${obj-dir}" />
<mkdir dir="${lib-dir}" />
</target>
<target name="clean-init">
<delete dir="${obj-dir}" />
<delete dir="${lib-dir}" />
</target>

<target name="compile" depends="init">
<javac srcdir="${src-dir}"
destdir="${obj-dir}"
/>
</target>
<target name="clean-compile">
<delete>
<fileset dir="${obj-dir}" includes="**/*.class" />
</delete>
</target>

<target name="jar" depends="compile">
<jar destfile="${lib-dir}/hello.jar"
basedir="${obj-dir}"
/>
</target>

<target name="clean-jar">
<delete file="${lib-dir}/hello.jar" />
</target>
<target name="run" depends="jar">
<java classname="hello"
classpath="${lib-dir}/hello.jar"
fork="true"
/>
</target>


<target name="all" depends="run"/>

<target name="clean" depends="clean-init"/>
</project>
And now we can execute our class and clean up our work directories.

$ ant run
Buildfile: build.xml

init:
[mkdir] Created dir: /Tutorial/Ant/Properties/obj
[mkdir] Created dir: /Tutorial/Ant/Properties/lib

compile:
[javac] Compiling 1 source file to /Tutorial/Ant/Properties/obj

jar:
[jar] Building jar: /Tutorial/Ant/Properties/lib/hello.jar

run:
[java] Hello World

BUILD SUCCESSFUL
Total time: 4 seconds
$ ant clean
Buildfile: build.xml

clean-init:
[delete] Deleting directory /Tutorial/Ant/Properties/obj
[delete] Deleting directory /Tutorial/Ant/Properties/lib

clean:

BUILD SUCCESSFUL
Total time: 2 seconds

Ant and JUnit

In this Ant tutorial installment, we'll take a look at the JUnit task in ant. The JUnit task is moderately complex, so you'll definitely want to read the Ant manual. I would also recommend taking a look at Java Development with Ant by Erik Hatcher and Steve Loughran (this link will allow you to download Chapter 4: Testing with JUnit). to complete your installation of JUnit. Finally, this example was developed using version 1.5.1 of Ant and version 3.8.1 of JUnit.
We'll start with the test class shown below. For simplicity, we'll use a test case that always succeeds and doesn't really test anything. We'll take a closer look at writing JUnit test cases later. For now we'll just say that a JUnit test case extends the junit.framework.TestCase class. Please create this file in a sub-directory named test (or change the value of the build file tst-dir property to suit).

import junit.framework.*;
public class TestExample extends TestCase {
public void testOne()
{
assertTrue( "TestExample", true );
}
}
And here is the build.xml file that we'll be using. Please modify this build file to reflect the location of junit.jar on your system. In addition, you must add junit.jar to your CLASSPATH or copy junit.jar to your Ant library from your JUnit library to enable the integration of JUnit and Ant.

<project default="all">
<property name="tst-dir" location="test" />
<property name="TALK" value="true" />

<path id="classpath.base">
</path>
<path id="classpath.test">
<pathelement location="/Projects/Java/Lib/junit.jar" />
<pathelement location="${tst-dir}" />
<path refid="classpath.base" />
</path>

<target name="compile-test">
<javac srcdir="${tst-dir}"
verbose="${TALK}"
>
<classpath refid="classpath.test"/>
</javac>
</target>
<target name="clean-compile-test">
<delete verbose="${TALK}">
<fileset dir="${tst-dir}" includes="**/*.class" />
</delete>
</target>

<target name="test" depends="compile-test">
<junit>
<classpath refid="classpath.test" />
<formatter type="brief" usefile="false" />
<test name="TestExample" />
</junit>
</target>

<target name="all" depends="test" />
<target name="clean" depends="clean-compile-test" />
</project>
There are three primary differences between this build.xml file and the ones used previously:
  1. Addition of the TALK property. We'll use this as a flag to the verbose attribute of various tasks. Our example below will demonstrate the effect of the verbose attribute for the delete and the javac tasks. Note that we assign the property with the value attribute since this is not a file system location.
    This is different from the ant -verbose option. Setting verbose=true on a task causes that task to be more informative about it's behavior. Passing the -verbose option to ant causes ant to be more informative about ant's behavior.
  2. Explicit declaration of the Java Classpath. In this case we define a base classpath and then we define the test classpath relative to it. If we were actually testing a real class, then the base classpath would be used to compile that class. The test classpath adds the location of the JUnit tests and the JUnit jar file to the base classpath.
  3. The JUnit task. There are three nested elements to our JUnit task:
    1. The java classpath.
    2. The formatter, which controls the output from the task.
    3. The name, which defines a single test class to be run.
We can now execute our test as shown below.

$ ant -DTALK=false clean
Buildfile: build.xml

clean-compile-test:
[delete] Deleting 1 files from /Tutorial/Ant/Junit/test

clean:

BUILD SUCCESSFUL
Total time: 2 seconds
$ ant -DTALK=false
Buildfile: build.xml

compile-test:
[javac] Compiling 1 source file

test:
[junit] Testsuite: TestExample
[junit] Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 0.007 sec


all:

BUILD SUCCESSFUL
Total time: 4 seconds
$ ant clean
Buildfile: build.xml

clean-compile-test:
[delete] Deleting 1 files from /Tutorial/Ant/Junit/test
[delete] Deleting /Tutorial/Ant/Junit/test/TestExample.class

clean:

BUILD SUCCESSFUL
Total time: 2 seconds
$ ant
Buildfile: build.xml

compile-test:
[javac] Compiling 1 source file
[javac] [parsing started /Tutorial/Ant/Junit/test/TestExample.java]
[javac] [parsing completed 81ms]
[javac] [loading /Projects/Java/Lib/junit.jar(junit/framework/TestCase.class)]
[javac] [checking TestExample]
[javac] [loading /Projects/Java/Lib/junit.jar(junit/framework/Assert.class)]
[javac] [loading /System/Library/.../1.3.1/Classes/classes.jar(java/lang/Object.class)]
[javac] [loading /Projects/Java/Lib/junit.jar(junit/framework/Test.class)]
[javac] [loading /System/Library/.../1.3.1/Classes/classes.jar(java/lang/String.class)]
[javac] [wrote /Tutorial/Ant/Junit/test/TestExample.class]
[javac] [total 866ms]

test:
[junit] Testsuite: TestExample
[junit] Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 0.005 sec


all:

BUILD SUCCESSFUL
Total time: 4 seconds

Ant and multiple JUnit test cases

In this Ant tutorial installment, we'll use the JUnit task to execute multiple test classes. Our starting point will be the trivial JUnit task presented previously. This example was developed using version 1.5.1 of Ant and version 3.8.1 of JUnit.
For simplicity, we'll add the additional JUnit test class shown below. This class uses the assertEquals method with arguments that will always cause an AssertionFailedError. Please place this file in a sub-directory named test as before.

import junit.framework.*;
public class Test extends TestCase {
public void test()
{
assertEquals( "Equality Test", 0, 1 );
}
}
And here is the build.xml file that we'll be using. There are just two changes from our previous example (shown in bold). First, we set property TALK to false in order to decrease the amount of output. And second, we replace the single test sub-element of the JUnit task with a batchtest that uses a file system regular expression to identify the test classes to run.

<project default="all">
<property name="tst-dir" location="test" />
<property name="TALK" value="false" />

<path id="classpath.base">
</path>

<path id="classpath.test">
<pathelement location="/Projects/Java/Lib/junit.jar" />
<pathelement location="${tst-dir}" />
<path refid="classpath.base" />
</path>

<target name="compile-test">
<javac srcdir="${tst-dir}"
verbose="${TALK}"
>
<classpath refid="classpath.test"/>
</javac>
</target>
<target name="clean-compile-test">
<delete verbose="${TALK}">
<fileset dir="${tst-dir}" includes="**/*.class" />
</delete>
</target>

<target name="test" depends="compile-test">
<junit>
<classpath refid="classpath.test" />

<formatter type="brief" usefile="false" />
<batchtest>
<fileset dir="${tst-dir}" includes="**/Test*.class" />
</batchtest>
</junit>
</target>

<target name="all" depends="test" />

<target name="clean" depends="clean-compile-test" />
</project>
We can now execute our test.

$ ant clean
Buildfile: build.xml

clean-compile-test:
[delete] Deleting 2 files from /Tutorial/Ant/JUnit2/test

clean:


BUILD SUCCESSFUL
Total time: 4 seconds
$ ant test
Buildfile: build.xml

compile-test:
[javac] Compiling 2 source files

test:
[junit] Testsuite: Test
[junit] Tests run: 1, Failures: 1, Errors: 0, Time elapsed: 0.012 sec

[junit] Testcase: test(Test): FAILED
[junit] Equality Test expected:<0> but was:<1>
[junit] junit.framework.AssertionFailedError: Equality Test expected:<0> but was:<1>
[junit] at Test.test(Unknown Source)


[junit] TEST Test FAILED
[junit] Testsuite: TestExample
[junit] Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 0.001 sec


BUILD SUCCESSFUL
Total time: 5 seconds
Note that ant exits with BUILD SUCCESSFUL despite a junit failure. This was an intentional choice by the designers to allow the completion of a test suite. A revised target that includes the recommended JUnit task structure is shown below. In this structure, a property is set when the JUnit task encounters a failure (test failure or JUnit error). After the JUnit task completes, we fail the target if the failureProperty was set.

<target name="test" depends="compile-test">
<junit failureProperty="test.failure" >
<classpath refid="classpath.test" />
<formatter type="brief" usefile="false" />
<batchtest>
<fileset dir="${tst-dir}" includes="**/Test*.class" />
</batchtest>
</junit>

<fail message="test failed" if="test.failure" />

</target>
We'll also modify the compile-test target by setting debug="true" in the javac task as shown. This will add line number information to the errors reported from JUnit.

<target name="compile-test">
<javac srcdir="${tst-dir}"
verbose="${TALK}"
debug="true"
>
<classpath refid="classpath.test"/>
</javac>
</target>
Now we execute ant:

$ ant clean
Buildfile: build.xml

clean-compile-test:
[delete] Deleting 2 files from /Tutorial/Ant/JUnit2/test

clean:

BUILD SUCCESSFUL
Total time: 3 seconds
$ ant test
Buildfile: build.xml

compile-test:
[javac] Compiling 2 source files

test:
[junit] Testsuite: Test
[junit] Tests run: 1, Failures: 1, Errors: 0, Time elapsed: 0.009 sec

[junit] Testcase: test(Test): FAILED
[junit] Equality Test expected:<0> but was:<1>
[junit] junit.framework.AssertionFailedError: Equality Test expected:<0> but was:<1>
[junit] at Test.test(Test.java:7)


[junit] TEST Test FAILED
[junit] Testsuite: TestExample
[junit] Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 0 sec


BUILD FAILED
file:/Tutorial/Ant/JUnit2/build.xml:36: test failed

Total time: 4 seconds
Much better. Here's the full build.xml file:

<project default="all">
<property name="tst-dir" value="test" />
<property name="TALK" value="false" />

<path id="classpath.base">
</path>

<path id="classpath.test">
<pathelement location="/Projects/Java/Lib/junit.jar" />
<pathelement location="${tst-dir}" />
<path refid="classpath.base" />
</path>

<target name="compile-test">
<javac srcdir="${tst-dir}"
verbose="${TALK}"
debug="true"
>
<classpath refid="classpath.test"/>
</javac>
</target>
<target name="clean-compile-test">
<delete verbose="${TALK}">
<fileset dir="${tst-dir}" includes="**/*.class" />
</delete>
</target>

<target name="test" depends="compile-test">
<junit failureProperty="test.failure">
<classpath refid="classpath.test" />

<formatter type="brief" usefile="false" />
<batchtest>
<fileset dir="${tst-dir}" includes="**/Test*.class" />
</batchtest>
</junit>

<fail message="test failed" if="test.failure" />
</target>

<target name="all" depends="test" />
<target name="clean" depends="clean-compile-test" />
</project>

Conditional Execution in Ant

In this Ant tutorial installment, we'll take a look at the conditional execution of targets. Let's start with the simple Hello World build file from before. Modify the hello and goodbye targets to include the if and unless attributes as shown in bold.

<project default="all">
<target name="hello" if="hello.set">
<echo message="Hello, World"/>
</target>

<target name="goodbye" unless="hello.set">
<echo message="Goodbye, Cruel World"/>
</target>

<target name="all" depends="hello,goodbye" />
</project>
The if="named.property" attribute causes a target to be executed if the named property is set. And the unless="named.property" attribute causes a target to be executed unless the named property is set. If we execute the build file without specifying a value for the property "hello.set", then the hello target will be skipped and the goodbye target will be executed. And specifying a value for the property "hello.set" yields the opposite result.

$ ant
Buildfile: build.xml

hello:

goodbye:
[echo] Goodbye, Cruel World

all:

BUILD SUCCESSFUL
Total time: 2 seconds
$ ant -Dhello.set=true
Buildfile: build.xml

hello:
[echo] Hello, World

goodbye:

all:

BUILD SUCCESSFUL
Total time: 4 seconds
Of course, we could just specify the desired target on the command line and avoid all that. But let's take a look at how we can use this to cleanup after a failed target. Start with the build file we used to run multiple JUnit test cases and split the original target "test" into three pieces: test-execute, test-failed, and test. Target "test-execute" will contain the JUnit task, target "test-failed" will contain the cleanup code and be conditional on if="test.failure", and target "test" will tie it all together. This will execute our clean up code if a test fails and skip the clean up code if all tests pass. Note that we could consolidate targets "test-failed" and "test" together at the cost of some clarity about the intent of each target.

<project default="all">
<property name="tst-dir" value="test" />
<property name="TALK" value="false" />

<path id="classpath.base">
</path>

<path id="classpath.test">
<pathelement location="/Projects/Java/Lib/junit.jar" />
<pathelement location="${tst-dir}" />
<path refid="classpath.base" />
</path>

<target name="compile-test">
<javac srcdir="${tst-dir}"
verbose="${TALK}"
debug="true"
>
<classpath refid="classpath.test"/>
</javac>
</target>
<target name="clean-compile-test">
<delete verbose="${TALK}">
<fileset dir="${tst-dir}" includes="**/*.class" />
</delete>
</target>

<target name="test-execute" depends="compile-test">
<junit failureProperty="test.failure">
<classpath refid="classpath.test" />
<formatter type="brief" usefile="false" />

<batchtest>
<fileset dir="${tst-dir}" includes="**/Test*.class" />
</batchtest>
</junit>
<fail message="test failed" if="test.failure" />
</target>

<target name="test-failed" depends="test-execute" if="test.failure">
<echo message="perform test cleanup" />
<fail message="test failed" />
</target>
<target name="test" depends="test-failed" />

<target name="all" depends="test" />

<target name="clean" depends="clean-compile-test" />
</project>
Execution with Test Failure

$ ant -f build.xml
Buildfile: build.xml

compile-test:

test-execute:
[junit] Testsuite: Test
[junit] Tests run: 1, Failures: 1, Errors: 0, Time elapsed: 0.035 sec

[junit] Testcase: test(Test): FAILED
[junit] Equality Test expected:<0> but was:<1>
[junit] junit.framework.AssertionFailedError: Equality Test expected:<0> but was:<1>
[junit] at Test.test(Test.java:7)
[junit] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
[junit] at sun.reflect.NativeMethodAccessorImpl.invoke( ... .java:39)
[junit] at sun.reflect.DelegatingMethodAccessorImpl.invoke( ... .java:25)


[junit] TEST Test FAILED
[junit] Testsuite: TestExample
[junit] Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 0.001 sec


test-failed:
[echo] perform test cleanup

BUILD FAILED
file:/Tutorial/Ant/Conditional/junit.xml:38: test failed

Total time: 3 seconds
Execution with Test Success

$ ant -f build.xml
Buildfile: build.xml

compile-test:

test-execute:
[junit] Testsuite: Test
[junit] Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 0.026 sec

[junit] Testsuite: TestExample
[junit] Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 0.001 sec


test-failed:

test:

all:

BUILD SUCCESSFUL
Total time: 4 seconds

Setting Java System Properties with Ant

In this installment, we'll learn how to set a Java System Property with ant. Here is our Java class. It simply accesses the System Properties object and prints the value of the test.property key.

import java.util.Properties;

public class prop {
public static void main( String[] args )
{
String testProp = "test.property";
Properties sysProps = System.getProperties();

System.out.println( "Value of " + testProp + " is " +
sysProps.getProperty(testProp) );
}
}
And here is the simple Ant build.xml that compiles and executes that class.

<project default="run">

<target name="compile">
<javac srcdir="." />
</target>

<target name="run" depends="compile">
<java classname="prop"
fork="true">
</java>
</target>

</project>
With the following result:

$ ant run
Buildfile: build.xml

compile:
[javac] Compiling 1 source file

run:
[java] Value of test.property is null

BUILD SUCCESSFUL
Total time: 6 seconds
To specify a System Property we use the sysproperty attribute of the java task. We'll change the run target to assign the value blue to the key test.property

<target name="run" depends="compile">
<java classname="prop"
fork="true">
<sysproperty key="test.property"
value="blue"
/>
</java>
</target>
And we get the expected output on execution.

$ ant
Buildfile: build.xml

compile:

run:
[java] Value of test.property is blue

BUILD SUCCESSFUL
Total time: 3 seconds
So far, so good. Of course, if test.property is going to have a fixed value, then we might as well set it from within our application. If test.property will typically have a wide range of simple values, then it is probably best managed as a parameter with a default value that can be overridden from the command line.

<project default="run">

<target name="compile">
<javac srcdir="." />
</target>

<property name="COLOR" value="red" />
<target name="run" depends="compile">
<java classname="prop"
fork="true">
<sysproperty key="test.property"
value="${COLOR}"
/>
</java>
</target>

</project>
And if the property has a small number of complex values, then it can be set based upon another parameter that can be set from the command line.

<project default="run">

<target name="compile">
<javac srcdir="." />
</target>

<property name="COLOR" value="red" />
<target name="setup-run">
<condition property="COMPLEX-COLOR"
value="complex.property.when.red.">
<contains string="red" substring="${COLOR}" casesensitive="false" />
</condition>
<condition property="COMPLEX-COLOR"
value="complex.property.when.blue.">
<contains string="blue" substring="${COLOR}" casesensitive="false" />
</condition>
<condition property="COMPLEX-COLOR"
value="complex.property.when.unknown.">
<not>
<isset property="COMPLEX-COLOR" />
</not>
</condition>
</target>

<target name="run" depends="compile,setup-run">
<java classname="prop"
fork="true">
<sysproperty key="test.property"
value="${COMPLEX-COLOR}"
/>
</java>
</target>

</project>
Which let's us control the property from the command line:

$ ant
Buildfile: build.xml

compile:

setup-run:

run:
[java] Value of test.property is complex.property.when.red.

BUILD SUCCESSFUL
Total time: 3 seconds
$ ant -DCOLOR=BLue
Buildfile: build.xml

compile:

setup-run:

run:
[java] Value of test.property is complex.property.when.blue.

BUILD SUCCESSFUL
Total time: 3 seconds
$ ant -DCOLOR=purple
Buildfile: build.xml

compile:

setup-run:

run:
[java] Value of test.property is complex.property.when.unknown.

BUILD SUCCESSFUL
Total time: 3 seconds

Setting Properties with Ant Tasks

In this Ant tutorial installment, we'll take a closer look at setting properties. The combination of properties and conditional execution allows more robust build files that adapt to the computing environment. Start with this basic build.xml file.

<project default="set">
<target name="set">
<property name="status" value="false"/>
<echo message="the status is ${status}" />
</target>

<property name="status" value="true"/>
</project>
And then execute ant.

$ ant
Buildfile: build.xml

set:
[echo] the status is true

BUILD SUCCESSFUL
Total time: 2 seconds
This demonstrates two characteristics of the property task:
  1. Properties are immutable. Once they are set, they stay set (with some inevitable exceptions). If properties were mutable, then the property assignment task <property name="status" value="false"/> would result in the message the status is false.
  2. Property tasks that are direct children of the project are executed before any target task is executed. In this case, the property assignment task <property name="status" value="true"/> is a direct child of the project and is executed before the target set.
Now let's see how we can use the available task to set a property value. The available task can be used to test for the existence of a file or directory, test for the existence of a class in a classpath, and test for the existence of a system resource. We'll just work with the first option, please see the fine manual for the other choices. This build file tests for the presence of foo.

<project default="all">
<target name="init">
<available property="present"
file="foo"
/>
</target>

<target name="exists" depends="init" if="present">
<echo message="foo exists" />
</target>

<target name="all" depends="exists" />
</project>
Execute this build.xml file twice. Once with an empty directory and once after creating file foo with the touch command.

$ ls
build.xml
$ ant
Buildfile: build.xml

init:

exists:

all:

BUILD SUCCESSFUL
Total time: 3 seconds
$ touch foo
$ ant
Buildfile: build.xml

init:

exists:
[echo] foo exists

all:

BUILD SUCCESSFUL
Total time: 3 seconds
In the previous example, we only checked for the existence of foo. In this example, we will also identify whether it is a directory or a file. The available task's type="dir" attribute specifically tests for a directory and is used to set property isaDir. We could use the type="file" attribute to set property isaFile but we'll use the condition task instead. The condition task is the generalized method for setting parameters. Please see the manual for a complete list of the supported conditions.

<project default="all">
<target name="init">
<available property="present"
file="foo"
/>
<available property="isaDir"
file="foo"
type="dir"
/>
<condition property="isaFile">
<and>
<isset property="present" />
<not>
<isset property="isaDir" />
</not>
</and>
</condition>
</target>

<target name="exists" depends="init" if="present">
<echo message="foo exists" />
</target>

<target name="isFile" depends="init" if="isaFile">
<echo message="foo is is a file" />
</target>

<target name="isDir" depends="init" if="isaDir">
<echo message="foo is is a directory" />
</target>

<target name="all" depends="exists,isFile,isDir" />
</project>
Execute this build file three times. Once with an empty directory, once after creating directory foo, and once after removing directory and creating file foo.

$ ls
build.xml
$ ant
Buildfile: build.xml

init:

exists:

isFile:

isDir:

all:

BUILD SUCCESSFUL
Total time: 3 seconds
Ant/Setting 41 $ mkdir foo
mkdir foo
Ant/Setting 42 $ ant
ant
Buildfile: build.xml

init:

exists:
[echo] foo exists

isFile:

isDir:
[echo] foo is is a directory

all:

BUILD SUCCESSFUL
Total time: 3 seconds
$ rmdir foo
$ touch foo
$ ant
Buildfile: build.xml

init:

exists:
[echo] foo exists

isFile:
[echo] foo is is a file

isDir:

all:

BUILD SUCCESSFUL
Total time: 3 seconds

Modifying Property Files with Ant

I was recently testing a Web Service that expected a unique key be passed with each invocation. I could have used the date/time or I could have persisted a counter in a database. Instead, I used the optional ant propertyfile task to persist a counter in a property file.
Here is a simple ant build file demonstrating how to increment a counter in a Java properties file. The propertyfile file attribute specifies the name of the property file and each nested entry element specifies how the value of a property file key should be modified. In this case, the value of the key "count" is treated as an int and the value 1 is added upon each invocation.

<project default="run">
<target name="run">
<propertyfile file="test.properties">
<entry key="count" type="int" operation="+" value="1" />
</propertyfile>
</target>
</project>
Here is the build file in operation.

1 $ cat test.properties
count=100
2 $ ant run
Buildfile: build.xml

run:
[propertyfile] Updating property file: /Projects/Learn/Ant/propertyfile/test.properties

BUILD SUCCESSFUL
Total time: 0 seconds
3 $ cat test.properties
#Sun Oct 01 14:11:06 EDT 2006
count=101
The propertyfile entry element also allows us to specify a default value.

<project default="run">
<target name="run">
<propertyfile file="test.properties">
<entry key="count" default="10" type="int" operation="+" value="1" />
</propertyfile>
</target>
</project>

1 $ rm test.properties
2 $ ant run
Buildfile: build.xml

run:
[propertyfile] Creating new property file: /Projects/Learn/Ant/propertyfile/test.properties

BUILD SUCCESSFUL
Total time: 0 seconds
3 $ cat test.properties
#Sun Oct 01 14:30:18 EDT 2006
count=11
4 $ ant run
Buildfile: build.xml

run:
[propertyfile] Updating property file: /Projects/Learn/Ant/propertyfile/test.properties

BUILD SUCCESSFUL
Total time: 0 seconds
5 $ cat test.properties
#Sun Oct 01 14:37:12 EDT 2006
count=12
And finally, another test to show that any keys not explicitly specified are left alone.

1 $ cat test.properties
#Sun Oct 01 14:37:12 EDT 2006
count=12
more=another value
andmore=yet another value
2 $ ant run
Buildfile: build.xml

run:
[propertyfile] Updating property file: /Projects/Learn/Ant/propertyfile/test.properties

BUILD SUCCESSFUL
Total time: 0 seconds
3 $ cat test.properties
#Sun Oct 01 14:47:09 EDT 2006
andmore=yet another value
more=another value
count=13

Ant Hello World Revisited

We'll continue our Ant tutorials with "Hello World," only now we'll execute a Java class. Start by creating a simple hello class as shown below.

public class hello {
public static void main( String[] args )
{
System.out.println( "Hello World" );
}
}
From the command line, compiling, jarring and executing this class is as simple as:

$ javac hello.java
$ jar -cvf hello.jar hello.class
added manifest
adding: hello.class(in = 415) (out= 285)deflated 31%)
$ java -classpath hello.jar hello
Hello World
Now let's write an Ant build file to compile. Naturally, you should start by reading the fine manual description of the javac task. We'll just use the srcdir attribute to compile all java files in the directory tree rooted at srcdir.

<project default="compile">
<target name="compile">
<javac srcdir="." />
</target>

</project>
Executing ant from the command line:

$ rm *.class
$ ant -f hello.xml compile
Buildfile: hello.xml

compile:
[javac] Compiling 1 source file

BUILD SUCCESSFUL
Total time: 4 seconds
Now, let's add a target to create the jar (added lines are shown in bold). The destfile attribute is required and both the basedir and the includes attributes are necessary in this case. The jar file will only contain the manifest file without the basedir attribute and the includes attribute is required to exclude all the non-class files (in particular, the jar file cannot contain itself). The regular expression used in the includes attribute will match all class files in the directory tree rooted at basedir.

<project default="compile">
<target name="compile">
<javac srcdir="." />
</target>

<target name="jar" depends="compile">
<jar destfile="hello.jar"
basedir="."
includes="**/*.class"
/>
</target>
</project>
Executing ant from the command line:

$ rm *.jar
$ ant -f hello.xml jar
Buildfile: hello.xml

compile:

jar:
[jar] Building jar: /Tutorial/Ant/Jar/hello.jar

BUILD SUCCESSFUL
Total time: 2 seconds
$ jar -tvf hello.jar
jar -tvf hello.jar
0 Wed Jan 22 17:06:32 EST 2003 META-INF/
55 Wed Jan 22 17:06:32 EST 2003 META-INF/MANIFEST.MF
335 Wed Jan 22 16:36:16 EST 2003 hello.class
Finally, let's add a target to execute our class (added lines in bold). We insure that the jar is always built first by having the execution target run depend upon the jar target. In this example, both the classname and the classpath attributes of the java task are used to completely specify the class to execute — eliminating a dependence upon the CLASSPATH environment variable. And we request a new JVM by setting fork="true"; providing the class full access to the java runtime and preventing a call to System.exit() from terminating the ant process.

<project default="compile">
<target name="compile">
<javac srcdir="." />
</target>

<target name="jar" depends="compile">
<jar destfile="hello.jar"
basedir="."
includes="**/*.class"
/>
</target>

<target name="run" depends="jar">
<java classname="hello"
classpath="hello.jar"
fork="true"
/>
</target>
</project>
And execute:

$ant -f hello.xml run
Buildfile: hello.xml

compile:

jar:

run:
[java] Hello World

BUILD SUCCESSFUL
Total time: 2 seconds

Ant Hello World

We'll continue our Ant tutorials with "Hello World," only now we'll execute a Java class. Start by creating a simple hello class as shown below.

public class hello {
public static void main( String[] args )
{
System.out.println( "Hello World" );
}
}
From the command line, compiling, jarring and executing this class is as simple as:

$ javac hello.java
$ jar -cvf hello.jar hello.class
added manifest
adding: hello.class(in = 415) (out= 285)deflated 31%)
$ java -classpath hello.jar hello
Hello World
Now let's write an Ant build file to compile. Naturally, you should start by reading the fine manual description of the javac task. We'll just use the srcdir attribute to compile all java files in the directory tree rooted at srcdir.

<project default="compile">
<target name="compile">
<javac srcdir="." />
</target>

</project>
Executing ant from the command line:

$ rm *.class
$ ant -f hello.xml compile
Buildfile: hello.xml

compile:
[javac] Compiling 1 source file

BUILD SUCCESSFUL
Total time: 4 seconds
Now, let's add a target to create the jar (added lines are shown in bold). The destfile attribute is required and both the basedir and the includes attributes are necessary in this case. The jar file will only contain the manifest file without the basedir attribute and the includes attribute is required to exclude all the non-class files (in particular, the jar file cannot contain itself). The regular expression used in the includes attribute will match all class files in the directory tree rooted at basedir.

<project default="compile">
<target name="compile">
<javac srcdir="." />
</target>

<target name="jar" depends="compile">
<jar destfile="hello.jar"
basedir="."
includes="**/*.class"
/>
</target>
</project>
Executing ant from the command line:

$ rm *.jar
$ ant -f hello.xml jar
Buildfile: hello.xml

compile:

jar:
[jar] Building jar: /Tutorial/Ant/Jar/hello.jar

BUILD SUCCESSFUL
Total time: 2 seconds
$ jar -tvf hello.jar
jar -tvf hello.jar
0 Wed Jan 22 17:06:32 EST 2003 META-INF/
55 Wed Jan 22 17:06:32 EST 2003 META-INF/MANIFEST.MF
335 Wed Jan 22 16:36:16 EST 2003 hello.class
Finally, let's add a target to execute our class (added lines in bold). We insure that the jar is always built first by having the execution target run depend upon the jar target. In this example, both the classname and the classpath attributes of the java task are used to completely specify the class to execute — eliminating a dependence upon the CLASSPATH environment variable. And we request a new JVM by setting fork="true"; providing the class full access to the java runtime and preventing a call to System.exit() from terminating the ant process.

<project default="compile">
<target name="compile">
<javac srcdir="." />
</target>

<target name="jar" depends="compile">
<jar destfile="hello.jar"
basedir="."
includes="**/*.class"
/>
</target>

<target name="run" depends="jar">
<java classname="hello"
classpath="hello.jar"
fork="true"
/>
</target>
</project>
And execute:

$ant -f hello.xml run
Buildfile: hello.xml

compile:

jar:

run:
[java] Hello World

BUILD SUCCESSFUL
Total time: 2 seconds