Showing posts with label Ant. Show all posts
Showing posts with label Ant. Show all posts

Friday, March 20, 2015

Displaying Paths in Ant

In the blog posts Java and Ant Properties Refresher and Ant <echoproperties /> Task, I wrote about how being able to see how properties are seen by an Ant build can be helpful in understanding that build better. It is often the case that it'd also be valuable to see various paths used in the build as the build sees them, especially if the paths are composed of other paths and pieces from other build files. Fortunately, as described in the StackOverflow thread Ant: how to echo class path variable to a file, this is easily done with Ant's PathConvert task.

The following XML snippet is a very simple Ant build file that demonstrates use of <pathconvert> to display an Ant path's contents via the normal mechanisms used to display Ant properties.

build-show-paths.xml: Ant build.xml Using pathconvert

<project name="ShowPaths" default="showPaths" basedir=".">

   <path id="classpath">
      <pathelement path="C:\groovy-2.4.0\lib"/>
      <pathelement location="C:\lib\tika-1.7\tika-app-1.7.jar"/>
   </path>
   
   <target name="showPaths">
      <pathconvert property="classpath.path" refid="classpath" />
      <echo message="classpath = ${classpath.path}" />
   </target>

</project>

The simple Ant build file example shown above creates an Ant path named "classpath". It then uses the pathconvert task to create a new property ("classpath.path") that holds the value held in the "classpath" path. With this done, the property "classpath.path" can have its value displayed using Ant's echo task as demonstrated in "Java and Ant Properties Refresher."

When debugging issues with Ant builds, use of Ant's -verbose is often handy. However, sometimes -verbose is a heavier solution than is actually required and often the simple ability to easily identify what properties and paths the Ant build "sees" can be very helpful in diagnosing build issues.

Tuesday, April 1, 2014

Compiling and Running Java Without an IDE

A recent Java subreddit thread called "Compiling Java Packages without IDE" posed the question, "is [there] a command that compiles a group of java files that are inside a package into a separate folder (let's just call it bin), and how would I go about running the new class files?" The post's author, kylolink, explains that "When I started out using Java I relied on Eclipse to do all the compiling for me and just worried about writing code." I have seen this issue many times and, in fact, it's what prompted my (now 4 years old) blog post GPS Systems and IDEs: Helpful or Harmful? I love the powerful modern Java IDEs and they make my life easier on a daily basis, but there are advantages to knowing how to build and run simple Java examples without them. This post focuses on how to do just that.

In my blog post Learning Java via Simple Tests, I wrote about how I sometimes like to use a simple text editor and command-line tools to write, build, and run simple applications. I have a pretty good idea now how my much "overhead" my favorite Java IDEs require and make an early decision whether the benefits achieved from using the IDE are sufficient to warrant the "overhead." In most real applications, there's no question the IDE "overhead" is well worth it. However, for the simplest of example applications, this is not always the case. The rest of this post shows how to build and run Java code without an IDE for these situations.

The Java Code to be Built and Executed

To make this post's discussion more concrete, I will use some very simple Java classes that are related to each other via composition or inheritance and are in the same named package (not in the unnamed package) called dustin.examples. Two of the classes do not have main functions and the third class, Main.java does have a main function to allow demonstration of running the class without an IDE. The code listings for the three classes are shown next.

Parent.java
package dustin.examples;

public class Parent
{
   @Override
   public String toString()
   {
      return "I'm the Parent.";
   }
}
Child.java
package dustin.examples;

public class Child extends Parent
{
   @Override
   public String toString()
   {
      return "I'm the Child.";
   }
}
Main.java
package dustin.examples;

import static java.lang.System.out;

public class Main
{
   private final Parent parent = new Parent();
   private final Child child = new Child();

   public static void main(final String[] arguments)
   {
      final Main instance = new Main();
      out.println(instance.parent);
      out.println(instance.child);
   }
}

The next screen snapshot shows the directory structure with these class .java source files in place. The screen snapshot shows that the source files are in a directory hierarchy representing the package name (dustin/examples because of package dustin.examples) and that this package-reflecting directory hierarchy is under a subdirectory called src. I have also created classes subdirectory (which is currently empty) to place the compiled .class files because javac will not create that directory when it doesn't exist.

Building with javac and Running with java

No matter which approach one uses to build Java code (Ant, Maven, Gradle, or IDE) normally, I believe it is prudent to at least understand how to build Java code with javac. The Oracle/Sun-provided javac command-line tool's standard options can be seen by running javac -help and additional extension options can be viewed by running javac -help -X. More details on how to apply these options can be found in the tools documentation for javac for Windows or Unix/Linux.

As the javac documentation states, the -sourcepath option can be use to express the directory in which the source files exist. In my directory structure shown in the screen snapshot above, this would mean that, assuming I'm running the javac command from the C:\java\examples\javacAndJava\ directory, I'd need to have something like this in my command: javac -sourcepath src src\dustin\examples\*.java. The next screen snapshot shows the results of this.

Because we did not specify a destination directory for the .class files, they were placed by default in the same directory as the source .java files from which they were compiled. We can use the -d option to rectify this situation. Our command could be run now, for example, as javac -sourcepath src -d classes src\dustin\examples\*.java. As stated earlier, the specified destination directory (classes) must already exist. When it does, the command will place the .class files in the designated directory as shown in the next screen snapshot.

With the Java source files compiled into the appropriate .class files in the specified directory, we can now run the application using the Java application launcher command line tool java. This is simply done by following the instructions shown by java -help or by the java tools page and specifying the location of the .class files with the -classpath (or -cp) option. Using both approaches to specify that the classes directory is where to look for the .class files is demonstrated in the next screen snapshot. The last argument is the fully qualified (entire Java package) name of the class who has a main function to be executed. The commands demonstrated in the next screen snapshot is java -cp classes dustin.examples.Main and java -classpath classes dustin.examples.Main.

Building and Running with Ant

For the simplest Java applications, it is pretty straightforward to use javac and java to build and execute the application respectively as just demonstrated. As the applications get a bit more involved (such as code existing in more than one package/directory or more complex classpath dependencies on third-party libraries and frameworks), this approach can become unwieldy. Apache Ant is the oldest of the "big three" of Java build tools and has been used in thousands of applications and deployments. As I discussed in a previous blog post, a very basic Ant build file is easy to create, especially if one starts with a template like I outlined in that post.

The next code listing is for an Ant build.xml file that can be use to compile the .java files into .class files and then run the dustin.examples.Main class just like was done above with javac and java.

build.xml
<?xml version="1.0" encoding="UTF-8"?>
<project name="BuildingSansIDE" default="run" basedir=".">
   <description>Building Simple Java Applications Without An IDE</description>

   <target name="compile"
           description="Compile the Java code.">
      <javac srcdir="src"
             destdir="classes"
             debug="true"
      includeantruntime="false" />
   </target>

   <target name="run" depends="compile"
           description="Run the Java application.">
      <java classname="dustin.examples.Main" fork="true">
         <classpath>
           <pathelement path="classes"/>
         </classpath>
      </java>
   </target>
</project>

I have not used Ant properties and not included common targets I typically include (such as "clean" and "javadoc") to keep this example as simple as possible and to keep it close to the previous example using javac and java. Note also that I've included "debug" set to "true" for the javac Ant task because it's not true in Ant's default but is true with javac's default. Not surprisingly, Ant's javac task and java task closely resemble the command tools javac and java.

Because I used the default name Ant expects for a build file when it's not explicitly specified (build.xml) and because I provided the "run" target as the "default" for that build file and because I included "compile" as a dependency to run the "run" target and because Ant was on my environment's path, all I need to do on the command line to get Ant to compile and run the example is type "ant" in the directory with the build.xml file. This is demonstrated in the next screen snapshot.

