Advanced Ant
Getting the most from your build tool
Kev Jackson
Developer, Freelance.com
Part-time Lecturer, RMIT (Vietnam)
Contents
- Who am I?
- Extending Ant - Custom tasks & Antlibs
- Extending Ant - Custom build listeners
- Scripting Ant - <scriptdef/>
Contents, no extra info.
Who am I?
Kev Jackson
Ant Committer & PMC member
Extending Ant - Custom Tasks
- Great!
- ...Complete control over what we do
- ...in Java so no problems* with different OS
- ...problem solved! Or, not quite...
- Classpath problems: example JUnit Task (how long has this been around?)
- ...http://ant.apache.org/faq.html#delegating-classloader-1.6
- ...and people still have problems getting it to work
- Simple fix...place all third-party jars in $ANT_HOME/lib
Extending Ant - Antlibs
- Advantages over a custom task
- ...available automatically (if placed in $ANT_HOME/lib)
- ...in Java so no problems* with different OS
- ...can bundle a lot of functionality together in one 'library'
- ...separate from Ant core (unlike optional taskdefs)
- ...can be updated separately from Ant core (no longer have to wait for new Ant to take advantage of new features in an antlib)
Antlibs - What?
- Just a custom task
- ...placed in $ANT_HOME/lib
- or in user.home/.ant/lib (for centrally managed installation)
- or you can specify them manually
- ...with an antlib.xml file
- ...declared as part of an xml namespace in your build.xml file
Antlibs - Making a tla (GNU Arch) antlib
- Remember: just a 'normal' custom task
- 1: Install GNU Arch (tla)...
Antlibs - Making a tla (GNU Arch) antlib
Antlibs - Making a tla (GNU Arch) antlib
- 1: Install GNU Arch (tla)...
- 2: Write a command line wrapper similar to AbstractCVSTask...
Antlibs - Making a tla (GNU Arch) antlib
Environment env = new Environment();
Execute exe =
new Execute(getExecuteStreamHandler(), null);
exe.setAntRun(getProject());
exe.setCommandline(toExecute.getCommandline());
exe.setEnvironment(env.getVariables());
try {
String actualCommandLine =
executeToString(exe);
log( actualCommandLine,
Project.MSG_VERBOSE
);
int retCode = exe.execute();
...
|
- See http://people.apache.org/~kevj/ant-tla/ant-tla.html for the full code
Antlibs - Making a tla (GNU Arch) antlib
- 1: Install GNU Arch (tla)...
- 2: Write a command line wrapper similar to AbstractCVSTask...
- 3: Add custom classes for individual actions (get/update etc)
Antlibs - Making a tla (GNU Arch) antlib
Antlibs - Making a tla (GNU Arch) antlib
Antlibs - Making a tla (GNU Arch) antlib
- 1: Install GNU Arch (tla)...
- 2: Write a command line wrapper similar to AbstractCVSTask...
- 3: Add custom classes for individual actions (get/update etc)
Antlibs - Making a tla (GNU Arch) antlib
<?xml version="1.0" encoding="utf-8"?>
<antlib>
<taskdef name="tla"
classname="org.apache.ant.tla.Tla"
/>
<taskdef name="registerarchive"
classname="org.apache.ant.tla.RegisterArchive"
/>
<taskdef name="get"
classname="org.apache.ant.tla.Get"
/>
<taskdef name="update"
classname="org.apache.ant.tla.Update"
/>
</antlib>
Antlibs - Making a tla (GNU Arch) antlib
- 1: Install GNU Arch (tla)...
- 2: Write a command line wrapper similar to AbstractCVSTask...
- 3: Add custom classes for individual actions (get/update etc)
- 4: Add an antlib.xml
- 5: Write a simple build file using the antlib...
Antlibs - Making a tla (GNU Arch) antlib
<?xml version="1.0" encoding="utf-8"?>
<project name="tla-test" basedir="../../../" default="get"
xmlns:tla="antlib:org.apache.ant.tla">
<property name="repo-dir" value="tla-test"/>
<target name="get">
<tla:registerarchive
repoURL="http://www.atai.org/archarchives/atai@atai.org--public/"
/>
<tla:get archive="atai@atai.org--public"
revision="tla--atai-dists--1.3.4"
dest="${repo-dir}" />
</target>
<target name="cleanup">
<delete dir="${repo-dir}"/>
</target>
</project>
Antlibs - Making a tla (GNU Arch) antlib
- 1: Install GNU Arch (tla)...
- 2: Write a command line wrapper similar to AbstractCVSTask...
- 3: Add custom classes for individual actions (get/update etc)
- 4: Add an antlib.xml
- 5: Write a simple build file using the antlib...
- 6: Test the code with the build file
Antlibs - Making a tla (GNU Arch) antlib
Antlibs - Making a tla (GNU Arch) antlib
- 1: Install GNU Arch (tla)...
- 2: Write a command line wrapper similar to AbstractCVSTask...
- 3: Add custom classes for individual actions (get/update etc)
- 4: Add an antlib.xml
- 5: Write a simple build file using the antlib...
- 6: Test the code with the build file
- 7: Write a repeatable test
Antlibs - Making a tla (GNU Arch) antlib
Antlibs - Making a tla (GNU Arch) antlib
Antlibs - Things to notice
- Not specified like a normal custom task
- Use xml namespaces
- Trivial to change a normal build file into an 'antunit' test
- What's this tla:register-archive?
- What about backwards compatibility with Ant 1.5 etc?
Antlibs - Build.xml -> Antunit test
- Add the antunit namespace to your build file
- Specify the driving target as the default in the build file
- In the driving target read in the build file (yes the same file), and set up the <au:plainlistener/>
- Write a target named 'testX'
- Inside 'testX', perform actions as normal
- After the actions have been performed use the appropriate <au:assertX/> to test the result
- For setup, add a <target name="setUp">
- For teardown, add a <target name="tearDown">
Antlibs - Antunit
- Antunit is an antlib
- Just like JUnit (setUp -> test1..testN -> tearDown)
- Dependencies are *still* called
- Antunit reads a buildfile (x.xml) to drive the tests
- Antunit provides basic assertions, but more can be added
- (My) preferred method of testing Ant task code (plain JUnit or even BuildFileTests are now obselete)
- Antunit 1.0, released this January
- See http://ant.apache.org/antlibs/antunit/ for more information
Antlibs - tla:register-archive?
- Sorry, a required task so that we can 'get'
- Wanted to concentrate on one function for the examples
- In the code for this antlib, there are other features which I didn't reveal today...
- You can try the code out from http://people.apache.org/~kevj/ant-tla/ant-tla.html
Antlibs - A compatibility 'trick'
- What if you want to distribute a custom task that can run as both an antlib or as a normal custom task?
- DRY - Don't repeat yourself
- Create an antlib, and in the base directory write a properties file
- In the antlib.xml file, use <taskdef resource="tasks.properties"/>
- Example - Ant-contrib
Antlibs - A compatibility 'trick'
- Define tasks that are compatible with <Ant 1.6 in a properties file
Antlibs - A compatibility 'trick'
- Define tasks which require >Ant 1.6 in the antlib.xml
Antlibs - A compatibility 'trick'
- 1: DRY - Don't repeat yourself
- 2: Easier maintenance as edits can be made to the properties file and they are instantly available in the antlib
- 3: Good backwards compatibility for normal taskdefs, antlib tasks available for ant 1.6+
Antlibs - Inside antlib.xml
- Allowed inside an antlib.xml file:
- <taskdef>
- <typedef>
- <presetdef>
- <macrodef>
- <scriptdef>
- anything that implements the AntLibDefinition interface
- For Example, antunit:
Antlibs - Inside antlib.xml
Extending Ant - Custom Build Listener
- Ant can also be extended via a BuildListener implementation
- A listener is alerted to events like:
- buildStarted / buildFinished
- targetStarted / targetFinished
- taskStarted / taskFinished
- subBuildStarted / subBuildFinished
- and messageLogged
Extending Ant - Custom Build Listener
Extending Ant - Custom Build Listener
- This got me thinking; why would you need that?
- On BUILD SUCCESSFUL perform some action?
- Conversly do something on BUILD FAILURE?
Extending Ant - Custom Build Listener
- Serves as an example for us now :)
- Let's code up a custom BuildListener
- that performs a task 'after' the build has completed
- First the build.xml I'd like to be able to write
Extending Ant - Custom Build Listener
<project name="test-exec-listener" basedir="." default="test">
<taskdef name="exec-listener"
classname="org.apache.tools.ant.listener.ExecListener"/>
<exec-listener onSuccess="true">
<echo>Executing after build...</echo>
<exec executable="cmd">
<arg value="dir"/>
</exec>
</exec-listener>
<target name="test">
<sleep seconds="3"/>
</target>
</project>
Extending Ant - Custom Build Listener
- Need to register the listener as a taskdef
- ...this saves us from the -listener=... at the command line
- The listener takes any number of nested tasks (2 in this case)
- The listener should be declared at the project level, not in a target
- Now the code...
Extending Ant - Custom Build Listener
public class ExecListener extends Task
implements BuildListener, TaskContainer {
private Vector nestedTasks = new Vector();
/* default to run on BUILD SUCCESSFUL */
private boolean onSuccess = true;
public void buildFinished(BuildEvent event) {
boolean execNested = onSuccess ? (event.getException() == null)
: (event.getException() != null);
if(execNested) {
for (Iterator i = nestedTasks.iterator(); i.hasNext();) {
Task nestedTask = (Task) i.next();
nestedTask.perform();
}
}
}
Extending Ant - Custom Build Listener
/*
* Add this listener to the project,
* removes the cli fiddly -listener option
*/
public void execute() {
getProject().addBuildListener(this);
}
public void addTask(Task task) {
nestedTasks.addElement(task);
}
public void setOnSuccess(boolean onSuccess) {
this.onSuccess = onSuccess;
}
}
Extending Ant - Custom Build Listener
- We take advantage of the fact that top level tasks are executed first
- ... we use this to register our build listener
- ... use the BuildListener interface to wait for BuildFinished
- ... check the build doesn't contain any errors
- ... perform the nested tasks
Extending Ant - Dynamic Languages
- Finally, we can extend ant with scripts
- ... can go in CDATA section or in own src file
- ... CDATA and python probably not a good choice!
- Java6 and javax.scripting == better js/Rhino support
- <script> too limited, use <scriptdef>
- Define a new 'task' with <scriptdef> use later
Extending Ant - <scriptdef>
<scriptdef name="scripttest" language="javascript">
<![CDATA[
project.log("Hello from script")
]]>
</scriptdef>
<target name="test">
<scripttest/>
</target>
Extending Ant - <scriptdef>
<scriptdef name="rubytest"
language="ruby">
<![CDATA[
require 'fileutils'
File.open('/copytest.txt', "r").each do |line|
arr=line.split(' ')
FileUtils.mkdir_p(File.dirname(arr[1]),:verbose=>true)
FileUtils.cp(arr[0],arr[1],:verbose=>true)
end
]]>
</scriptdef>
- Example taken from Ant Users list
- Code by Gilbert Rebhan
Ivy
- Ant builds code, but has no way of handling dependencies a la maven
- Let's look at Ant's new sub-project Ivy
- (Congrats to the Ivy team for passing this milestone!)
ivy.xml - an example
<ivy-module version="2.0">
<info organisation="org.apache" module="hello"/>
<configurations>
<conf name="compile" visibility="private"/>
<conf name="test" extends="compile"/>
<conf name="master"/>
<conf name="runtime" extends="compile"/>
<conf name="default" extends="master,runtime"/>
</configurations>
<publications>
<artifact conf="master"/>
</publications>
<dependencies>
<dependency org="commons-logging" name="commons-logging" rev="1.1"
conf="compile->default;runtime->default"/>
<dependency org="junit" name="junit" rev="3.8.2" conf="test->default"/>
</dependencies>
</ivy-module>
ivy.xml - deconstruction
<info organisation="org.apache" module="hello"/>
- Specifying the name of your module
ivy.xml - deconstruction
<configurations>
<conf name="compile" visibility="private"/>
<conf name="test" extends="compile"/>
<conf name="master"/>
<conf name="runtime" extends="compile"/>
<conf name="default" extends="master,runtime"/>
</configurations>
- Configuration test inherits dependencies of compile
- Configuration default inherits dependencies of both master and runtime
ivy.xml - deconstruction
<publications>
<artifact conf="master"/>
</publications>
- Master configuration has no dependencies
- Contains nothing but the module's own artifact
ivy.xml - deconstruction
<dependencies>
<dependency org="commons-logging" name="commons-logging" rev="1.1"
conf="compile->default;runtime->default"/>
<dependency org="junit" name="junit" rev="3.8.2" conf="test->default"/>
</dependencies>
- compile & runtime require commons-logging
- test extends default (so it gest commons-logging)
- test also requires junit
Mapping Maven2 to Ivy - dependencies
<dependencies>
<dependency>
<groupId>org.apache.ant</groupId>
<artifactId>ant</artifactId>
<version>1.7.1</version>
</dependency>
</dependencies>
Mapping Maven2 to Ivy - dependencies
<dependencies>
<dependency org="org.apache.ant" name="ant" rev="1.7.1"/>
</dependencies>
- Take home point - Ivy is less verbose :-)
Using Ivy in Ant
<project xmlns:ivy="antlib:org.apache.ivy.ant"
name="hello-build">
- Ivy is an antlib, set the namespace to auto-include the taskdefs
Using Ivy in Ant
<target name="resolve" description="retreive dependencies with ivy">
<ivy:retrieve/>
</target>
- Downloads the dependencies from a repository (default is ibiblio)
- Uses sensible defaults (and puts the dependencies into a ${basedir}/lib)
- Possible to specify much more complex conditions, but not required for all cases
Using Ivy in Ant
<target name="compile" description="standard compile">
<javac srcdir="${src.dir}"
destdir="${build.dir}"
classpathref="lib.path.id"/>
</target>
- Standard compile because:
- ... ivy has resolved the dependencies,
- ... downloaded (and cached) them,
- ... copied them into your ${basedir}/lib