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)
  • 4: Add an antlib.xml

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'

  • Benefits:
  • 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

  • On the Ant User list someone asked for:
  • Hi,
    
    I am beginner in ant.
    
    I am trying to write one program in ant- which will save Pass/fail
    Notification in one text file. Could you help me please?
    
    Thanks in advance!

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