Task Dependencies: ant

Steven J Zeil

Last modified: Oct 20, 2020
Contents:

Abstract

ant is a build manager based upon a task dependency graph expressed in an XML file

In this lesson we look at how ant addresses a number of shortcomings of make.

We will look at the task-based build model implemented by ant at how this differs from the dependency-based model of make, and at how to describe projects to the ant tool.

We will look at how ant could be applied to some of our sample projects.


ant


What’s Wrong with make?

ant is actually an acronym for Another Neat Tool.

But why do we need “another” tool, however neat, for build management?


Other Criticisms

1 The ant Command

 


ant Options

Some useful options:

-k, -keep-going
“Keep going.” Don’t stop the build at the first failue, but continue building any required targets that do not depend on the one whose construction has failed.
-f filename
Use filename instead of the default build.xml. Also -file or -buildfile
-Dproperty=value
Sets a property (similar to make’s variables)

2 Build Files

The ant build file is an XML file.

<project name="382Website" default="deploy">
     <description>
        Extract Metadata Extractor - top level
    </description>
  ⋮
</project>

2.1 Targets

At its heart, a build file is a collection of targets.

ant targets correspond, roughly, to make’s “artificial targets”.


Example of Targets

simplebuild.xml.listing
<project name="JavaBuild" default="deploy"> ➀
  <description>
    Example of a simple project build
  </description>

  <target name="compile" description="Compile src/.../*.java into bin/"> ➁
    <mkdir dir="bin" />                    ➂
    <javac srcdir="src" destdir="bin"
           debug="true" includeantruntime="false"/>
    <echo>compiled </echo>
  </target>

  <target name="unittest" depends="compile" unless="test.skip"> ➃
    <mkdir dir="test-reports" />
    <junit printsummary="on" haltonfailure="true" 
           fork="true" forkmode="perTest">
      <formatter type="plain" />
      <batchtest todir="test-reports">
        <fileset dir="bin">
          <include name="**/Test*.class" />
          <exclude name="**/Test*$*.class" />
        </fileset>
      </batchtest>
    </junit>
  </target>

  <target name="deploy" depends="unittest" description="Create project's Jar file">
    <jar destfile="myProject.jar">
      <fileset dir="bin"/>
    </jar>
  </target>
</project>

The project has a name and default target

A basic target. It is named “compile” and has a description (which may be picked up by some IDEs)

This target has 3 tasks. It creates a directory, compiles Java source code, and prints a message when completed.

This target illustrates both a dependency and a condition.


Task versus File Dependencies

ant targets correspond, roughly, to make’s “artificial targets”.

So this build file

simplebuild.xml.listing
<project name="JavaBuild" default="deploy"> ➀
  <description>
    Example of a simple project build
  </description>

  <target name="compile" description="Compile src/.../*.java into bin/"> ➁
    <mkdir dir="bin" />                    ➂
    <javac srcdir="src" destdir="bin"
           debug="true" includeantruntime="false"/>
    <echo>compiled </echo>
  </target>

  <target name="unittest" depends="compile" unless="test.skip"> ➃
    <mkdir dir="test-reports" />
    <junit printsummary="on" haltonfailure="true" 
           fork="true" forkmode="perTest">
      <formatter type="plain" />
      <batchtest todir="test-reports">
        <fileset dir="bin">
          <include name="**/Test*.class" />
          <exclude name="**/Test*$*.class" />
        </fileset>
      </batchtest>
    </junit>
  </target>

  <target name="deploy" depends="unittest" description="Create project's Jar file">
    <jar destfile="myProject.jar">
      <fileset dir="bin"/>
    </jar>
  </target>
</project>

is roughly equivalent to this makefile

simplemake.listing
JAVAFILESsrc=$(shell find src/ -name '*.java')
JAVAFILES=$(JAVAFILESsrc:src/%=%)
CLASSFILES=$(JAVAFILES:%.java=%.class)
TESTFILES=$(shell find src/ -name 'Test*.java')
TESTS=$(TESTFILES:src/%.java=%)

deploy: unittest
	cd bin; jar cvf myProject.jar `find . -name '*.class'`

unittest: build
	cd bin; for test in $(TESTS); do \
	   java $$test; \
	done
build:
	cd src; javac -d ../bin -g $(JAVAFILES)

though a “real” makefile author would probably write this:

simplemake2.listing
JAVAFILESsrc=$(shell find src/ -name '*.java')
JAVAFILES=$(JAVAFILESsrc:src/%=%)
CLASSFILES=$(JAVAFILES:%.java=%.class)
TESTFILES=$(shell find src/ -name 'Test*.java')
TESTS=$(TESTFILES:src/%.java=%)