Although I demonstrated compiling AND running the simple Java application with Ant, I typically only compile with Ant and run with java (or a script that invokes java if the classpath is heinous).

Building and Running with Maven

Although Ant was the first mainstream Java build tool, Apache Maven eventually gained its own prominence thanks in large part to its adoption of configuration by convention and support for common repositories of libraries. Maven is easiest to use when the code and generated objects conform to its standard directory layout. Unfortunately, my example doesn't follow this directory structure, but Maven does allow us to override the expected default directory structure. The next code listing is for a Maven POM file that overrides the source and target directories and provides other minimally required elements for a Maven build using Maven 3.2.1.

pom.xml
<project>
   <modelVersion>4.0.0</modelVersion>
   <groupId>dustin.examples</groupId>
   <artifactId>CompilingAndRunningWithoutIDE</artifactId>
   <version>1</version>
  
   <build>
      <defaultGoal>compile</defaultGoal>
      <sourceDirectory>src</sourceDirectory>
      <outputDirectory>classes</outputDirectory>
      <finalName>${project.artifactId}-${project.version}</finalName>
   </build>
</project>

Because the above pom.xml file specifies a "defaultGoal" of "compile" and because pom.xml is the default custom POM file that the Maven executable (mvn) looks for and because the Maven installation's bin directory is on my path, I only needed to run "mvn" to compile the .class files as indicated in the next screen snapshot.

I can also run the compiled application with Maven using the command mvn exec:java -Dexec.mainClass=dustin.examples.Main, which is demonstrated in the next screen snapshot.

As is the case with Ant, I would typically not use Maven to run my simple Java application, but would instead use java on the compiled code (or use a script that invokes java directly for long classpaths).

Building and Running with Gradle

Gradle is the youngest, trendiest, and hippest of the three major Java build tools. I am sometimes skeptical of the substance of something that is trendy, but I have found many things to like about Gradle (written in Groovy instead of XML, built-in Ant support, built-in Ivy support, configuration by convention that is easily overridden, Maven repository support, etc.). The next example shows a Gradle build file that can be used to compile and run the simple application that is the primary example code for this post. It is adapted from the example I presented in the blog post Simple Gradle Java Plugin Customization.

build.gradle
apply plugin: 'java'
apply plugin: 'application'

// Redefine where Gradle should expect Java source files (*.java)
sourceSets {
    main {
        java {
            srcDirs 'src'
        }
    }
}

// Redefine where .class files are written
sourceSets.main.output.classesDir = file("classes")

// Specify main class to be executed
mainClassName = "dustin.examples.Main"

defaultTasks 'compileJava', 'run'

The first two lines of the build.gradle file specify application of the Java plugin and the Application plugin, bringing a bunch of functionality automatically to this build. The definition of "sourceSets" and "sourceSets.main.output.classesDir" allows overriding of Gradle's Java plugin's default directories for Java source code and compiled binary classes respectively. The "mainClassName" allows explicit specification of which class should be run as part of the Application plugin. The "defaultTasks" line specifies the tasks to be run by simply typing "gradle" at the command line: 'compileJava' is a standard task provided by the Java plugin and 'run' is a standard task provided by the Application plugin. Because I named the build file build.gradle and because I specified the default tasks of 'compileJava' and 'run' and because I have the Gradle installation bin directory on my path, all I needed to do to build and run the examples was to type "gradle" and this is demonstrated in the next screen snapshot.

Even the biggest skeptic has to admit that Gradle build is pretty slick for this simple example. It combines brevity from relying on certain conventions and assumptions with a very easy mechanism for overriding select defaults as needed. The fact that it's in Groovy rather than XML is also very appealing!

As is the case with Ant and Maven, I tend to only build with these tools and typically run the compiled .class files directly with java or a script that invokes java. By the way, I typically also archive these .class into a JAR for running, but that's outside the scope of this post.

Conclusion

An IDE is often not necessary for building simple applications and examples and can even be more overhead than it's worth for the simplest examples. In such a case, it's fairly easy to apply javac and java directly to build and run the examples. As the examples become more involved, a build tool such as Ant, Maven, or Gradle becomes more appealing. The fact that many IDEs support these build tools means that a developer could transition to the IDE using the build tool created earlier in the process if it was determined that IDE support was needed as the simple application grew into a full-fledged project.

Wednesday, January 8, 2014

Evolving Gradle Build from Ant Build: Importing Ant Build File

Changing the build system on a large project can be difficult and a lot of work. Fortunately for those migrating Ant builds to Gradle builds, Gradle provides particularly convenient mechanisms to facilitate this migration. Because Gradle is built on Groovy and Groovy includes built-in Ant support via AntBuilder, Gradle builds can use AntBuilder to call Ant tasks and run Ant targets. However, Gradle provides an even easier mechanism for referencing existing Ant targets from a Gradle build with Gradle's support for importing an Ant build via DefaultAntBuilder and that is the subject of this post.

Being able to call existing Ant targets from a new Gradle build is advantageous because it allows the migration to take place over time. One can start using Gradle almost immediately with all the real work delegated to the existing Ant build. Then, as time and priorities allow, different Ant tasks can be replaced with Gradle tasks.

To demonstrate how easy it is to import an Ant build in a Gradle build, I first provide the code listing for a simplified Ant build.

Ant Build File: build.xml
<?xml version="1.0" encoding="UTF-8"?>
<project name="JavaArrays" default="all" basedir=".">
   <description>Java Array Utility Functions</description>

   <property name="javac.debug" value="true" />
   <property name="src.dir" value="src" />
   <property name="dist.dir" value="dist" />
   <property name="classes.dir" value="classes" />
   <property name="javadoc.dir" value="${dist.dir}/javadoc" />

   <property name="jar.name" value="javaArrays.jar" />
   <property name="jar.filesonly" value="true" />

   <path id="classpath">
   </path>

   <target name="-init">
      <mkdir dir="${classes.dir}" />
      <mkdir dir="${dist.dir}" />
   </target>

   <target name="compile"
           description="Compile the Java code."
           depends="-init">
      <javac srcdir="${src.dir}"
             destdir="${classes.dir}"
             classpathref="classpath"
             debug="${javac.debug}"
             includeantruntime="false" />
   </target>

   <target name="jar"
           description="Package compiled classes into JAR file"
           depends="compile">
      <jar destfile="${dist.dir}/${jar.name}"
           basedir="${classes.dir}"
           filesonly="${jar.filesonly}">
      </jar>
   </target>

   <target name="all"
           description="Compile Java source, assemble JAR, and generate documentation"
           depends="jar, javadoc" />

   <target name="javadoc" description="Generate Javadoc-based documentation">
      <mkdir dir="${javadoc.dir}" />
      <javadoc doctitle="Examples of Java Array Utility Functions"
               destdir="${javadoc.dir}"
               sourcepath="${src.dir}"
               classpathref="classpath"
               private="true"
               author="Dustin" />
   </target>

   <target name="clean" description="Remove generated artifacts.">
      <delete dir="${classes.dir}" />
      <delete dir="${dist.dir}" />
   </target>

</project>

The above Ant build file has some fairly typical targets with names like "compile", "jar", "javadoc", and "clean". All of this functionality can be imported into a Gradle build file. The next code listing is the complete Gradle build file that does this.

Gradle build.gradle that imports Ant build.xml
ant.importBuild 'build.xml'

The one-line Gradle build file shown above imports the Ant build file shown earlier. The effects of this can be easily seen in the following screen snapshots. The initial screen snapshot shows that the single line Gradle build file makes the "arrays" project available to the Gradle build as well as "other tasks" of "all" and "clean" with the descriptions associated with those Ant targets.

