ant is a build manager based upon a task dependency graph expressed in an XML file
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
Other Criticisms
Some feel that make is too low-level with its focus on individual files
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
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.
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.
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.
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.
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.
➃ 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.
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