deploy: myProject.jar
unittest: testReport.txt
build: $(CLASSFILES)

myProject.jar: testReport.txt $(CLASSFILES)
	cd bin; jar cvf myProject.jar `find . -name '*.class'`

testReport.txt: $(CLASSFILES)
	-rm testReport.txt
	cd bin; for test in $(TESTS); do \
	   java $$test >> testReport.txt; \
	done


bin/%.class: src/%.java
	cd src; javac -d ../bin -g $*.java

Make Efficiency

If we do

make
make

The second command does not actually perform any steps.


Ant Efficiency

What happens if we do

ant
ant -Dskip.test=1

2.2 Ant Building Blocks and Concepts

(Optional reading. Read on if you actually want to use Ant. Otherwise, jump to Case Studies.)

2.2.1 Properties

Properties are named string values.


The <property Task

Two basic modes:


Additional <property Variants

2.2.2 File Sets and Lists


File Sets

<fileset file="src/main.cpp"/>
<fileset dir="src"
    includes="main.cpp utility.h utility.cpp"/>
<fileset dir="src" includes="*.cpp,*.h"/>

File Lists

<filelist dir="src"
    files="main.cpp utilities.h utilities.cpp"/>

Mappers


Selectors

Selectors provide more options for selecting files than simple include/exclude based on the names.

2.2.3 Path Sets

Used to specify a sequence of paths, usually to be searched.

<classpath>
    <pathelement path="${env.CLASSPATH}"/>
    <fileset dir="target/classes">
       <include name="**/*.class"/>
    </fileset>
    <filelist refid="third-party_jars"/>
</classpath>

Referencing Path Sets

2.2.4 Filters

Filters are used to modify the outputs of some commands by performing various substitutions:

<copy file="../../templates/@{format}.tex"
      tofile="${doc}-@{format}.ltx">
  <filterset>
    <filter token="doc" value="${doc}"/>
    <filter token="relPath" value="${relPath}"/>
    <filter token="format" value="@{format}"/>
  </filterset>
</copy>

A filter set replaces tokens like @doc@ by a string, in this case the value of the property ${doc}


Filter Chains

Filter chains offer a variety of more powerful options, e.g.,

<loadfile property="doctitle" srcfile="${doc}.info.tex">
  <filterchain>
    <linecontains>
      <contains value="\title{"/>
    </linecontains>
    <tokenfilter>
      <replaceregex pattern=" *\\title[{]([^}]*)[}]"
                    replace="\1"/>
    </tokenfilter>
  </filterchain>
</loadfile>

2.3 Tasks

The Ant Manual has a good breakdown on these.


Extending Ant

<project name="code2html" default="build">

  <taskdef classpath="JFlex.jar"
           classname="JFlex.anttask.JFlexTask"
           name="jflex" />
     ⋮

  <target name="generateSource">
    <mkdir dir="src/main/java"/>
    <jflex file="src/main/jflex/code2html.flex"
	   destdir="src/main/java"/>
    <jflex file="src/main/jflex/code2tex.flex"
  	       destdir="src/main/java"/>
       ⋮

Finding Extensions

3 Case Studies

3.1 Simple Java Build

