Monthly Archives: April 2008

Using Commons Logging from Ant

Here is a common problem with custom Ant tasks. A typical task is implemented using multiple classes, so classes that don’t extend org.apache.tools.ant.Task class don’t necessarily have to have a dependency on Ant APIs. For example, it is pretty easy to pass Ant properties in a hashtable instead of passing the entire project object. This makes the custom task’s code more generic and reusable.

One little issue that still remains is logging. Ant users are accustomed to running Ant with -verbose option which tells Ant tasks to print more detailed information. Oftentimes, -verbose is the only way to debug a build.

Unfortunately, using Ant logging requires access to Project or Task objects. As a result, the dependency on Ant APIs permeates the code that otherwise could have remained generic.

My solution for this is to use a simple class that implements org.apache.commons.logging.Log interface so that we can use jakarta commons logging (JCL) APIs instead of using Ant logging directly.
The class is called AntCommonsLogger.

To initialize AntCommonsLogger, users can either invoke “antCommonsLoggerInit” task in the beginning of a project or call AntCommonsLogger.init( getProject()) somewhere in their custom task class. After that, AntCommonsLogger becomes the default logger so that any class can use the the familiar commons logging pattern without any changes:


private static Log logger = LogFactory.getLog(CustomTask.class.getName());
...
logger.info("message");
logger.debug("message");
logger.trace("message");


“info” messages (i.e. calls to logger.info()) display during normal Ant execution, “debug” messages display if “-verbose” was specified and “trace” messages display if “-debug” was specified. This is a bit counterintuitive but this is the best we could do given that Ant’s “verbose” does not have a direct counterpart in JCL.

To begin using AntCommonsLogger, download myarch-antutil and add antCommonsLoggerInit task definition to your project, e.g.:


    <taskdef resource="com/myarch/antutil/tasks.properties">
        <classpath>
            <pathelement path="${basedir}/myarch-antutil.jar"/>
            <!-- lib.dir must contain commons-logging -->
            <fileset dir="${lib.dir}" />
        </classpath>
    </taskdef>

You also need to add myarch-antutil.jar and commons-logging.jar to the classpath of your custom tasks.

Note that calling antCommonsLoggerInit makes AntCommonsLogger the default logger for this JVM instance. This means that all Java classes invoked by this Ant script (e.g., using “java” task) that use JCL, will use AntCommonsLogger instead of java.util logging or log4j. If this is not what you want, call AntCommonsLogger.init(getProject()) at the begging of your custom task and AntCommonsLogger.restorePreviousDefault() at the end.

Download myarch-antutil

Jython Ant Wrapper Examples

Somebody asked me about examples for PAnt Jython wrapper. Here are some. I’ll be updating this page with more examples in the future.

Following is a simple echo task. Note that pant.py has to be on your “python.path”. You can set it by adding it
to your ANT_OPTS environment variable (ANT_OPTS=-Dpython.path=python_path).



        <script language="jython">
from pant import *
pant=PAnt(project)
pant.execTask(name="echo", message="foo")
        </script>

Following is a copy task example. Note the use of “nested” function to denore nested elements. “expandproperties” is assigned
an empty dictionary since it does not have any attributes.



pant = PAnt( project )
pant.copy(todir="${test.prop}", fileset=nested(dir=".", include=nested(name="*.xml")), 
          filterchain=nested(expandproperties={}) ) 

Following is an example of Exec task. Multiple “env” elements
are distinguished by adding the suffix “_number“. The suffix can in fact be anything, PAnt,
simply ignores the substring starting with underbar.
This example also demonstrates how you can mix and match python variables and Ant properties
in the same piece of code.


shellFile="myshell.sh"
commandLine="options"
pant.exec(dir="${bin.dir}", executable=shellFile,  failonerror="true", resultproperty="result.code",
          env_1=nested(key="key1", value='${val1}'),
          env_2=nested(key="key2", value='${val2}'),
          env_3=nested(key="key3", value='${val3}'),
          arg=nested(line=commandLine) )


Update:

MyArch Jython Task provides tighter integration of Ant and Jython. You may want to use it together with PAnt.

Please refer to our official PAnt project page for more information and to download PAnt