One can use gradle tasks --all to see all Ant targets, including the dependent targets such as "compile", "jar", and "javadoc". This is demonstrated in the next screen snapshot.

The next screen snapshot demonstrates running the default "all" target in the Ant build from the Gradle build.

As the build listings and images have demonstrated, importing an existing Ant build in a Gradle build is a straightforward process.

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.

Tuesday, August 20, 2013

Ant Properties Nuances

Every once in a while, I'm reminded of a few subtle nuances of Ant properties that can, when forgotten, lead to confusion when dealing with Ant. In particular, the fact that Ant properties are generally immutable (not counting local properties as of Ant 1.8) and are set "permanently" upon their first setting can lead to slightly surprising results.

The properties section of the Ant Manual states, "Normally property values can not be changed, once a property is set, most tasks will not allow its value to be modified." The section of that manual on the Property task adds, "Properties are immutable: whoever sets a property first freezes it for the rest of the build; they are most definitely not variables."

The order of definition of properties influences their setting. In general, once a property is set, its value cannot be changed by a later attempt at redefinition in the same build file or in called build files. Furthermore, there are a set of properties that are already defined that generally cannot be redefined within an Ant build file. These include Java System properties and the built-in Ant properties.

Although definition of properties within an Ant build file cannot override the values of the default Ant built-in properties or the Java system properties, these values for these properties' names can generally be set with the -D option on the Ant launcher. However, a small number of these cannot be reset even with the -D option. For example, ant.file cannot be changed from the path and name of the Ant build file even when passed as a parameter via the -D option. Of course, it's probably just as well because there seems to be no good reason to pretend that the Ant build file is any other than what it really is.

To demonstrate the above "rules" of Ant property resolution, the following simple Ant build file can be used.

build.xml Displaying Properties in Ant
<project name="Project" default="showProperties" basedir=".">

   <property environment="env"/>
   
   <target name="showProperties">
      <!-- Java System Properties -->
      <echo message="java.home: ${java.home}" />
      <echo message="user.home: ${user.home}" />
      <!-- Custom Properties -->
      <echo message="name.last: ${name.last}" />
      <echo message="name.first: ${name.first}" />
      <!-- Ant Built-in Properties -->
      <echo message="ant.file: ${ant.file}" />
      <echo message="ant.version: ${ant.version}" />
      <echo message="ant.java.version: ${ant.java.version}" />
   </target>

</project>

There are a couple of Java system properties, a couple of custom properties, and a few Ant built-in properties in this example. These allow me to easily demonstrate how properties can be overridden or not overridden. The next screen snapshot shows the "default" settings of the properties without being overridden. The two custom ones are not defined at all, but the others (Java system and Ant built-in properties) have values automatically set for the Ant build.

The next screen snapshot shows an attempt to supply the values for the properties used in the build by passing them in via the -D parameters. As the example shows, even the system properties and Ant built-in properties can be overridden with the -D property setting, but the ant.file property is not overridden.

A common way to specify properties used in an Ant file are to specify them within the Ant build file using the Property task. The next code listing adds internally defined properties to the file shown above.

build.xml Defining Properties Internally
<project name="Project" default="showProperties" basedir=".">

   <property environment="env"/>
   <property name="user.home" value="/bin" />
   <property name="java.home" value="java" />
   <property name="name.last" value="Flintstone" />
   <property name="name.first" value="Fred" />
   <property name="ant.file" value="text.txt" />
   <property name="ant.version" value="1.8." />
   <property name="ant.java.version" value="6" />

   <target name="showProperties">
      <!-- Java System Properties -->
      <echo message="java.home: ${java.home}" />
      <echo message="user.home: ${user.home}" />
      <!-- Custom Properties -->
      <echo message="name.last: ${name.last}" />
      <echo message="name.first: ${name.first}" />
      <!-- Ant Built-in Properties -->
      <echo message="ant.file: ${ant.file}" />
      <echo message="ant.version: ${ant.version}" />
      <echo message="ant.java.version: ${ant.java.version}" />
   </target>

</project>

The next screen snapshot shows running this Ant file without any properties provided with the -D arguments. Note that the only properties which have been successfully set by the internal specification are the custom properties. The Java system properties and built-in Ant properties are unaffected by the attempts to internally set the properties.

There are advantages to Ant's properties generally being immutable. However, one must be cautious when assuming just because a property is declared in a particular Ant build file (or in a property file referenced by that build file), that it is actually the value to which that property is set for the build. If the property has already been set elsewhere, the local attempt at redefining the property has no effect other than to falsely advertise a value for that property which does not actually apply.

Monday, June 4, 2012

Recent Software Development Posts of Interest - Early June 2012

There has recently been a large number of posts on some of my favorite topics and I summarize and reference a few of them here.

Why Developers Should Blog

I devoted a post to why I believe more software developers should write blogs. Ron Gross's post Why you should write a blog post today provides five reasons to write technical blog posts and contrasts blogs to other social media to demonstrate technical memento advantages of blogs.

NetBeans 7.2 Search Capability

There has been significant coverage of NetBeans 7.2 beta in recent weeks. Much of the focus has been on its improved performance, its greater helpfulness and its TestNG support. Geertjan Wielenga focuses on improved searching capability in NetBeans 7.2 beta in his post Beefed Up Code Navigation Tools in NetBeans IDE 7.2.

Running Common Java Development Tools with Ant

Modern Java IDEs often make it easy to integrate Java tools. An example is the NetBeans SQE plugin. However, not all Java development and execution necessarily occurs in the IDE. In addition, some projects may use more than one IDE. In such cases, command-line approaches have their advantages. These can be even easier to use when run as Ant targets. The post How to run Findbugs, Pmd, Checkstyle, JUnit and Cobertrua with an Ant Build-Script? covers using Ant to run popular Java development tools FindBugs, PMD, Checkstyle, JUnit, and Cobertura.

Upgrading Gradle

I think that Gradle has a lot of potential for being a very popular build tool if it can gain enough early traction. The post Upgrading Gradle demonstrates how easy it is to upgrade one's Gradle installation ("It’s basically the same as upgrading ant.").

SVG, FXML, and NetBeans

The post SVG to FXML using NetBeans comprehensively covers issues related to transforming SVG to FXML and ends with a link to another post showing how to use NetBeans to demonstrate the transformation.

Conclusion

There have been numerous posts in recent weeks on some of my favorite topics: NetBeans, blogging, Gradle, and JavaFX. This post has referenced a sample of these posts.

Monday, May 23, 2011

Transforming Ant Build XML to Groovy

There are numerous ways in which Groovy can be applied to build Java-based applications. These building approaches range from invoking commands like javac and jar directly from Groovy code to Groovy-powered build systems like Gradle. Ant is still probably the best-known build tool in all of Javadom and there are numerous ways Groovy and Ant can be used together. I wrote briefly about three ways of using Ant with Groovy in the beginning of my post AntBuilder: Groovy Meets Ant and then focused on calling targets defined in Ant build files from Groovy code using Ant's ProjectHelper and Project classes.

In this post, I focus on writing a Groovy script that takes an existing Ant build file (often called build.xml) as input and generates a Groovy script that executes the same tasks defined in the build file. A few things about Groovy make this generation of code easier and I mention them as relevant in the post.

Writing code that generates code has been a hallmark of the lazy efficient developer for many years. Code that writes code can be used to replace mundane code writing with efficient and highly repeatable code generation. Code generators can take a while to build, but if the code they generate is large or commonly generated, the cost can be recovered in terms of future savings. Code generation is very common in the DBA/SQL world, but it is also common in software development. Hamlet D'Arcy's presentation Code Generation on the JVM: Writing Code that Writes Code covers various techniques for writing code that generates code on the JVM. I earlier reviewed the JavaOne 2010 version of his presentation given by Andres Almiray.

