Build Systems
Most software engineers soon tire of the often repetitive command line
tasks required to build a software system. Build systems such
as Make and Ant automate this process and remove its tedium, while
simultaneously optimizing the process to prevent unnecessary work and
shorten build times. This lab will introduce the basics of
using the Apache Ant build automation tool.
Installing Ant
Some systems (such as Macs) come with Ant already installed.
Check to see if your system already has ant installed by
attempting to run it from the command line:
If ant is installed this command should result in Ant printing
its version information. If not, you'll need to install it.
Many Linux
distributions have Ant available through their packaging system (e.g.:
"sudo apt-get install ant" should install it on Debian-based systems).
Others will need to install it manually using the procedure
outlined in the Ant manual available at this
link.
This installation guide can sometimes be tricky, so I have provided
another guide for a windows installation through cygwin. Linux users can simply use the sudo apt-get install ant command to install Ant.
Ant Buildfiles
Any project wishing to use Ant must supply
an XML-based buildfile (usually named "build.xml")
which specifies what Ant should do.
Buildfiles consist of a top level project
element which contains at least one target subelement
and usually some number of global property subelements. We'll
look at each of these subelements in turn before returing to how
they're put together to form the top-level property element.
Global Properties
Global properties are similar to constants in that they allow you to
associate a value with an identifier. These identifiers can
then be used elsewhere in the buildfile to refer to the value.
This is often used to define important values once that are
then referenced multiple times throughout the buildfile (e.g.: to
define the directory where the source files are located).
This concept is useful in improving buildfile readability by
allowing all the important values in a buildfile to be defined in one
continguous location. It also makes it easier to
modify a buildfile by enabling changes to be applied in a single
location in the buildfile instead of multiple locations throughout.
Properties are defined using a "property" element. For
example, the following defines the property "src.dir" to the value
"src":
<property name="src.dir" value="src"/>
Once defined, a property's value can be recalled by placing its name
between "${" and "}". For example, if you've defined the
"src.dir" property above you can use that property elsewhere with
"${my_property}". Properties can even be used in the
definition of yet other properties, like so:
<property name="mypkg.dir" value="${src.dir}/com.example.mypkg"
Exercise:
Create the XML elements needed to define the following
properties. Use properties to define other properties
wherever possible.
Name |
Value |
src.dir |
src |
build.dir |
build |
dist.dir |
dist |
dist.jar.dir |
dist/lib |
jar.dir |
lib |
Targets
A target is a grouping of tasks that need to be performed to reach a
certain desired state. A buildfile will typically include
targets for such things as initializing the build directory structure,
compiling the source files, and building a Java archive. A
target is defined by a sequence of task subelements, so we'll discuss
these first.
Tasks
Ant's strength lies in its powerful collection of built-in tasks.
The tasks available cover pretty much anything you'll need to
do during a build process, including tasks that work with archives,
handle compilation, interact with version control systems, perform
file/directory operations, etc. We'll cover the absolute
basics for a handful of the most common tasks here, but for a
comprehensive
look at what is available see the list
of tasks in Ant's manual.
Tasks are defined by an element with the tasks name. The
options available for each task is unique for that tasks.
Usually you need only include the bare minimum of information
required to do the task and Ant takes care of the rest. Tasks
also are typically aware of whether or not they need to be run again.
For example, the compilation tasks won't actually compile
unless the class files are out of date. Here are some of the
most commonly used tasks:
mkdir
The mkdir task is used to create a dictionary. You need only
supply the name of the directory you wish to create, for example:
<mkdir dir="${some.dir}">
This will create a new directory named whatever the value of the
"some.dir" property is.
Exercise:
Create the task element necessary to create a directory named "build".
delete
The delete task is used for deleting files and directories.
You supply the name of the directory or file to delete.
Here are a couple of examples:
<delete dir="some_dir">
<delete file="some_file">
The first task will delete the directory "some_dir" and all of its
contents; the second will delete the file "some_file".
Exercise:
Create the task element necessary to delete a directory named "build".
javac
The javac task is arguably the workhorse of the Ant build system;
it compiles a Java source tree. You must provide a
directory containing the source and usually a destination for
the compiled class files is also specified. You may also need
to specify a classpath for compilation if the source to be compiled
relies upon classes defined elsewhere. Lastly, you also will
usually want to set the attribute "includeantruntime" to "false" which
will ensure that only the classpath specified in the antfile is
used and not any system or ant-specific classpath - this makes
it more likely that your buildfile will work on all systems, not just
yours. An example javac task follows:
<javac srcdir="src" destdir="build" classpath="my_lib.jar" includeantruntime="false"/>
This task will compile all the Java files in the "src" directory and
place the compiled class files in the "build" directory whilst
mirroring the internal directory structure of the "src" directory
(e.g.: if there is a java file at "src/com/example/MyClass.java" the
generated class file will be placed in
"build/com/example/MyClass.class". The compilation will use a
classpath of "my_lib.jar", so any classes found in that JAR can be used
by the source without problems, and the classpath will not be polluted
by any other classpath entries. Not bad for a one-liner!
Exercise:
Create the task element necessary to compile all the java source files
in the "my_src" directory, and place the resultant class files in the
"my_classes" directory. The element should also include the
jarfile "my_jarfile.jar" in the compilation task's classpath.
jar
The jar task handles the process of packaging up a set of class files
into a Java archive (JAR) file. You need only specify the
output jarfile's name and the base directory of the class heirarchy to
be packaged up. Here's an example:
<jar jarfile="my_lib.jar" basedir="build">
This packages up all the class files in the "build" directory and
creates a valid Java archive named "my_lib.jar".
Exercise:
Create the task element necessary to create a jarfile named
"my_jar.jar" from all the classfiles found in the "my_classes"
directory.
java
The java task allows you to execute a Java program. You must
specify either a classname or an executable jarfile. If you
specify a jarfile, you must also set the "fork" attribute to true
(which means that a separate Java instance than the one Ant itself is
running in will be created. Here's an example:
<java jar="executable_jar.jar" fork="true"/>
This executes the "executable_jar.jar" jarfile in its own Java virtual
machine.
Exercise:
Create the task element necessary to execute the jarfile named
"my_jar.jar".
We'll return now to the topic of targets. All
buildfiles must contain at least one target. Targets are
created using a target element which consists of some number of tasks
that will be executed in sequential order whenever the target is to be
built. Here's an example target:
<target name="clean">
<delete dir="build_dir">
<delete file="temp_file">
</target>
Now whenever the "clean" target is run, the directory "build_dir" and
the file "temp_file" will be deleted. Notice that the target
has a "name" attribute - every target must have a unique name.
You can also specify dependencies between targets.
This tells ant that before a target's tasks can be run that
the target that it depends upon must have been executed. Ant
keeps track of these dependencies and ensures that they are met during
the build process. For example, say you have the following
snippet in your buildfile:
<target name="jar", depends="compile">
...
</target>
Before performing the tasks contained in the "jar" target (not shown in
snippet above), Ant will make sure to run the "compile" target.
This is very useful, because you can request that Ant build
the "jar" target and it will automatically make sure that it is being
built from up-to-date class files.
Projects
Now that we know the basic components of a buildfile, we can create the
required top-level project element. The project element can
include the following attributes:
- name: this provides a name
for the project
- default: this specifies the
default target to be built when Ant is invoked without a requested
target
- basedir: this specifies a
base directory upon which relative paths specified elsewhere in the
buildfile will be based
Here's an example project element (lower level elements are not included
for simplicity):
<project name="MyProject" basedir="." default="jar">
<property name="src.dir" value="src"/>
<target name="clean">
....
</target>
<target name="compile">
....
</target>
<target name="jar" depends="compile">
....
</target>
<target name="run" depends="jar">
....
</target>
</project>
This defines a project with one global property ("src.dir") and four
targets ("clean", "compile", "jar", and "run"). The default
target in this project is "jar", and you can also see how targets can
be set up to depend upon one another.
Running Ant
Ant is invoked by typing "ant" at the command line. By
default Ant will search for a default target (set via the top-level project element)
if you don't specify a target argument.
Ant also supports requesting that a specific target
be built by invoking ant with the requested target's name.
For example:
This would cause the run target to be built. In the example
project from above, this would cause the following build sequence (due
to dependencies): "compile" -> "jar" -> "run".
Putting it all together
Using the elements introduced above, you'll now use Ant to build yet
another CircleCalc program.
Exercise:
Checkout the Ant-based CircleCalc project from the following subversion
URL:
http://subversion.assembla.com/svn/ee461l-circlecalcantstub/trunk/
This directory contains an incomplete Ant buildfile. Your
task is to fix it. The final buildfile should meet the following
requirements:
- All directory and file names referenced from within tasks shall
use properties for their values
- All properties should be defined in the file before any targets
are defined
- The buildfile shall include the following targets:
- init: creates the build directory used for storing class files
- compile: compiles all the source files in the source directory
and places them in the build directory
- dist: creates a directory for distributing the program and
creates a Java archive (jarfile) that is placed in the distribution
directory
- run: executes the executable-jar created by the dist target
- clean: removes all
files and directories created by the build process
- The default target shall be dist
- All targets shall have the appropriate dependencies
necessary for them to complete successfully
- Invoking "ant run" should successfully execute the CircleCalc
program
Important Notes:
- that the "dist" target found in the incomplete buildfile shows
the additional subelements required for the "jar" task to create an
executable jarfile which executes the "Circle" class
- this version of CircleCalc depends upon a class defined by the
Apache Common math library. The required jar is intentionally not
included in the subversion repository download. You can find the
required jarfile "commons-math-2.2.jar" in the binary archives
available at this
link.
Reference materials:
Developed with guidance from
Miryung Kim
Copyright
(c) 2011
Professor Miryung Kim and a course development TA Evan Grim
Permission is granted to copy, distribute and/or modify this document
under the terms of the GNU Free Documentation License, Version 1.1 or
any later version published by the Free Software Foundation; with no
Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
A copy of the license can be found on the GNU web site here.