build.xml.sjb.listing
<project name="codeAnnotation" basedir="." default="build"
	 xmlns:ivy="antlib:org.apache.ivy.ant">

  <record name="ant.log" action="start" append="false" />  ➀
	
  <path id="testCompilationPath">                         ➁
    <fileset dir="lib" includes="*.jar"/>
    <pathelement path="target/classes"/>
  </path>

  <path id="testExecutionPath">
    <fileset dir="lib" includes="*.jar"/>
    <pathelement path="target/classes"/>
    <pathelement path="target/test-classes"/>
  </path>

  <property name="build.dir" value="build"/>
  <property name="src.dir" value="src"/>

	
  <import file="ivyBuild.xml" as="ivy"/>                 ➂


  <target name="compile" depends="ivy.resolve-ivy"        ➃
	description=
  	"Compile all source code, including the lexical
	 analysis code generated using jflex." 
  	>
    <mkdir dir="target/classes"/>
    <javac srcdir="src/main/java" destdir="target/classes"
	   source="1.8" includeantruntime="false"/>
  </target>

  <target name="compile-tests" depends="compile"          ➄
  	description="Compile JUnit tests"
  	>
    <mkdir dir="target/test-classes"/>
    <javac srcdir="src/test/java" destdir="target/test-classes"
	   source="1.8" includeantruntime="false">
      <classpath refid="testCompilationPath"/>
    </javac>
  </target>

  <target name="test" depends="compile-tests"           
  	description="Run all JUnit tests, producing a
  	summary report in target/test-results."
  	>
    <mkdir dir="target/test-results/details"/>
    <junit printsummary="yes"                             ➅
	   haltonfailure="no" fork="no"                  
	   >
        <classpath refid="testExecutionPath"/>
      <formatter type="xml"/>
      <batchtest todir="target/test-results/details">
	     <fileset dir="target/test-classes">
	        <include name="**/*Test*.class"/>
	     </fileset>
      </batchtest>
    </junit>
    <junitreport todir="target/test-results">              ➆
      <fileset dir="target/test-results/details">
        <include name="TEST-*.xml"/>
      </fileset>
      <report format="frames" styledir="junit-stylesheets" 
         todir="target/test-results/html"/>
    </junitreport>
  </target>

  <target name="build"  depends="test"
  	description="Construct a jar file with the
  	compiled code and a zip file with the project
  	source code.">
  	
  	<jar destfile="codeAnnotation.jar" basedir="target/classes">  ➇
  	    <manifest>
  		    <attribute name="Main-Class"
  		               value="edu.odu.cs.code2html.Code2HTML"/>
  		</manifest>
  	</jar>
  </target>


  <target name="clean">                                           ➈
    <delete dir="target"/>
  </target>

  
</project>

You can find this entire project, with the Ant files, here.

3.2 Java Build with Code Generation

Not All Sourcecode is Hand-Written

This project adds a stage before compilation to generate some of the source code that then needs to be compiled.

build.xml.jcg.listing
<project name="codeAnnotation" basedir="." default="build"
	 xmlns:ivy="antlib:org.apache.ivy.ant">

  <record name="ant.log" action="start" append="false" />
	
  <path id="testCompilationPath">
    <fileset dir="lib" includes="*.jar"/>
    <pathelement path="target/classes"/>
  </path>

  <path id="testExecutionPath">
    <fileset dir="lib" includes="*.jar"/>
    <pathelement path="target/classes"/>
    <pathelement path="target/test-classes"/>
  </path>

  <property name="build.dir" value="build"/>
  <property name="src.dir" value="src"/>

	
  <import file="ivyBuild.xml" as="ivy"/>

  
  <target name="generateSource" depends="ivy.resolve-ivy"
  	description="Use jflex to process lexeme descriptions
  	  for C++ and Java, generating the Java source code for
  	  the resulting lexical analyzers."
  	>
    <taskdef classpath="lib/jflex-1.4.3.jar"                      ➀
	    classname="JFlex.anttask.JFlexTask" name="jflex" />
    <mkdir dir="target/gen/java"/>
    <jflex file="src/main/jflex/code2html.flex"                  ➁
	   destdir="target/gen/java"/>
    <jflex file="src/main/jflex/code2tex.flex"
	   destdir="target/gen/java"/>
    <jflex file="src/main/jflex/list2html.flex"
	   destdir="target/gen/java"/>
    <jflex file="src/main/jflex/list2tex.flex"
	   destdir="target/gen/java"/>
  </target>
  

  <target name="compile" depends="generateSource"     ➂
	description=
  	"Compile all source code, including the lexical
	 analysis code generated using jflex." 
  	>
    <mkdir dir="target/classes"/>
    <javac srcdir="target/gen/java" destdir="target/classes"  ➃
	   source="1.7" includeantruntime="false"/>
    <javac srcdir="src/main/java" destdir="target/classes"
	   source="1.7" includeantruntime="false"/>
  </target>

  <target name="compile-tests" depends="compile"
  	description="Compile JUnit tests"
  	>
    <mkdir dir="target/test-classes"/>
    <javac srcdir="src/test/java" destdir="target/test-classes"
	   source="1.7" includeantruntime="false">
      <classpath refid="testCompilationPath"/>
    </javac>
  </target>

  <target name="test" depends="compile-tests"
  	description="Run all JUnit tests, producing a
  	summary report in target/test-results."
  	>
    <mkdir dir="target/test-results/details"/>
    <junit printsummary="yes" 
	   haltonfailure="no" fork="no"
	   >
        <classpath refid="testExecutionPath"/>
      <formatter type="xml"/>
      <batchtest todir="target/test-results/details">
	     <fileset dir="target/test-classes">
	        <include name="**/*Test*.class"/>
	     </fileset>
      </batchtest>
    </junit>
    <junitreport todir="target/test-results">
      <fileset dir="target/test-results/details">
        <include name="TEST-*.xml"/>
      </fileset>
      <report format="frames" styledir="junit-stylesheets" todir="target/test-results/html"/>
    </junitreport>
  </target>

  <target name="build"  depends="test"
  	description="Construct a jar file with the
  	compiled code and a zip file with the project
  	source code.">
  	
  	<jar destfile="codeAnnotation.jar" basedir="target/classes">
  	    <manifest>
  		    <attribute name="Main-Class"
  		               value="edu.odu.cs.code2html.Code2HTML"/>
  		</manifest>
  	</jar>
  </target>


  <target name="clean">
    <delete dir="target"/>
  </target>

  <target name="cleaner" depends="clean">
    <delete dir="lib"/>
  </target>

  <target name="cleanest" depends="cleaner">
    <delete dir="ivy"/>
  </target>
  