Groovy is particularly effective at writing Java and other Groovy code. I have demonstrated this with a few examples in posts such as Groovy: Java Enum Generation from XML and Reproducing "code too large" Problem in Java. In this post, I again look at using Groovy to generate other code. In this case, I generate Groovy code that performs similar functionality as an input Ant build file.

If one only wanted to execute the targets of the Ant file, I probably would just use Ant. If I wanted to run the targets as-is from an existing Ant file, I would probably use the approach I discussed in Executing Ant Build File Targets from Groovy. However, if I would like my Groovy code to execute some new targets in addition to or adapted from existing Ant build files, a good starting point would be a script like that I cover in this post. The code in this post could be extended to convert an existing Ant build file into Groovy code that could then be altered and enhanced as desired. This might be easier than trying to write the Groovy build code from scratch.

The source Ant build file used for the example in this post is called build.xml and is contained in the next code listing. It's a simple build file with some basic targets, but it is enough to demonstrate the concept of generating a Groovy script that performs the same as an Ant build file. Note that what the application this Ant file builds is not so important for what is covered here, but its related blog post is Effective Java NullPointerException Handling.

build.xml
<?xml version="1.0" encoding="UTF-8"?>
<project name="AvoidingNullPointerExceptionInJava" default="all" basedir=".">
   <description>Avoiding NullPointerException in Java</description>

   <property name="javac.debug" value="true" />
   <property name="src.dir" value="src" />
   <property name="dist.dir" value="dist" />
   <property name="classes.dir" value="classes" />
   <property name="javadoc.dir" value="${dist.dir}/javadoc" />

   <property name="jar.name" value="AvoidingNullPointerExceptionInJava.jar" />
   <property name="jar.filesonly" value="true" />

   <path id="javadoc.example.classpath" />

   <target name="-init">
      <mkdir dir="${classes.dir}" />
      <mkdir dir="${dist.dir}" />
   </target>

   <target name="compile"
           description="Compile the Java code."
           depends="-init">
      <javac srcdir="${src.dir}"
             destdir="${classes.dir}"
             classpathref="javadoc.example.classpath"
             debug="${javac.debug}" />
   </target>

   <target name="jar"
           description="Package compiled classes into JAR file"
           depends="compile">
      <jar destfile="${dist.dir}/${jar.name}"
           basedir="${classes.dir}"
           filesonly="${jar.filesonly}">
      </jar>
   </target>

   <target name="all"
           description="Compile Java source, assemble JAR, and generate documentation"
           depends="jar, javadoc" />

   <target name="javadoc" description="Generate Javadoc-based documentation">
      <mkdir dir="${javadoc.dir}" />
      <javadoc doctitle="Avoiding NullPointerException in Java Examples"
               destdir="${javadoc.dir}"
               sourcepath="${src.dir}"
               classpathref="javadoc.example.classpath"
               private="true"
               author="Dustin" />
   </target>

   <target name="clean" description="Remove generated artifacts.">
      <delete dir="${classes.dir}" />
      <delete dir="${dist.dir}" />
   </target>

</project>

The Ant build file is specified in XML. The good news here is that Groovy makes XML parsing very easy with either XmlSlurper or XmlParser (I use the former in this post). Indeed, one can easily parse this entire XML file with Groovy with very little challenge. However, the use of Ant's ProjectHelper and Project classes in conjunction with Groovy's XML parsing make the script generation a little cleaner because those two Ant classes do some of the heavy lifting for us in terms of providing project basics (name, description, default and other targets).

convertAntBuildFileToGroovy.groovy
#!/usr/bin/env groovy
/**
 * convertAntBuildFileToGroovy.groovy
 *
 * This is an example of how Groovy can be used to convert an Ant build.xml file
 * into a Groovy code equivalent.
 */

import org.apache.tools.ant.Project
import org.apache.tools.ant.ProjectHelper
 
def inputBuildFileName = args[0]
def outputScriptName = args.length > 1 ? args[1] : "antbuild.groovy"
def groovyScriptFile = new File(outputScriptName)
println "Parsing alleged Ant build XML file:\n\t${inputBuildFileName}"
println "Writing Groovy script:\n\t${outputScriptName}"

def projectXml = new XmlSlurper().parse(inputBuildFileName)
def antFile = new File(inputBuildFileName)
def project = new Project()
project.init()
ProjectHelper.projectHelper.parse(project, antFile)

def projectName = project.name
def projectDesc = project.description
def defaultTarget = project.defaultTarget
def basedir = project.baseDir
println "Ant project named: '${projectName}'"
println "Ant project description: '${projectDesc}'"
println "Ant project base directory: '${basedir}'"
def properties = new HashMap<String,String>()
def antProperties = project.properties
antProperties.each
{ key, value ->
   properties.put(key, escapeFilePath(value))
}
groovyScriptFile.write(buildScriptStart(outputScriptName, inputBuildFileName, projectName, properties))

println "Ant project targets:"
def targets = new HashMap<String,NodeChild>()
projectXml.target.each
{
   def targetName = it.@name as String
   targets.put(targetName, it)
   println "\t${targetName}"
}
groovyScriptFile << buildTargetExecutionMethod(targets.keySet(), defaultTarget)
targets.each
{ key, value ->
   groovyScriptFile << buildMethodForTarget(value, properties)
}


import groovy.util.slurpersupport.Attributes
import groovy.util.slurpersupport.NodeChild


/**
 * Replace any Ant properties in the provided String (indicated with ${} syntax)
 * with any Ant defined properties provided in the antProperties parameter and
 * matching the string.
 *
 * @param originalString String with potential Ant substitution tokens (${}).
 * @param antProperties Map of possible property name matches for substitution
 *    token.
 * @return Original String with substitution tokens replaced with Ant properties.
 */
def String replaceAntPropertiesWithValues(
   String originalString, Map<String,String> antProperties)
{
   def newValue = ""
   if (originalString.contains('${'))
   {
      newValue = originalString
      originalString.eachMatch(~/\$.*?}/)
      {
         def newString = antProperties.get(it.substring(2, it.size()-1))
         newValue = newValue.replace(it, newString)
      }
   }
   else
   {
      newValue = originalString
   }
   return newValue
}


/**
 * Build the code for the initial part of the Groovy script.
 *
 * @param scriptName Name of the script.
 * @param buildFileName Name of build file which is source for generated script.
 * @param antProjectName Name of project in Ant build file.
 * @param antProperties Properties explicitly specified in Ant build file.
 * @return Groovy code for initial portion of script.
 */
def String buildScriptStart(
   String scriptName, String buildFileName, String antProjectName,
   Map<String, String> antProperties)
{
   def groovyShebang = '#!/usr/bin/env groovy'
   def antBuilder = "ant = new AntBuilder()"
   def scriptStart = new StringBuilder()
   scriptStart << groovyShebang << "\n\n"
   scriptStart << "//\n// ${scriptName}\n//\n// "
   scriptStart << "Build script adapted from Ant build file\n"
   scriptStart << "// ${buildFileName}\n"
   scriptStart << "// and project ${antProjectName}\n" 
   scriptStart << "//\n\n" << antBuilder << "\n\n"
   scriptStart << "def properties = ${antProperties.inspect()}\n\n"
   return scriptStart
}


/**
 * Build the set of methods that will be called to execute script equivalents
 * of Ant targets in XML file.
 *
 * @param targetNames Names of Ant targets.
 * @param defaultTarget The default target for the Ant project.
 * @return Groovy code calling method equivalents of XML targets.
 */
