Use Jython to Write Ant Tasks

April 2004
MoTuWeThFrSaSu
    1 2 3 4
5 6 7 8 91011
12131415161718
19202122232425
2627282930  
Mar
2004
 May
2004

A short article describing the steps to use Jython and Groovy to write custom Ant tasks.

XML-Image Syndication

XML-Image Comment Feed

Letterimage Contact me

Author: Ed Taekema
Contact: etaekema@earthlink.net
Date: 2004/04/14

Abstract

This article looks at how to add scripted behaviour to ant builds. It details the steps to write a custom Ant task in jython, compile it and install it into ant so it can be used as any other task in an ant build. The article also takes a quick look at an alternate implementation using Groovy.


Ant is the current tool of choice for java builds. This is so partially because it was the first java oriented build tool on the scene and because the reigning champion Make was getting long in the tooth and had fallen out of favour with the java crowd. But Java builds are getting more and more difficult and these days there is general dissatisfaction with ant1. Note particularly Bruce Eckel's Comments and Martin Fowler's further comments. The comments to Bruce Eckels's posting show similar fustrations. Fowler summarizes the issues like this:

... Simple builds are easy to express as a series of tasks and dependencies. For such builds the facilities of ant/make work well. But more complex builds require conditional logic, and that requires more general programming language constructs - and that's where ant/make fall down.

Ken Arnold's article The Sum of Ant led me to Jonathon Simon's article Scripting with Jython Instead of XML and got me thinking about extending ant with Jython. Simon's article presents a technique to drive Ant tasks, testing, etc all from Jython. What I am presenting is a technique to embed Jython scripts into Ant which is admittedly backwards from Simon's approach, but hopefully adds power and flexibility to ant builds.

My experience working with large builds automated through ant is not dissimilar to what Fowler is referring to. Eventually, builds need to do either a lot of odd conditional logic in the xml file and ends up burying the logic in scripts, or in a large number of custom tasks written in java. This is particularly the case if your builds include non-java source that ant just isn't smart about building. In one case in particular, the set of custom tasks for the build is really its own system with maintenance and staff costs that are quite substantial. A large number of scripts can quickly become a problem for enterprise build systems as they are difficult to standardize and cross platform issues are always looming.

Fortunately, all is not lost. Ant continues to evolve and version 1.6 is a significant step forward for large build systems. Mike Spille, in his article ANT's Finally a Real Build Tool, demonstrates that the new <import> tag now allows build managers to write truly modular and standardized build systems based on Ant! As Ant grows up, more and more of these issues will get resolved.

One of the strengths that Make always had was the ability to easily call scripts and command utilities. This is something that is definitely possible with Ant script/exec tasks, but it feels very un-java. What we need is an elegant way to add adhoc behaviour to Ant builds ... in a java-ish way.

Writing Custom Ant Tasks

What I think can do the job is to take a more considered approach to using a scripting tool inside an ant build. Rather than just create a mishmash of scripts that are called from exec or script tasks, I suggest that we write custom ant build tasks in a high level scripting language – in this case, Jython.

Writing custom ant tasks allows a build manager to leverage the huge number of already written tasks in their builds while writing what naturally belongs in a more flexible tool in custom ant tasks that can themselves then be reused, are as cross platform as java itself, and wholly integrated into Ant. Because Ant uses java introspection to determine the capabilities of custom tasks, Jython is the perfect tool to accomplish this. All we need to do is ensure that the methods that Ant expects are present in the Jython classes and Ant won't notice the difference.

What we will implement is the perennial SimpleTask which is nothing more than a hello world for ant. It should be sufficient to demonstrate the key steps.

Setup Development Environment

To compile the jython source in this article you will need to add the ant.jar file to your classpath. This will make it available to Jython to extend which we'll do below. To do that define your classpath:

<DOS>
set CLASSPATH=c:\path\to\ant\lib\ant.jar

<UNIX>
export CLASSPATH=/path/to/ant/lib/ant.jar

SimpleTask Jython Class

The following is a very simple Ant task written in Jython(python). Save this as SimpleTask.py

from org.apache.tools.ant import Task

class SimpleTask(Task): 

  message = ""

  def execute(self):
     """@sig public void execute()"""
     Task.log(self, "Message: " + self.message)

  def setMessage(this, aMessage):
     """@sig public void setMessage(java.lang.String str)"""
     this.message = aMessage

This simple Jython class extends the ant Task superclass. For each of the properties we want to support for this task, we write a setXXXXX method where XXXXX corresponds to the property we are going to set in the ant build file. Ant creates an object from the class, calls the setXXXXX methods to setup the properties and then calls the execute method (actually, it calls the perform method on the Task superclass which calls the execute() method). So lets try it out.

Compiling Jython Code To A Jar

To build this into a jar file for use in Ant, do the following:

jythonc -a -c -d -j myTasks.jar SimpleTask.py

This will produce a jar file myTasks.jar and include the jython core support classes in the jar. Copy this jar file into your ant installation's lib directory. In my case I copy it to c:\tools\ant\lib.

Build.XML file to use the Task

Once you've got that working, here is a very simple test ant build file to test your custom jython task.

<project name="ant jython demo" default="testit" basedir=".">

  <!-- Define the tasks we are building -->
  <taskdef name="Simple" classname="SimpleTask" />

  <!-- Test Case starts here -->
  <target name="testit"> 
     <Simple message="Hello World!" />
  </target>

</project>

A Task Container Task

All right, that is a pretty simple task. What else can we do? Well, the sky is the limit really. Here is an example of a task container. In this case, the task holds references to a set of other tasks (SimpleTask tasks in this case):