</project>

You can find this entire project, with the Ant files, here.

3.3 C++ Multi-project Build

Because this project is divided into multiple subprojects, we will have multiple build.xml files:

3.3.1 The master file

In the project root directory:

build.xml.manroot.listing
<project name="manhattan" basedir='.' default="build">

  <record name="ant.log" action="start" append="false" />

  <!-- Invoke this build file as
     ant
       - to build the project
     ant clean
       - to clean out all generated files

  -->


  <target name="build">
      <ant target="build"  antfile="gtest/build.xml" inheritAll="false"/>
      <ant target="build"  antfile="lib/build.xml" inheritAll="false"/>
      <ant target="build"  antfile="exe/build.xml" inheritAll="false"/>
  </target>

  <target name="clean">
      <ant target="clean"  antfile="gtest/build.xml" inheritAll="false"/>
      <ant target="clean"  antfile="lib/build.xml" inheritAll="false"/>
      <ant target="clean"  antfile="exe/build.xml" inheritAll="false"/>
  </target>
  
</project>

3.3.2 The lib subproject

build.xml.manlib.listing
<project name="manhattan-lib" basedir="." default="build"
	xmlns:cpptasks="antlib:net.sf.antcontrib.cpptasks"
>

  <description>
    Builds the ADTs for the manhattan program.
  </description>

  <property file="${user.home}/.ant-global.properties"/>
  
  <record name="ant.log" action="start" append="false" />
  
  <taskdef classpath="../antcontrib-cpptasks.jar"               ➀
	   resource="net/sf/antcontrib/cpptasks/antlib.xml" />
    	
  <target name="compile">
    <mkdir dir="build/objs/main"/>
    <mkdir dir="build/libs"/>
    <cc outtype="static"                                     ➁
	subsystem="console" 
	name="g++"
	outfile="build/libs/main" 
	objdir="build/objs/main">
      <fileset dir="src/main/cpp">
	<include name="**/*.cpp"/>
      </fileset>
      <includepath path="src/main/headers"/>
    </cc>
  </target>

  <target name="compiletest" depends="compile">             ➂
    <mkdir dir="build/objs/test"/>
    <mkdir dir="build/exe"/>
    <cc outtype="executable" 
	subsystem="console" 
	name="g++"
	objdir="build/objs/test">
      <fileset dir="src/mainTest/cpp">
	<include name="**/*.cpp"/>
      </fileset>
      <compilerarg value='-g'/>
      <compilerarg value='-pthread'/>
      <includepath path="src/main/headers"/>
      <includepath path="../gtest/include"/>
    </cc>
    <apply executable="g++">  <!-- Using apply because I can't get   ➃
                                  linking to work in antcontrib -->
      <arg value="-o"/>
      <arg value="build/exe/runTests"/>
      <arg value="../gtest/build/libs/libgtest.a"/>
      <arg value="build/libs/libmain.a"/>
      <arg value="-lpthread"/>
      <fileset dir="build/objs/test">
	<include name="*.o"/>
      </fileset>
    </apply>
  </target>

  <target name="test" depends="compiletest">
    <exec executable="build/exe/runTests"/>          ➄
  </target>

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


  <target name="clean">
     <delete dir="build"/>
  </target>


</project>

The other subproject build.xml files are similar, but simpler.

You can find this entire project, with the Ant files, here.

4 Eclipse/Ant Integration

Limitations of Eclipse Builder


Project Dependencies

Eclipse supports the idea of projects that depend on other projects, so you could do


Eclipse/Ant Integration

Eclipse is generally ant-friendly.


Eclipse Builders

Eclipse supports multiple, plug-able builders.