def String buildTargetExecutionMethod(Set<String> targetNames, String defaultTarget)
{
   println "Target Names: ${targetNames}"
   def targetExecStr = new StringBuilder()
   targetExecStr << "if (args.length < 1)\n"
   targetExecStr << "{\n"
   targetExecStr << "   ${buildMethodName(defaultTarget)}\n"
   targetExecStr << "}\n"
   targetExecStr << "else\n"
   targetExecStr << "{\n"
   targetExecStr << "   args.each\n"
   targetExecStr << "   {\n"
   targetNames.each
   {
      if (!it.startsWith('-'))
      {
         targetExecStr << "       if (it.equals('${it}'))\n"
         targetExecStr << "       {\n"
         targetExecStr << "          ${buildMethodName(it)}\n"
         targetExecStr << "       }\n"
      }
   }
   targetExecStr << "   }\n"
   targetExecStr << "}\n"
   return targetExecStr
}


/**
 * Build code for a Groovy method from the provided 'target' element.
 *
 * @param targetElement NodeChild representing an Ant XML target element.
 * @return Code for a Groovy method corresponding to the provided Ant XML
 *    target element.
 */
def String buildMethodForTarget(NodeChild targetElement, Map<String,String> antProperties)
{
   def methodComment = targetElement.@description
   def methodCode = new StringBuilder()
   if (methodComment && !methodComment.isEmpty())
   {
      methodCode << "\n/**\n * ${methodComment}\n */\n"
   }
   methodCode << "def void ${buildMethodName(targetElement.@name as String)}\n"
   methodCode << "{\n"
   methodCode << buildDependencyCalls(targetElement.@depends)
   targetElement.mkdir.each
   {
      methodCode << "   " << buildAntMakeDirectoryCall(it, antProperties) << "\n"
   }
   targetElement.delete.each
   {
      methodCode << "   " << buildAntDeleteCall(it, antProperties) << "\n"
   }
   targetElement.javac.each
   {
      methodCode << "   " << buildAntJavacCall(it, antProperties) << "\n"
   }
   targetElement.jar.each
   {
      methodCode << "   " << buildAntJarCall(it, antProperties) << "\n"
   }
   targetElement.javadoc.each
   {
      methodCode << "   " << buildAntJavadocCall(it, antProperties) << "\n"
   }
   methodCode << "}\n"
   return methodCode
}


/**
 * Build a Groovy-friendly method name from the provided Ant target name.
 *
 * @param targetName Name of Ant target.
 * @return Groovy-friendly method name corresponding to provided Ant target name.
 */
def String buildMethodName(String targetName)
{
   return "run${(targetName - "-").capitalize()}Target()"
}


/**
 * Build Groovy code calling dependent methods.
 *
 * @param dependencyAttribute Ant Target's depends attribute.
 * @return Groovy code calling dependencies.
 */
def String buildDependencyCalls(Attributes dependencyAttribute)
{
   def methodsCalled = new StringBuilder()
   def dependencyStr = dependencyAttribute as String
   if (!dependencyStr.isEmpty())
   {
      def dependencies = dependencyStr.replace(" ", "").split(",")
      dependencies.each
      {
         methodsCalled << "   ${buildMethodName(it)}\n"
      }
   }
   return methodsCalled
}


/**
 * Builds a Groovy Ant mkdir call from provided directory name.
 *
 * @param mkdirElement Ant XML element representation for mkdir task.
 * @param antProperties Properties for Ant project.
 * @return String containing Groovy call for using AntBuilder to create directory.
 */
def String buildAntMakeDirectoryCall(NodeChild mkdirElement, Map<String,String> antProperties)
{
   def evaluatedDirectoryName = replaceAntPropertiesWithValues(
      mkdirElement.@dir as String, antProperties)
   def mkdirCall = new StringBuilder()
   def antBaseDir = antProperties.get('basedir')
   mkdirCall << "ant.mkdir(dir: '${mergeAntBaseDirWithRelativeDir(antBaseDir, evaluatedDirectoryName)}')"
   return mkdirCall
}


/**
 * Builds a Groovy Ant delete call from provided directory name.
 *
 * @param mkdirElement Ant XML element representation for delete task.
 * @param antProperties Properties for Ant project.
 * @return String containing Groovy call for using AntBuilder to delete.
 */
def String buildAntDeleteCall(NodeChild deleteElement, Map<String,String> antProperties)
{
   def evaluatedDirectoryName = replaceAntPropertiesWithValues(
      deleteElement.@dir as String, antProperties)
   def deleteCall = new StringBuilder()
   def antBaseDir = antProperties.get('basedir')
   deleteCall << "ant.delete(dir: '${mergeAntBaseDirWithRelativeDir(antBaseDir, evaluatedDirectoryName)}')"
   return deleteCall
}


/**
 * Builds a Groovy Ant Javac Task call from the provided build's "javac" element.
 *
 * @param jarElement Ant XML element representation for javac task.
 * @param antProperties Properties for Ant project.
 * @return Groovy code for invoking Javac Ant task as XML element did.
 */
def String buildAntJavacCall(NodeChild javacElement, Map<String,String> antProperties)
{
   def javacCall = new StringBuilder()
   def antBaseDir = antProperties.get('basedir')
   def javacSrcDir = replaceAntPropertiesWithValues(
      javacElement.@srcdir as String, antProperties)
   def javacDestDir = replaceAntPropertiesWithValues(
      javacElement.@destdir as String, antProperties)
   def javacDebug = replaceAntPropertiesWithValues(
      javacElement.@debug as String, antProperties)
   javacCall << "ant.javac(srcdir: '${mergeAntBaseDirWithRelativeDir(antBaseDir, javacSrcDir)}', "
   javacCall << "destdir: '${mergeAntBaseDirWithRelativeDir(antBaseDir, javacDestDir)}', "
   javacCall << "debug: '${javacDebug}')"

   return javacCall
}


/**
 * Builds a Groovy Ant Jar Task call from the provided build's "jar" element.
 *
 * @param jarElement Ant XML element representation for jar task.
 * @param antProperties Properties for Ant project.
 * @return Groovy code for invoking Jar Ant task as XML element did.
 */
def String buildAntJarCall(NodeChild jarElement, Map<String,String> antProperties)
{
   def antJarCall = new StringBuilder()
   def destfile = replaceAntPropertiesWithValues(
      jarElement.@destfile as String, antProperties)
   def classBasedir = replaceAntPropertiesWithValues(
      jarElement.@basedir as String, antProperties)
   def filesonly = replaceAntPropertiesWithValues(
      jarElement.@filesonly as String, antProperties)
   def antBaseDir = antProperties.get('basedir')
   antJarCall << "ant.jar(destfile : '${mergeAntBaseDirWithRelativeDir(antBaseDir, destfile)}', "
   antJarCall << "basedir : '${mergeAntBaseDirWithRelativeDir(antBaseDir, classBasedir)}', "
   antJarCall << "filesonly : '${filesonly}')"
   return antJarCall
}


/**
 * Builds a Groovy Ant Javadoc Task call from the provided build's "javadoc" element.
 *
 * @param jarElement Ant XML element representation for javadoc task.
 * @param antProperties Properties for Ant project.
 * @return Groovy code for invoking Javadoc Ant task as XML element did.
 */
