Monday, December 9, 2013

Listening and Logging Ant Output in Groovy

In the comments section of my post Executing Ant Build File Targets from Groovy, CRC recently asked, "I've used your script and it seems to work but I could't see any output at the console (I'm calling a echo task in build.xml), why?" This is a great question and one that I feel is better answered in a post than in a comment.

In the developerWorks article Invoking Apache Ant programmatically, Nell Gawor explains what needs to be done to see the output normally seen on the console when Ant's echo task is accessed in a build executed on the command line: "When Ant is executed from the command line, the output automatically goes to the console. But [when using Project.executeTarget(String)] you need to be explicit. You need to add a BuildLogger and add it as a listener so it will receive notification of events that happen during the build."

The BuildLogger that Gawor mentions in the "Loggers" section of her article is part of Ant's support for listeners and loggers. In her article (and in my post), the DefaultLogger is used because of its simplicity. The DefaultLogger implements BuildLogger and provides the methods setOutputPrintStream(PrintStream) and setErrorPrintStream(PrintStream). It is these two "set" methods that allow one to associate standard output and standard error respectively with the Ant Project instance.

The following code listing shows the adapted Groovy script that will run a provided Ant target and will print the output of the invoked Ant targets (such as those that employ the echo task) to standard output.

#!/usr/bin/env groovy
/**
 * applyBuildFileInGroovy2.groovy
 *
 * This is an example of executing an Ant build file from Groovy code using
 * Ant's Project and ProjectHelper classes. The only difference between this and
 * applyBuildFileInGroovy.groovy is that this version will write to standard
 * output and standard error for invoked Ant targets as appropriate.
 *
 * Usage: applyBuildFileInGroovy.groovy _buildFilePathName_ [target1] [target2] ... [targetn]
 *
 * where _buildFilePathName_ is the path and file name of the build file to be
 * used by this script and zero or more targets in that build file can be
 * specified (default target used if no targets specified).
 */

import org.apache.tools.ant.DefaultLogger 
import org.apache.tools.ant.Project
import org.apache.tools.ant.ProjectHelper

if (args.length < 1)
{
   println "You must provide an Ant build file as the first parameter."
   System.exit(-1)
}

def antBuildFilePathAndName = args[0]
def antFile = new File(antBuildFilePathAndName)
def project = new Project()
def consoleLogger = new DefaultLogger()
consoleLogger.errorPrintStream = System.err
consoleLogger.outputPrintStream = System.out
consoleLogger.messageOutputLevel = Project.MSG_INFO
project.addBuildListener(consoleLogger);
project.init()
ProjectHelper.projectHelper.parse(project, antFile)
if (args.length > 1)
{
   def antTargets = args - antBuildFilePathAndName
   antTargets.each
   {
      project.executeTarget(it)
   }
}
else
{
   // use default target because no targets were specified on the command line
   project.executeTarget(project.defaultTarget);
}

In the above code listing, the lines that were added and are relevant to this post are line 17 (importing DefaultLogger) and lines 31-34 (setting the DefaultLogger's standard output stream, standard error stream, and message output level). The available message output levels are defined in the Project class as constant integers MSG_DEBUG, MSG_ERR, MSG_INFO, MSG_VERBOSE, and MSG_WARN. These are described in those constants' respective Javadoc comments and they are also described in the echo task documentation.

Running the enhanced script now will not only perform logical behavior of invoked Ant targets, but will also direct output of those tasks to standard output and standard error. Thanks to CRC for asking the interesting question and to Nell Gawor for the articulate explanation.

2 comments:

CRC said...

Thank you very much for your fast reply! You saved my day. I'm new to Groovy but I'm very impressed of its capabilities as a scripting tool!
What I'm try to reach is to make ant build more flexible in order to control my deployment process.
Another question: what about ant properties files? Can I reuse them when invoke ant task from Groovy?

CRC said...

I found the answer by myself: yes it's possible and very simple.

Here I list all properties available in ant:

def proprieta = project.getProperties();

for ( e in proprieta ) {
println e.key + " = " + e.value
}