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
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
The commands that people write into their makefile rules are generally not portable either:
/
in *nix versus \
in Windows, legal characters, quoting rules)
:
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?
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:
-file
or -buildfile
The ant build file is an XML file.
<project name="382Website" default="deploy">
<description>
Extract Metadata Extractor - top level
</description>
⋮
</project>
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,
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.
: 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
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
Each of the tasks is executed, but
The javac
task knows not to re-compile Java files with up-to-date class files
The jar
task knows not to update Jar files that are newer than all of the files being added.
If we remove the -Dskip.test=1
, however, the tests will be re-run.
So some level of incremental behavior gets built into many of the individual tasks.
make
.ant
. It depends on the implementation of each individual task.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 ‘.’
compile.src
, compile.dest
, compile.options
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.
Additional <property
Variants
<property file="project.default.properties"/>
Loads property values from a file, written as a series of property=
_value_ lines
courseName=CS795
baseurl=https://secweb.cs.odu.edu/~zeil/cs795SD/s13
homeurl=https://secweb.cs.odu.edu/~zeil/cs795SD/s13/Directory/topics.html
email=zeil@cs.odu.edu
<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.
600
)If the file does not exist, this does nothing at all.
<property environment="env"/>
Copies the OS environment variables into the build state, prefaced by the indicated prefix
${env.PATH}
A file set is a collection of existing files
A file list is a collection of files that may or may not exist
<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"/>
<filelist dir="src"
files="main.cpp utilities.h utilities.cpp"/>
id
or refid
attributesMappers
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
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
<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"/>
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.
tokenfilter
filter does a regular expression match and replace on that line to extract only the portion of that line between the { ... }
.The Ant Manual has a good breakdown on these.
Consistent with their XML structure, tasks can be parameterized via attributes or nested XML attributes
Look at:
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
<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 into target/classes/
.
➄ Compiles Java source code from src/test/java/
, putting the .class
files into target/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. Example
➇ 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.
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.
<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>
➀ The taskdef
“declares” a new jflex
task.
➁ Here we use jflex
to generate a scanner from several sets of regular expressions.
The source code for the new scanner is placed in target/gen/java
, keeping it separate from our hand-written code.
➂ Compilation can only occur if generateSource
was run.
➃ Here I have modified my original compilation instructions to also compile any Java source code in the new src/generated/java
directory.
You can find this entire project, with the Ant files, here.
Because this project is divided into multiple subprojects, we will have multiple build.xml
files:
In the project root directory:
<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>
<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>
➀ AntContrib
provides a set of C++ tasks.
➁ Compile the source code. The outtype
indicates that we are generating a (static) library.
➂ Compile the unit tests. The outtype
indicates that we are generating an executable.
➃ Unfortunately, I cannot get linking to work properly with the cc
task, so I forced that as an OS command.
➄ Run the unit test executable.
The other subproject build.xml
files are similar, but simpler.
You can find this entire project, with the Ant files, here.
Limitations of Eclipse Builder
Cannot run code-proprocessing (e.g., JFlex)
An Eclipse project is oriented towards producing a single output product (program, library, … )
With C++ projects, a problem if you have a “real” product
(e.g., a library) and a set of test drivers, each of which yields a distinct program executable.
Project Dependencies
Eclipse supports the idea of projects that depend on other projects, so you could do
project1 produces the binary distribution jar
project2 depends on project1 and produces a source distribution jar
Does not scale well.
Eclipse/Ant Integration
Eclipse is generally ant-friendly.
Drop a build.xml file into a project and Eclipse will recognize 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
Eclipse Builders
Eclipse supports multiple, plug-able builders.
Open Project Properties and go to “Builders”
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”