def String buildAntJavadocCall(NodeChild javadocElement, Map<String,String> antProperties)
{
   def antJavadocCall = new StringBuilder()
   def doctitle = javadocElement.@doctitle
   def destdir = replaceAntPropertiesWithValues(
      javadocElement.@destdir as String, antProperties)
   def sourcepath = replaceAntPropertiesWithValues(
      javadocElement.@sourcepath as String, antProperties)
   def javadocPrivate = javadocElement.@private
   def author = javadocElement.@author
   def antBaseDir = antProperties.get('basedir')
   antJavadocCall << "ant.javadoc(doctitle: '${doctitle}', "
   antJavadocCall << "destdir: '${mergeAntBaseDirWithRelativeDir(antBaseDir, destdir)}', "
   antJavadocCall << "sourcepath: '${mergeAntBaseDirWithRelativeDir(antBaseDir, sourcepath)}', "
   antJavadocCall << "private: '${javadocPrivate}', "
   antJavadocCall << "author: '${author}')"

   return antJavadocCall
}


/**
 * Combine the two provided directories (base and relative) into single directory
 * path.
 *
 * @param antBaseDir Base directory for Ant project.
 * @param relativeDir Relative directory.
 * @return Consolidated directory formed by base + relative directory.
 */
def String mergeAntBaseDirWithRelativeDir(String antBaseDir, String relativeDir)
{
   return "${antBaseDir}${File.separator}${File.separator}${relativeDir}"
}


/**
 * Escapes the provided file path appropriately for a Java/Groovy String that
 * will need to be processed by Java/Groovy APIs. The returned String has two
 * escaped Windows/DOS path separators.
 *
 * @param filePath File path to be converted to Java/Groovy escaped String for
 *     DOS/Windows file separator.
 * @return String with any Windows/DOS escaped file path separators converted
 *     to two escaped Windows/DOS file path separators.
 */
def String escapeFilePath(String filePath)
{
   return filePath.replace("\\", "\\\\")
}

The next screen snapshot shows the output of this script as it generates a new Groovy script.


The generated Groovy script, antbuild.groovy is shown next. All of the code in this code listing was generated code.

antbuild.groovy (Generated Script)
#!/usr/bin/env groovy

//
// antbuild.groovy
//
// Build script adapted from Ant build file
// C:\java\examples\avoidingNullPointerExceptions\build.xml
// and project AvoidingNullPointerExceptionInJava
//

ant = new AntBuilder()

def properties = ["java.vm.version":"21.0-b04", "ant.core.lib":"C:\\groovy-1.8.0\\lib\\ant-1.8.2.jar", "sun.jnu.encoding":"Cp1252", "java.vendor.url":"http://java.oracle.com/", "java.vm.info":"mixed mode, sharing", "user.dir":"C:\\java\\examples\\groovyExamples\\antToGroovy", "sun.cpu.isalist":"pentium_pro+mmx pentium_pro pentium+mmx pentium i486 i386 i86", "java.awt.graphicsenv":"sun.awt.Win32GraphicsEnvironment", "sun.os.patch.level":"Service Pack 2", "tools.jar":"C:\\Program Files\\Java\\jdk1.7.0\\lib\\tools.jar", "java.io.tmpdir":"C:\\Users\\Dustin\\AppData\\Local\\Temp\\", "user.home":"C:\\Users\\Dustin", "java.awt.printerjob":"sun.awt.windows.WPrinterJob", "java.version":"1.7.0-ea", "file.encoding.pkg":"sun.io", "java.vendor.url.bug":"http://java.sun.com/cgi-bin/bugreport.cgi", "javac.debug":"true", "file.encoding":"Cp1252", "sun.java.command":"org.codehaus.groovy.tools.GroovyStarter --main groovy.ui.GroovyMain --conf C:\\groovy-1.8.0\\conf\\groovy-starter.conf --classpath .;C:\\Program Files\\Java\\jre6\\lib\\ext\\QTJava.zip;. convertAntBuildFileToGroovy.groovy C:\\java\\examples\\avoidingNullPointerExceptions\\build.xml", "line.separator":"\r\n", "java.vm.specification.vendor":"Oracle Corporation", "dist.dir":"dist", "java.vm.vendor":"Oracle Corporation", "java.class.path":"C:\\groovy-1.8.0\\lib\\groovy-1.8.0.jar", "sun.io.unicode.encoding":"UnicodeLittle", "user.variant":"", "user.language":"en", "user.name":"Dustin", "os.arch":"x86", "java.runtime.version":"1.7.0-ea-b134", "sun.boot.class.path":"C:\\Program Files\\Java\\jdk1.7.0\\jre\\lib\\resources.jar;C:\\Program Files\\Java\\jdk1.7.0\\jre\\lib\\rt.jar;C:\\Program Files\\Java\\jdk1.7.0\\jre\\lib\\sunrsasign.jar;C:\\Program Files\\Java\\jdk1.7.0\\jre\\lib\\jsse.jar;C:\\Program Files\\Java\\jdk1.7.0\\jre\\lib\\jce.jar;C:\\Program Files\\Java\\jdk1.7.0\\jre\\lib\\charsets.jar;C:\\Program Files\\Java\\jdk1.7.0\\jre\\lib\\modules\\jdk.boot.jar;C:\\Program Files\\Java\\jdk1.7.0\\jre\\classes", "sun.desktop":"windows", "script.name":"C:\\java\\examples\\groovyExamples\\antToGroovy\\convertAntBuildFileToGroovy.groovy", "sun.cpu.endian":"little", "groovy.starter.conf":"C:\\groovy-1.8.0\\conf\\groovy-starter.conf", "awt.toolkit":"sun.awt.windows.WToolkit", "sun.boot.library.path":"C:\\Program Files\\Java\\jdk1.7.0\\jre\\bin", "java.vm.name":"Java HotSpot(TM) Client VM", "java.home":"C:\\Program Files\\Java\\jdk1.7.0\\jre", "javadoc.dir":"dist/javadoc", "ant.java.version":"1.7", "java.endorsed.dirs":"C:\\Program Files\\Java\\jdk1.7.0\\jre\\lib\\endorsed", "basedir":"C:\\java\\examples\\avoidingNullPointerExceptions", "sun.management.compiler":"HotSpot Client Compiler", "ant.project.name":"AvoidingNullPointerExceptionInJava", "java.runtime.name":"Java(TM) SE Runtime Environment", "jar.name":"AvoidingNullPointerExceptionInJava.jar", "ant.file.type.AvoidingNullPointerExceptionInJava":"file", "java.library.path":"C:\\Program Files\\Java\\jdk1.7.0\\bin;.;C:\\Windows\\Sun\\Java\\bin;C:\\Program Files\\Java\\jdk1.7.0\\bin", "file.separator":"\\", "java.specification.vendor":"Oracle Corporation", "java.vm.specification.version":"1.7", "ant.file.AvoidingNullPointerExceptionInJava":"C:\\java\\examples\\avoidingNullPointerExceptions\\build.xml", "groovy.home":"C:\\groovy-1.8.0", "sun.java.launcher":"SUN_STANDARD", "ant.version":"Apache Ant(TM) version 1.8.2 compiled on December 20 2010", "user.timezone":"", "program.name":"", "os.name":"Windows Vista", "path.separator":";", "jar.filesonly":"true", "classes.dir":"classes", "java.ext.dirs":"C:\\Program Files\\Java\\jdk1.7.0\\jre\\lib\\ext;C:\\Windows\\Sun\\Java\\lib\\ext", "sun.arch.data.model":"32", "os.version":"6.0", "java.specification.name":"Java Platform API Specification", "ant.project.default-target":"all", "user.script":"", "user.country":"US", "java.class.version":"51.0", "java.vendor":"Oracle Corporation", "java.vm.specification.name":"Java Virtual Machine Specification", "java.specification.version":"1.7", "src.dir":"src"]

