Enterprise Java Development@TOPIC@
This chapter demonstrates the basics of automating the manual steps in the previous chapter using the Apache Ant build tool. If you just skim thru this step, please be sure to take note of how everything gets explicitly defined in Ant. There are not many rules of the road and standard defaults to live by. That will be a big contrast when working with Maven.
All course examples and projects submitted will use Maven. Ant will be used to wrap command lines for Java SE clients executed outside the normal build environment. However, this exercise shows Ant only being used as part of the artifact build and test environment as a stepping stone to understanding some of the basic build and test concepts within Maven.
If you do not have Ant installed on your system, it can be from http://ant.apache.org/
This chapter is optional!!! It contains many tedious steps to setup a module build using the Ant build tool -- which will not be part of class. It is presented here as an example option to building the module with shell scripts. If you wish to just skim the steps -- please do. Please do not waste time trying to get Ant to build your Java modules for this class.
Create a build.properties file in $PROJECT_BASEDIR. This will be used to define any non-portable property values. Place the most non-portable base variables (.e.g, M2_REPO location) towards the top and build lower-level paths from them. This makes the scripts much easier to port to another environment. If you still have your maven repository in your $HOME directory, you can make use of ${user.home} environment variable rather than a hard-coded path.
#ex1 build.properties #M2_REPO=c:/jhu/repository M2_REPO=${user.home}/.m2/repository junit.classpath=${M2_REPO}/junit/junit/4.10/junit-4.10.jar
Create a build.xml file in $PROJECT_BASEDIR. Note the following key elements.
project - a required root for build.xml files
name - not significant, but helpful
default - the target to run if none is supplied on command line
basedir - specifies current directory for all tasks
property - defines an immutable name/value
file - imports declarations from a file; in this case build.properties created earlier
name/value - specifies a property within the script
target - defines an entry point into the build.xml script. It hosts one or more tasks.
name - defines name of target, which can be supplied on command line.
echo - a useful Ant task to printout status and debug information. See Ant docs for more information.
<?xml version="1.0" encoding="utf-8" ?>
<!-- ex1 build.xml
-->
<project name="ex1" default="" basedir=".">
<property file="build.properties"/>
<property name="artifactId" value="ex1"/>
<property name="src.dir" value="${basedir}/src"/>
<property name="build.dir" value="${basedir}/target"/>
<target name="echo">
<echo>basedir=${basedir}</echo>
<echo>artifactId=${artifactId}</echo>
<echo>src.dir=${src.dir}</echo>
<echo>build.dir=${build.dir}</echo>
<echo>junit.classpath=${junit.classpath}</echo>
</target>
</project>
Sanity check your build.xml and build.properties file with the echo target.
$ ant echo Buildfile: /home/jim/proj/784/exercises/ex1/build.xml echo: [echo] basedir=/home/jim/proj/784/exercises/ex1 [echo] artifactId=ex1 [echo] src.dir=/home/jim/proj/784/exercises/ex1/src [echo] build.dir=/home/jim/proj/784/exercises/ex1/target [echo] junit.classpath=/home/jim/.m2/repository/junit/junit/4.10/junit-4.10.jar BUILD SUCCESSFUL Total time: 0 seconds
Add the "package" target to compile and archive your /src/main classes. Note the following tasks in this target.
mkdir - creates a directory. See Ant Mkdir docs for more infomation.
javac - compiles java sources files. See Ant Javac docs for more information. Note that we are making sure we get JavaSE 8 classes compiled.
jar - builds a java archive. See Ant Jar Docs for more information.
<target name="package">
<mkdir dir="${build.dir}/classes"/>
<javac srcdir="${src.dir}/main/java"
destdir="${build.dir}/classes"
debug="true"
source="1.8"
target="1.8"
includeantruntime="false">
<classpath>
</classpath>
</javac>
<jar destfile="${build.dir}/${artifactId}.jar">
<fileset dir="${build.dir}/classes"/>
</jar>
</target>
Execute the "package" target just added. This should compile the production class from src/main into target/classes and build a Java archive with the production class in target/.
$ rm -rf target/; ant package Buildfile: /home/jim/proj/784/exercises/ex1/build.xml package: [mkdir] Created dir: /home/jim/proj/784/exercises/ex1/target/classes [javac] Compiling 1 source file to /home/jim/proj/784/exercises/ex1/target/classes [jar] Building jar: /home/jim/proj/784/exercises/ex1/target/ex1.jar BUILD SUCCESSFUL Total time: 2 seconds
You may get the following error when you execute the javac task. If so, export JAVA_HOME=(path to JDK_HOME) on your system to provide Ant a reference to a JDK instance.
build.xml:26: Unable to find a javac compiler; com.sun.tools.javac.Main is not on the classpath. Perhaps JAVA_HOME does not point to the JDK. It is currently set to ".../jre"
$ find . -type f ./src/main/java/myorg/mypackage/ex1/App.java ./src/test/java/myorg/mypackage/ex1/AppTest.java ./build.properties ./build.xml ./target/classes/myorg/mypackage/ex1/App.class ./target/ex1.jar
Add the "test" target to compile your /src/test classes. Make this the default target for your build.xml file. Note too that it should depend on the successful completion of the "package" target and include the produced archive in its classpath.
<project name="ex1" default="test" basedir=".">
...
<target name="test" depends="package">
<mkdir dir="${build.dir}/test-classes"/>
<javac srcdir="${src.dir}/test/java"
destdir="${build.dir}/test-classes"
debug="true"
source="1.8"
target="1.8"
includeantruntime="false">
<classpath>
<pathelement location="${build.dir}/${artifactId}.jar"/>
<pathelement path="${junit.classpath}"/>
</classpath>
</javac>
</target>
Execute the new "test" target after clearing out the contents of the target directory. Note that the target directory gets automatically re-populated with the results of the "compile" target and augmented with the test class from src/test compiled into target/test-classes.
$ rm -rf target/; ant Buildfile: /home/jim/proj/784/exercises/ex1/build.xml package: [mkdir] Created dir: /home/jim/proj/784/exercises/ex1/target/classes [javac] Compiling 1 source file to /home/jim/proj/784/exercises/ex1/target/classes [jar] Building jar: /home/jim/proj/784/exercises/ex1/target/ex1.jar test: [mkdir] Created dir: /home/jim/proj/784/exercises/ex1/target/test-classes [javac] Compiling 1 source file to /home/jim/proj/784/exercises/ex1/target/test-classes BUILD SUCCESSFUL Total time: 3 seconds
> find . -type f ./src/main/java/myorg/mypackage/ex1/App.java ./src/test/java/myorg/mypackage/ex1/AppTest.java ./build.properties ./build.xml ./target/classes/myorg/mypackage/ex1/App.class ./target/ex1.jar ./target/test-classes/myorg/mypackage/ex1/AppTest.class
Add the junit task to the test target. The junit task is being configured to run in batch mode and write a TXT and XML reports to the target/test-reports directory. See Ant docs for more details on the junit task. Make special note of the following:
printsummary - produce a short summary to standard out showing the number of tests run and a count of errors, etc.
fork - since Ant runs in a JVM, any time you run a task that requires a custom classpath, it is usually required that it be forked into a separate process (with its own classpath).
batchtest - run all tests found and write results of each test into the test-reports directory.
formatter - write a text and XML report of results
<mkdir dir="${build.dir}/test-reports"/>
<junit printsummary="true" fork="true">
<classpath>
<pathelement path="${junit.classpath}"/>
<pathelement location="${build.dir}/${artifactId}.jar"/>
<pathelement location="${build.dir}/test-classes"/>
</classpath>
<batchtest fork="true" todir="${build.dir}/test-reports">
<fileset dir="${build.dir}/test-classes">
<include name="**/*Test*.class"/>
</fileset>
</batchtest>
<formatter type="plain"/>
<formatter type="xml"/>
</junit>
A few years ago when I sanity checked this exercise I got the common error below. I corrected the issue by downloading a full installation from the Ant website and exporting my ANT_HOME to the root of that installation. (export ANT_HOME=/opt/apache-ant-1.9.4) and adding $ANT_HOME/bin to the PATH (export PATH=$ANT_HOME/bin:$PATH) ANT_HOME is required for Ant to locate the junit task.
BUILD FAILED /home/jim/proj/784/exercises/ex1/build.xml:57: Problem: failed to create task or type junit Cause: the class org.apache.tools.ant.taskdefs.optional.junit.JUnitTask was not found. This looks like one of Ant's optional components. Action: Check that the appropriate optional JAR exists in -/usr/share/ant/lib -/home/jim/.ant/lib -a directory added on the command line with the -lib argument Do not panic, this is a common problem. The commonest cause is a missing JAR. This is not a bug; it is a configuration problem
Execute the updated "test" target with the JUnit test.
$ rm -rf target; ant package: [mkdir] Created dir: /home/jim/proj/784/exercises/ex1/target/classes [javac] Compiling 1 source file to /home/jim/proj/784/exercises/ex1/target/classes [jar] Building jar: /home/jim/proj/784/exercises/ex1/target/ex1.jar test: [mkdir] Created dir: /home/jim/proj/784/exercises/ex1/target/test-classes [javac] Compiling 1 source file to /home/jim/proj/784/exercises/ex1/target/test-classes [mkdir] Created dir: /home/jim/proj/784/exercises/ex1/target/test-reports [junit] Running myorg.mypackage.ex1.AppTest [junit] Tests run: 2, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 15.143 sec [junit] Test myorg.mypackage.ex1.AppTest FAILED BUILD SUCCESSFUL Total time: 17 seconds
$ find . -type f ./src/main/java/myorg/mypackage/ex1/App.java ./src/test/java/myorg/mypackage/ex1/AppTest.java ./build.properties ./build.xml ./target/classes/myorg/mypackage/ex1/App.class ./target/ex1.jar ./target/test-classes/myorg/mypackage/ex1/AppTest.class ./target/test-reports/TEST-myorg.mypackage.ex1.AppTest.txt ./target/test-reports/TEST-myorg.mypackage.ex1.AppTest.xml
Note the 17 seconds it took to run/complete the test seems excessive. I was able to speed that up to 0.001 sec by commenting out the XML report option (which we will not use in this exercise).
Test output of each test is in the TXT and XML reports.
$ more target/test-reports/TEST-myorg.mypackage.ex1.AppTest.txt Testsuite: myorg.mypackage.ex1.AppTest Tests run: 2, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 15.246 sec ------------- Standard Output --------------- testApp Here's One! testFail Here's One! ------------- ---------------- --------------- Testcase: testApp took 0.007 sec Testcase: testFail took 0.022 sec FAILED app didn't return 0 junit.framework.AssertionFailedError: app didn't return 0 at myorg.mypackage.ex1.AppTest.testFail(AppTest.java:26)
Add a clean target to the build.xml file to delete built artifacts. See Ant docs for details on the delete task.
<target name="clean">
<delete dir="${build.dir}"/>
</target>
Re-run and use the new "clean" target you just added.
$ ant clean test Buildfile: /home/jim/proj/784/exercises/ex1/build.xml clean: [delete] Deleting directory /home/jim/proj/784/exercises/ex1/target package: [mkdir] Created dir: /home/jim/proj/784/exercises/ex1/target/classes [javac] Compiling 1 source file to /home/jim/proj/784/exercises/ex1/target/classes [jar] Building jar: /home/jim/proj/784/exercises/ex1/target/ex1.jar test: [mkdir] Created dir: /home/jim/proj/784/exercises/ex1/target/test-classes [javac] Compiling 1 source file to /home/jim/proj/784/exercises/ex1/target/test-classes [mkdir] Created dir: /home/jim/proj/784/exercises/ex1/target/test-reports [junit] Running myorg.mypackage.ex1.AppTest [junit] Tests run: 2, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 15.123 sec [junit] Test myorg.mypackage.ex1.AppTest FAILED BUILD SUCCESSFUL Total time: 17 seconds
Comment out the bogus testFail and rerun.
$ cat src/test/java/myorg/mypackage/ex1/AppTest.java ... //@Test public void testFail() {
$ ant clean test Buildfile: /home/jim/proj/784/exercises/ex1/build.xml clean: [delete] Deleting directory /home/jim/proj/784/exercises/ex1/target package: [mkdir] Created dir: /home/jim/proj/784/exercises/ex1/target/classes [javac] Compiling 1 source file to /home/jim/proj/784/exercises/ex1/target/classes [jar] Building jar: /home/jim/proj/784/exercises/ex1/target/ex1.jar test: [mkdir] Created dir: /home/jim/proj/784/exercises/ex1/target/test-classes [javac] Compiling 1 source file to /home/jim/proj/784/exercises/ex1/target/test-classes [mkdir] Created dir: /home/jim/proj/784/exercises/ex1/target/test-reports [junit] Running myorg.mypackage.ex1.AppTest [junit] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 15.161 sec BUILD SUCCESSFUL Total time: 17 seconds
In this chapter you performed many of the same actions you did in the previous chapter except that you implemented in a portable build tool called Ant. Ant is very powerful and open-ended. You were shown these same steps in Ant as an intermediate towards Maven. Ant, being open-ended, does not follow many conventions. Everything must be explicitely specified. You will find that to be in significant contrast with Maven. Ant is a "build tool" and Maven is a "build system" with conventions/rules.