Task Dependencies: ant
Steven J Zeil
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
-
ant devised by James Davidson of Sun, contributed to Apache project (along with what would eventually become TomCat), released in 2000
-
Quickly became a standard tool for Java projects
- slower to move into other arenas
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?
-
make works by issuing commands to /bin/sh
- That’s not portable.
-
The commands that people write into their makefile rules are generally not portable either:
- Commands themselves are system-dependent (e.g., mkdir, cp, chmod
- Paths are system-dependent (
/
in *nix versus\
in Windows, legal characters, quoting rules) - Path lists are system-dependent (
:
in *nix versus;
in Windows)
Other Criticisms
-
Some feel that make is too low-level with its focus on individual files
-
Some will feel that ant is too high-level
-
But this is the apparent rationale for moving the focus from file dependencies to task dependencies.
-
-
The makefile syntax is arcane and hard to work with.
-
And XML syntax isn’t?
-
1 The ant Command
-
ant looks for its instructions in a file named, by default, build.xml
-
The ant command can name any target to be built, e.g.,
ant setup
-
If no target is given, ant builds a target explicitly listed in build.xml as a default for the project.
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.
- The build file describes a project.
- The project has a name and a default target.
<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.
-
A target is an XML element and, as attributes, has a name and, optionally,
- a list of dependencies
- a condition
- a human-readable description
-
The target can contain multiple tasks, which contain the actual “commands” to get things done.
ant targets correspond, roughly, to make’s “artificial targets”.
Example of Targets
<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.
- The fact that the tag names resemble familiar commands is intended as self-documentation, but is not otherwise significant.
- The tag names actually map to Java class names that implement the task.
➃ This target illustrates both a dependency and a condition.
-
The tasks within this target would not be executed if I invoked ant like this:
ant -Dtest.skip=1
-
However, the
unittest
task would still be considered to have succeeded, in the sense that tasks that depend on it would be allowed to run.
Task versus File Dependencies
ant targets correspond, roughly, to make’s “artificial targets”.
So this build file
<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
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:
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
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.
-
Can be set from the command line or via
<property
and a few other tasks -
Accessed as
${
_propertyName_}
-
Properties are immutable: once set, attempts to re-assign their values are ignored
-
By convention, properties names are grouped into faux hierarchies with ‘.’
- e.g.,
compile.src
,compile.dest
,compile.options
- e.g.,
The <property
Task
Two basic modes:
-
<property name="compile.options" value="-g -O1"/>
Sets this property to “-g -O1”
-
<property name="compile.src" location="src/main/java"/>
Sets this property to the absolute path of the directory/file named.
- The / and \ characters are changed as necessary to conform to the OS on which ant is being run.
Additional <property
Variants
-
<property file="project.default.properties"/>
Loads property values from a file, written as a series of property
=
_value_ linescourseName=CS795 baseurl=https://www.cs.odu.edu/~zeil/cs795SD/s13 homeurl=https://www.cs.odu.edu/~zeil/cs795SD/s13/Directory/topics.html email=zeil@cs.odu.edu
- A common use of this it to load in a personal file of login credentials and other private data:
<property file="${user.home}/.ant-global.properties"/>
The property
${user.home}
maps to the user’s home directory as appropriate to the operating system. If the file.ant-global.properties
exists, can load private info.- Should be protected (e.g., Unix protection
600
)
If the file does not exist, this does nothing at all.
- Should be protected (e.g., Unix protection
- A common use of this it to load in a personal file of login credentials and other private data:
-
<property environment="env"/>
Copies the OS environment variables into the build state, prefaced by the indicated prefix
- e.g.,
${env.PATH}
- e.g.,
2.2.2 File Sets and Lists
-
A file set is a collection of existing files
- can be specified using wild cards
-
A file list is a collection of files that may or may not exist
- Must be specified explicitly without wild cards
File Sets
<fileset file="src/main.cpp"/>
<fileset dir="src"
includes="main.cpp utility.h utility.cpp"/>
<fileset dir="src" includes="*.cpp,*.h"/>
-
More commonly seen as a nested form
<fileset id="unitTests" dir="bin"> <include name="**/Test*.class"/> <exclude name="**/*$*.class"/> <exclude name="**/*Debug.class"/> </fileset>
-
The
id
in the prior example allows later references:<fileset refid="unitTests"/>
File Lists
<filelist dir="src"
files="main.cpp utilities.h utilities.cpp"/>
- Can also use
id
orrefid
attributes
Mappers
-
Allow for a transformation of file names
-
Some commands use a file set to describe inputs, then a mapper to describe outputs
<fileset dir="src" includes="*.cpp"/> <globmapper from="*.cpp" to="*.o"/>
would map each file in src/*.cpp to a corresponding .o file
-
And
<fileset dir="bin" includes="**/Test*.java"/> <packagemapper from="*.class" to="*"/>
would map a compiled unit test file project/package/TestADT.class to project.package.TestADT
-
There are several other mappers as well
Selectors
Selectors provide more options for selecting files than simple include/exclude based on the names.
-
For example, our previous examples assumed that unit tests would be identified by file name
Test*.java
.Here we look instead for any Java file containing the JUnit4 @Test annotation.:
<fileset id="unitTestSrc" dir="src"> <include name="**/Test*.java"/> <contains text="@Test" casesensitive="no"/> </fileset>
-
Other selectors replicate several of the tests from the classic Unix find command
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
- For reason unclear to me, you cannot name classpaths and re-use them directly, but must do it this way
<path name="test.compile.classpath"> <pathelement path="${env.CLASSPATH}"/> <fileset dir="target/classes"> <include name="**/*.class"/> </fileset> <filelist refid="third-party_jars"/> </path> ⋮ <classpath refid="test.compile.classpath"/>
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>
-
loadfile
loads an entire file into a property -
The
linecontains
filter limits the portion of the file loaded to any line containing a LaTeX\title{
…}
command. - The
tokenfilter
filter does a regular expression match and replace on that line to extract only the portion of that line between the{ ... }
.
2.3 Tasks
The Ant Manual has a good breakdown on these.
-
Consistent with their XML structure, tasks can be parameterized via attributes or nested XML attributes
- Sometimes you can do the same thing either way.
-
Look at:
- File tasks: copy, delete, mkdir, move, fixcrlf, sync
- Compile tasks: javac, depend
- Archive, documentation, testing tasks
- Execution tasks: java, exec, apply
Extending Ant
-
Ant has a built-in macro capability
-
More powerful extension is accomplished by adding Java classes, mapped onto task names:
<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
-
Many Java-oriented tools (e.g. JFlex) come with an ant task as part of the package.
-
Other are contributed by users of the tool, (e.g. LaTeX)
-
Some general-purpose Ant libraries.
e.g., antcontrib adds
- C/C++ compilation
- If and For-loop
- outofdate (a make-like file dependency wrapper)
- enhanced property tasks (e.g., URL encoding)
3 Case Study
3.1 Simple Java Build
<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>
-
➀ Copy all messages to a log file,
ant.log
-
➁ Set up some useful names for lists of paths.
-
➂ Ignore all “ivy” stuff for now. We’ll cover this in an upcoming lesson.
(OK, if you must know, this retrieves the latest versions of the JUnit and JFlex libraries from the internet and makes them available to this project build.)
-
➃ Compiles Java source code from
src/main/java/
, putting the.class
files intotarget/classes/
. -
➄ Compiles Java source code from
src/test/java/
, putting the.class
files intotarget/testclasses/
.I compile the tests separately from the “real” code so that later we can easily omit the test drivers from the published library’s binary code.
-
➅ Now we run the JUnit tests
haltonfailure
stops the build if we failed tests, so we never move on and produce a jar with known buggy code. -
➆ This produces an HTML report with summaries of how well our tests did.
-
➇ The compiled binaries for the “real” code (not the tests) are packaged into a
.jar
file.-
Note that the
.jar
production is simplified by having separated the project and test compilation results.
-
-
➈ Clean up is simple: delete the
target
directory
You can find this entire project, with the Ant files, here.
Eclipse is generally ant-friendly.
-
Drop a build.xml file into a project and Eclipse will recognize it.
- Right-clicking on it will bring up options to run it, or to configure how to run it
- including the selection of the target
- some preference given to targets with descriptions
- Right-clicking on it will bring up options to run it, or to configure how to run it
-
Once ant has been run, the “Run Last Tool” button defaults to re-running it.
-
But the default build is still Eclipse’s default build manager
- For projects with elaborate classpaths, requires keeping both the Eclipse project description and the build file up-to-date and consistent.
- Pre-compilation steps (e.g., tools that generate source code) are not re-run automatically when needed.
Eclipse Builders
Eclipse supports multiple, plug-able builders.
-
Open Project Properties and go to “Builders”
- In a typical java project, you have just the “Java Builder”
- Click new to see options.
In this case, select “Ant Builder”.
-
Fill in the main screen. Leave “Arguments” blank.
-
Go to the Targets tab. Select appropriate targets for
Clean: Menu selection
Project->clean
Manual build: What you want done after explicitly requesting a build
Auto build: What you want done after a file has been saved/changed
-
Return to the Builders list and uncheck the “Java Builder”
4 Overall
Goals | scripting | make | ant |
---|---|---|---|
easy to use? | Y | Y | Y |
easy to set up for a given project? | N | N | N |
efficient: avoid redundant/unnecessary actions | N | Y | ? |
efficient: detect and abort bad builds in progress | ? | Y | ? |
incremental: allow focused/partial builds | ? | Y | Y |
flexible: allow for a variety of build actions | N | Y | Y |
flexible: builds on/for a variety of platforms | N | N | Y |
configurable: permit the management of multiple artifact configurations | ? | ? | Y |