if (args.length < 1)
{
   runAllTarget()
}
else
{
   args.each
   {
       if (it.equals('clean'))
       {
          runCleanTarget()
       }
       if (it.equals('jar'))
       {
          runJarTarget()
       }
       if (it.equals('compile'))
       {
          runCompileTarget()
       }
       if (it.equals('javadoc'))
       {
          runJavadocTarget()
       }
       if (it.equals('all'))
       {
          runAllTarget()
       }
   }
}

/**
 * Remove generated artifacts.
 */
def void runCleanTarget()
{
   ant.delete(dir: 'C:\\java\\examples\\avoidingNullPointerExceptions\\classes')
   ant.delete(dir: 'C:\\java\\examples\\avoidingNullPointerExceptions\\dist')
}
def void runInitTarget()
{
   ant.mkdir(dir: 'C:\\java\\examples\\avoidingNullPointerExceptions\\classes')
   ant.mkdir(dir: 'C:\\java\\examples\\avoidingNullPointerExceptions\\dist')
}

/**
 * Package compiled classes into JAR file
 */
def void runJarTarget()
{
   runCompileTarget()
   ant.jar(destfile : 'C:\\java\\examples\\avoidingNullPointerExceptions\\dist/AvoidingNullPointerExceptionInJava.jar', basedir : 'C:\\java\\examples\\avoidingNullPointerExceptions\\classes', filesonly : 'true')
}

/**
 * Compile the Java code.
 */
def void runCompileTarget()
{
   runInitTarget()
   ant.javac(srcdir: 'C:\\java\\examples\\avoidingNullPointerExceptions\\src', destdir: 'C:\\java\\examples\\avoidingNullPointerExceptions\\classes', debug: 'true')
}

/**
 * Generate Javadoc-based documentation
 */
def void runJavadocTarget()
{
   ant.mkdir(dir: 'C:\\java\\examples\\avoidingNullPointerExceptions\\dist/javadoc')
   ant.javadoc(doctitle: 'Avoiding NullPointerException in Java Examples', destdir: 'C:\\java\\examples\\avoidingNullPointerExceptions\\dist/javadoc', sourcepath: 'C:\\java\\examples\\avoidingNullPointerExceptions\\src', private: 'true', author: 'Dustin')
}

/**
 * Compile Java source, assemble JAR, and generate documentation
 */
def void runAllTarget()
{
   runJarTarget()
   runJavadocTarget()
}

The above generated script can then be run to execute the same targets as in the original Ant build file. The next series of screen snapshots indicate running the generated Groovy script. The first image shows the "default" case where no target name is specified and the functionality is equivalent to that when the Ant build file's default is used. The other images demonstrate passing various target names to the generated script. Although the generated code is a little verbose in handling these options, it's not a big deal because it is generated code.






As the above images indicate, the generated Groovy script can now be used to execute targets in a manner very similar to running them via Ant directly.

The original script generated this script using many nice features of Groovy. These useful features demonstrated in that script include closures, AntBuilder, XML slurping, and advanced String functionality.

It is the use of AntBuilder that makes the generated script relatively short and easy to generate. A second purpose of this post after demonstrating generation of a Groovy build script from a source Ant build file is to also demonstrate AntBuilder used for various tasks such as making directories, cleaning, compiling, archiving, and generating Javadoc. The last shown Groovy code listing (the generated Groovy script) is contains several examples of AntBuilder in action.

I am confident that the original script could be made even leaner, but it does its job for the simple example build.xml file. The script only covers tasks and task attributes that exist in this particular source XML file, but could be easily extended to cover more Ant tasks and more attributes for the already covered Ant tasks.


Conclusion

Groovy is useful for all types of tasks related to development. In this post, I've shown how Groovy can be used to generate more Groovy code that mimics what an input Ant build file does. From this point, one could modify and enhance the generated script to suit particular script-related needs.

Tuesday, May 17, 2011

Executing Ant Build File Targets from Groovy

In the post AntBuilder: Groovy Meets Ant, I talked about how AntBuilder combines the best of Ant with Groovy. The Groovy Almanac post Using Ant build.xml and Antbuilder demonstrates how easy it is to use Ant's ProjectHelper and Project classes to execute Ant targets from Groovy code. In this, post I adapt and slightly expand that example to show how Groovy can be used to run any target in a specified Ant build file.

applyBuildFileInGroovy.groovy