from org.apache.tools.ant import Task
from org.apache.tools.ant import TaskContainer

class SimpleContainer(TaskContainer): 

  subtasks = []

  def execute(this):
     """@sig public void execute()"""
     
     for task in this.subtasks:
         task.perform()        
     
  def createSimpleTask(self):
     """@sig public java.lang.Object createSimpleTask()"""   

     task = SimpleTask()
     self.subtasks.append(task)
     return task

class SimpleTask(Task): 

  message = ""

  def execute(self):
     """@sig public void execute()"""
     Task.log(self, "Message: " + self.message)

  def setMessage(this, aMessage):
     """@sig public void setMessage(java.lang.String str)"""
     this.message = aMessage

The SimpleContainer extends the TaskContainer java class. Its createSimpleTask method creates a SimpleTask object and returns it to Ant so its properties can be set. Then when all the tasks have been added to the container and their properties set, the execute method on the SimpleContainer class is called which in turn calls the perform method on each of the contained tasks. Note that the perform method is inherited from the Task superclass and it in turn calls the the execute method which we have overriden.

Build.XML file to use the TaskContainer

Here is a ant build file to test your custom jython task container. Note that you don't need to include a task definition for the contained SimpleTask unless you want to use it directly. The createSimpleTask factory method does it for you.

<project name="ant jython demo" default="testit" basedir=".">

  <!-- Define the tasks we are building -->
  <taskdef name="Container" classname="SimpleContainer" />

  <!-- Test Case starts here -->
  <target name="testit"> 

     <Container> 

         <SimpleTask message="hello" />
         <SimpleTask message="there" />

     </Container>

  </target>

</project>    

Things To Look Out For

As I learned this technique I discovered that the magic doc strings are really necessary to force Jython to put the right methods in the generated java classes. For example:

"""@sig public void execute()"""

This is primarily due to Ant's introspection that looks for those specific methods and signatures. These docstrings are required or Ant won't recognize the classes as Ant tasks.

I also learned that for Jython to extend a java class, it must specifically import the java classes using this syntax:

from org.apache.tools.ant import Task
from org.apache.tools.ant import TaskContainer

class MyTask(Task):
   ...

You can not use this syntax:

import org.apache.tools.ant.Task
import org.apache.tools.ant.TaskContainer

 class MyTask(org.apache.tools.ant.Task):
    ...

This is because, for some reason, Jython doesn't figure out that MyTask is extending this java class and so doesn't generate the right Java wrapper classes. You will know that this working right when you see output like the following when you run the jythonc compiler:

processing SimpleTask

Required packages:
  org.apache.tools.ant

Creating adapters:

Creating .java files:
  SimpleTask module
    SimpleTask extends org.apache.tools.ant.Task <<<

What About Groovy

Groovy is a scripting language specifically targeting java developers. It is gathering steam in the java world now that it has been accepted as JSR 241 in Sun's community java process although that seems to be a little controversial. It also can be compiled to java byte code like Jython.

Here is an example of the SimpleTask done above in Groovy:

import org.apache.tools.ant.Task;
import java.lang.String

public class GroovySimpleTask extends Task {

  private String myMessage = ""

  public void execute() {
      this.log(myMessage)
  }

  public void setMessage(String aMessage) {
     myMessage = aMessage
  }

}

Its pretty easy to get this into ant. Here are the steps I followed:

groovyc GroovySimpleTask.groovy
jar cvf Groovy.jar GroovySimpleTask.class
copy Groovy.jar c:\tools\ant\lib

I also had to copy over to ant's lib directory, the groovy support jar files.

copy %GROOVY_HOME%\lib\groovy-1.0-beta-4.jar c:\tools\ant\lib
copy %GROOVY_HOME%\lib\*asm*.jar c:\tools\ant\lib

The advantage of Groovy is that you don't have to have the magic doc strings to force the method signatures to be what Ant expects. It took me a little while to figure out that you call the super class methods using the magic this object though. That is fairly indicative of what my experience with Groovy has been so far ... its pretty neat and contains lots of the same very high level language features that python perl and ruby do and then some, but the language is still changing and the documentation is poor. If the example is any indicator, there isn't much difference in the size of groovy vs jython, although after working with Jython for a while, I found it tiresome to have to constantly worry about the braces for marking blocks of code again. Groovy is certainly a language to keep an eye on though.

For now, Jython is mature, has a tons of third party modules, and is a documented way of doing this and is my first choice for writing custom ant tasks.

Summary

So there you have it. Here is a quick summary then of why this is a helpful technique.

First, it is a lot faster to write ant tasks that integrate with third party tools and systems using a glue language and python/jython is excellent at that. That is really my prime motivation for trying out this technique.

Secondly, Jython has the advantage over other scripting languages (which could be run using Ant's exec or script tasks) because it can be tightly integrated with Ant (i.e. use the same logging methods, same settings, etc). This makes it easier to build a standardized build environment.

Finally, and related to the last point, Jython can be compiled to java byte code which runs like any java class file. This means you don't have to have jython installed to use the custom tasks and your custom task, if written well, can run on a wide variety of platforms.

I think this is a reasonable way to add flexibility and additional integration points to Ant builds.

Footnotes

[1]See in particular Bill de hÓra's list, which is very comprehensive. One of the most cogent criticisms is Ken Arnold's Ant is nothing more than the sum of its parts.

last change 2004-04-23 00:16:00

Creative Commons License
This work is licensed under a Creative Commons License.