#!/usr/bin/env groovy
/**
 * applyBuildFileInGroovy.groovy
 *
 * This is an example of executing an Ant build file from Groovy code using
 * Ant's Project and ProjectHelper classes.
 *
 * 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.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()
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);
}

The simple Groovy script shown above will run the default target of the provided Ant build file if no other target is specified. If any arguments beyond the build file are provided to the script, they are treated as target names and processed.

In general, it might be just as easy to use Ant directly against the specified build file. However, the benefit of calling these targets from Groovy is that Groovy scripts and other code have access to pre-existing targets. In my post AntBuilder: Groovy Meets Ant, I focused on leveraging AntBuilder's DSL capabilities to build Ant calls in the code. The approach shown in this post uses targets already set up in an external Ant build file. This allows for easy reuse of custom targets from Groovy code without necessarily having to execute an Ant build. A side benefit of this is consistency between the direct Ant build and the Groovy code using the same targets.

Groovy's handy AntBuilder class is not used in the simple script above. In fact, although there is some Groovy syntax in the example, the main classes used (org.apache.tools.ant.Project and org.apache.tools.ant.ProjectHelper) are Java classes that could be used in any Java application or code. Groovy is a particularly nice fit, though, because of its concise, script-friendly syntax.

It is always possible to call Ant from Groovy code just as one would call any command from the operating system command line, but the tighter integration of using Project and ProjectHelper directly from Groovy code is preferable for several reasons. An example of this is the Project.getTargets() method. This method can be used in calling Groovy code to determine which targets are available. This list could be used to generate an interactive script that listed available targets and executed the selected one rather than passing them in on the command-line.

Conclusion

When pre-existing Ant build files contain build logic that is needed in a Groovy script, Ant's class for representing a project (Project) and Ant's class for configuring a project (ProjectHelper) are easy to use. When combined with Groovy, these classes enable concise scripts for calling Ant targets programmatically.

Tuesday, February 15, 2011

AntBuilder: Groovy Meets Ant

One of convenient classes that Groovy provides is the AntBuilder class, whose purpose is best described in the Javadoc-based documentation: "Allows Ant tasks to be used with GroovyMarkup." The AntBuilder class provides a strong association between the Groovy programming language and the well-known and ubiquitous Ant build tool.

The Ant Integration with Groovy section of the Groovy User Guide describes three common situations in which Ant ("the predominant build environment for Java projects") and Groovy ("a leading Scripting language for the JVM") are used in conjunction with one another. The first listed approach is when developers supplement their Java-building Ant build.xml scripts with Groovy using the specific to Groovy <groovy> tag or the general to Ant <script> tag and embed Groovy code inside these tag's bodies.

The second listed approach for integrating Groovy and Ant is to use Ant to compile Groovy code. The groovyc Ant task (<groovyc>) can be used to build Groovy code with the groovyc joint compiler. Although it can be desirable to run Groovy scripts and other code without explicit compilation, there are several advantages to compiling Groovy with groovyc and this groovyc Ant task makes this even easier to do.

The third listed approach for integrating Groovy and Ant is use of AntBuilder and is the focus of this blog post. Groovy's DSL capabilities are leveraged by AntBuilder to provide a "fluent" Groovy markup representation of the Ant tasks most of us are familiar with from Ant XML files.

There are advantages to being able to apply one's Ant knowledge from within Groovy code. First, many of us have used Ant for years and have anywhere from a passing familiarity to deep knowledge of the available Ant tasks. This is an advantage both in terms of developing the Groovy script code and in terms of others reading and maintaining the code. Even a relatively inexperienced Groovy developer who has some Ant knowledge will benefit when using AntBuilder in Groovy scripts.

The second advantage of being able to invoke Ant functionality easily from Groovy code is that Ant has gained significant functionality over its (relatively) long existence. Ant is no longer used explicitly for simple compilation and JAR/WAR/EAR assembly. It has functionality for file copying and naming, using Java mail, and a wide breadth of other functionality. All of Ant's richness is available to the Groovy developer via AntBuilder. In some cases, it may be easier (more concise) for the Groovy developer to use Ant's predefined task functionality than to write the equivalent functionality in Groovy.

Even when the task is to build Java projects, a developer may choose to perform the build via Groovy scripts rather than via Ant and its XML directly. That is another example of an advantage offered by Groovy+Ant integration encapsulated in AntBuilder. The developer can write Groovy scripts with conditionals, loops, and other full language constructs while still taking advantage of Ant's deep and broad building functionality. In other words, with AntBuilder, the developer can benefit from all of Ant's features without using XML.

AntBuilder makes use of and is illustrative of Groovy's metaobject facilities and the ability to build Domain Specific Languages (DSLs) in Groovy. Martin Fowler describes what a domain specific language is: "a computer language that's targeted to a particular kind of problem, rather than a general purpose language that's aimed at any kind of software problem." I have illustrated another Groovy DSL (MarkupBuilder) in the post GroovySql and MarkupBuilder: SQL-to-XML. Like MarkupBuilder, AntBuilder extends the abstract base class BuilderSupport (as do SwingBuilder and NodeBuilder).

The Groovy builders are particularly well suited for generating hierarchical data output. Therefore, it's no surprise that builders are useful for building markup (XML and HTML for example) and even for helping with Swing. See How Builders Work for a basic introduction into using Groovy builders and Practical Groovy: Mark it up with Groovy Builders for greater details.

AntBuilder makes it easy to harness the functionality of Ant from within Groovy code. I'll use of an example of using AntBuilder to begin my explanation of how it works and is used. Ant's support for file operations has been proven perhaps millions of times by now. The next code listing demonstrates Groovy code that uses AntBuilder to take advantage of Ant's file copying functionality. AntBuilder is not the only way to copy files in Groovy and two other ways (copying delegated to Windows/DOS operating system and copying using Groovy GDK) are also included in the example.

demoGroovyFileCopying.groovy
#!/usr/bin/env groovy

/*
 * demoGroovyFileCopying.groovy
 *
 * Demonstrates copying files in Groovy with three different mechanisms:
 *   1. AntBuilder
 *   2. Operating System copying with String.execute()
 *   3. Groovy File.write(File.text)
 */
 
 // AntBuilder instance used throughout this script
 AntBuilder antBuilder = new AntBuilder()
 
 demonstrateFileCopying(args[0], "target", antBuilder)
 
 /**
  * Demonstrate file copying in Groovy. Generated target files will be copies of
  * the file represented by the provided sourceFilePathAndName and the generated
  * files have names that start with the targetFileNameBase and have the type of
  * Groovy copying appended to the base in the file name.
  *
  * @param sourceFilePathAndName Path and file name of source file to be copied.
  * @param targetFileNameBase Base portion of name of all target files created
  *    as copies of the source file.
  */
 def void demonstrateFileCopying(
    final String sourceFilePathAndName,
    final String targetFileNameBase,
    final AntBuilder ant)
 {
    // File copying with AntBuilder
    def targetFileFromAntCopyName = targetFileNameBase + "_ant"
    def targetFileFromAntCopy = ant.copy(file: sourceFilePathAndName,
                                         tofile: targetFileFromAntCopyName)

    // File copying with Windows/DOS operating system and GDK String.execute()
    //   (use 'copy' in DOS and need 'cmd /c')
    "cmd /c copy ${sourceFilePathAndName} ${targetFileNameBase+'_dos'}".execute()

    // File copying with Groovy GDK File functionality
    def sourceFile = new File(sourceFilePathAndName)
    new File(targetFileNameBase + '_groovy').write(sourceFile.text)
 }

The next screen snapshot demonstrates the directory before and after this script is executed and includes the output showing the Ant functionality for file copy being invoked.


It could be argued that for file copying, using the Groovy approach with the GDK File class is the easiest approach. However, a Groovy developer not familiar with the Groovy approach might be more comfortable with the AntBuilder approach. Either approach is obviously preferable in most cases to the operating system dependent approach.

The AntBuilder approach might be easier if specialized functionality related to the copying needed to be performed that is supported by the Ant copy task but might not be so readily applied directly in Groovy. Suppose that I had a file that I not only wanted to copy, but wanted to replace tokens inside the source file with something else in the copied target file. Ant shines here and therefore so does AntBuilder. To illustrate this, I use a simple source file as shown in the next listing.

tokenSource.txt
Hello, my name is @NAME@ and I like Ant and Groovy.

With the above source file in place with a token delimited with the @ character, AntBuilder is again demonstrated in the following example.

copyFileAndReplaceNameToken.groovy
#!/usr/bin/env groovy

/*
 * demoGroovyFileCopyingAndTokenReplacing.groovy
 *
 * Demonstrates copying file with Groovy and AntBuilder and replacing @NAME@
 * token in source file in the copied file.
 */
 
// AntBuilder instance used throughout this script
def ant = new AntBuilder()
def sourceFilePathAndName = args[0]
def targetFileFromAntCopyName =  "target_antToken"
def targetFileFromAntCopy =
   ant.copy(file: sourceFilePathAndName,
            tofile: targetFileFromAntCopyName)
            {
               filterset()
               {
                  filter(token: "NAME", value: "Dustin")
               }
            }

The above Groovy code uses AntBuilder to not only copy the file, but to also replace the token @NAME@ with "Dustin". This output is shown in the next screen snapshot.


As the last example shows, AntBuilder makes it easy to leverage Ant's wide set of functionality from Groovy without a hint of XML. A developer familiar with Ant's XML structure is likely to be able to map Ant XML to the Groovy markup. Builders work so well because they rely on conventions and AntBuilder is no different. The convention is that XML element names are nested within other XML element names by placing them within curly braces of the outer elements. Attributes in the XML map to name/value pairs separated by colons within the parentheses containing arguments to the names associated with XML elements. In other words, the above Groovy script for copying the file with the filter is equivalent to the XML below in an Ant build script (where "args[0]" corresponds to the String passed in as the first argument to the Groovy script).

  <copy file="args[0]" tofile="target_antToken">
    <filterset>
      <filter token="NAME" value="Dustin"/>
    </filterset>
  </copy>

It is relatively easy to see the mapping of the above XML to the Groovy markup the way I indented it. Of course, I could have chosen to use less whitespace to make the code appear even more concise, but at the arguable cost of being less readable.

To apply core Ant tasks (such as copy used above) within Groovy using AntBuilder, nothing has to be placed on the script's classpath because Ant is included in the Groovy distribution. However, any optional Ant tasks that have external library dependencies require those external libraries to be placed on the classpath.

Conclusion

AntBuilder provides powerful functionality for the Groovy developer. While it can certainly be used in building code, its providing of Ant's breadth and depth of features can be useful in all sorts of contexts in which Groovy might be used besides simply builds. Appropriate use of AntBuilder can reduce the necessary Groovy code required for certain operations when Ant already does the heavy lifting. AntBuilder can also serve as a crutch to the developer who knows Ant better than Groovy but wants to write Groovy scripts.