Integrating Groovy into the build process using Ant

Apache Ant (http://ant.apache.org/) was one of the first build tools that appeared within the Java ecosystem, and it is still widely used by many organizations around the globe.

In this recipe, we will cover how to compile and test a complete Groovy project using Apache Ant. At the end of the recipe, we will show you how to use Groovy from within Ant to add scripting to a build task.

Getting ready

For demonstrating the build tool integration, let's create the following folders and files:

src/main/groovy/org/groovy/cookbook
    DatagramWorker.groovy
    MessageReceiver.groovy
    MessageSender.groovy
src/test/groovy/org/groovy/cookbook
    MessageProcessingTest.groovy

The aim of the project is to create a very simple UDP client/server and accompany it with a unit test.

The base class for the server and the client, DatagramWorker, has the following code:

abstract class DatagramWorker {

  byte[] buffer = new byte[256]
  DatagramSocket socket = null
  String lastMessage = null

  DatagramPacket receive() {
    def packet = new DatagramPacket(buffer, buffer.length)
    socket.receive(packet)
    lastMessage = new String(packet.data, 0, packet.length)
    println "RECEIVED: $lastMessage"
    packet
  }

  def send(String message,
           InetAddress address,
           int port) {
    def output = new DatagramPacket(
                   message.bytes,
                   message.length(),
                   address,
                   port
                 )
    socket.send(output)
  }

  def send(DatagramPacket inputPacket) {
    send('OK', inputPacket.address, inputPacket.port)
  }
}

The class contains a field for holding the socket object, a temporary data buffer and the lastMessage received by the socket. Additionally, it exposes basic methods for sending and receiving data.

The server class, MessageReceiver, is implemented in the following way:

class MessageReceiver extends DatagramWorker {

  def start() {
    socket = new DatagramSocket(12345)
    Thread.startDaemon {
      while(true) {
        send(receive())
      }
    }
  }

}

Upon the start method call, it starts a daemon thread that listens to incoming messages on port 12345 and sends OK back to the client when a message is received.

The client class, MessageSender, looks as follows:

class MessageSender extends DatagramWorker {

  def send(String message) {
    socket = new DatagramSocket()
    send(message, InetAddress.getByName('localhost'), 12345)
    receive()
  }

}

It contains a single method send, which sends a given message to the server running at localhost on port 12345.

Finally the unit test, MessageProcessingTest, verifies that both the server (receiver) and the client (sender) can successfully exchange messages:

import org.junit.Test

class MessageProcessingTest {

  MessageReceiver receiver = new MessageReceiver()
  MessageSender sender = new MessageSender()

  @Test
  void testMessages() throws Exception {
    receiver.start()
    sender.send('HELLO')
    assert receiver.lastMessage == 'HELLO'
    assert sender.lastMessage == 'OK'
  }

}

In this recipe, we also assume that you are familiar with Apache Ant, and you know how to install it. It also makes sense to use Apache Ivy for dependency management to simplify the dependencies download. So, we assume that Ant has the Ivy (http://ant.apache.org/ivy/) extension installed in ANT_HOME/lib.

How to do it...

At this point, we are going to create a fully functional Ant build for the project defined previously.

  1. Let's start with the following partial build.xml that you need to place in the root directory of our project:
    <project xmlns:ivy="antlib:org.apache.ivy.ant"
             basedir="."
             default="test"
             name="build-groovy">
      <property name="lib.dir"
                value="lib" />
      <property name="build.dir"
                value="output" />
      <property name="src.main.dir"
                value="src/main/groovy" />
      <property name="src.test.dir"
                value="src/test/groovy" />
      <property name="classes.main.dir"
                value="${build.dir}/main" />
      <property name="classes.test.dir"
                value="${build.dir}/test" />
      <property name="test.result.dir"
                value="${build.dir}/test-results" />
      <path id="lib.path.id">
        <fileset dir="${lib.dir}" />
      </path>
      <path id="runtime.path.id">
        <path refid="lib.path.id" />
        <path location="${classes.main.dir}" />
      </path>
      <path id="test.path.id">
        <path refid="runtime.path.id" />
        <path location="${classes.test.dir}" />
      </path>
      <!-- tasks -->
    </project>
  2. In order to start using Ivy, we need to define a dependency descriptor file, ivy.xml, and place it in the same directory as build.xml:
    <ivy-module version="2.0">
      <info organisation="org.groovy.cookbook"
            module="build-groovy" />
      <dependencies>
        <dependency org="org.codehaus.groovy"
                    name="groovy-all"
                    rev="2.1.6"
                    transitive="false" />
        <dependency org="junit"
                    name="junit"
                    rev="4.10" />
      </dependencies>
    </ivy-module>
  3. Once the dependencies are added, we can define the clean and prepare targets inside build.xml. Replace the <!-- tasks --> comment in the build.xml file with the following snippet:
    <target name="clean"
            description="Clean output directory">
      <delete dir="${build.dir}" />
    </target>
    <target name="prepare"
            description="Get dependencies and create folders">
      <ivy:retrieve />
      <mkdir dir="${classes.main.dir}" />
      <mkdir dir="${classes.test.dir}" />
      <mkdir dir="${test.result.dir}" />
      <taskdef name="groovyc"
               classname="org.codehaus.groovy.ant.Groovyc"
               classpathref="lib.path.id" />
    </target>
  4. Now we are ready to define the compile target:
    <target name="compile"
            depends="prepare"
            description="Compile Groovy code">
      <groovyc srcdir="${src.main.dir}"
               destdir="${classes.main.dir}"
               classpathref="lib.path.id" />
    </target>
  5. We can then define a similar target, testCompile, to compile our test code:
    <target name="testCompile"
            depends="prepare, compile"
            description="Compile Groovy unit tests">
      <groovyc srcdir="${src.test.dir}"
               destdir="${classes.test.dir}"
               classpathref="runtime.path.id" />
    </target>
  6. And finally we define a target for running unit tests:
    <target name="test"
            depends="testCompile"
            description="Run unit tests">
      <junit>
        <classpath refid="test.path.id" />
        <batchtest todir="${test.result.dir}">
          <fileset dir="${classes.test.dir}" includes="**/*" />
          <formatter type="plain" />
        </batchtest>
      </junit>
    </target>
  7. In order to build and test the project, we can just call the declared Ant targets:
    ant clean test

    You should get an output similar to the following:

    Buildfile: build.xml
    clean:
       [delete] Deleting directory output
    prepare:
    [ivy:retrieve] <...skipped part...>
        [mkdir] Created dir: outputmain
        [mkdir] Created dir: output	est
        [mkdir] Created dir: output	est-results
    compile:
       [groovyc] Compiling 3 source files to outputmain
    testCompile:
       [groovyc] Compiling 1 source file to output	est
    test:
    BUILD SUCCESSFUL
    Total time: 3 seconds

How it works...

There are several properties and paths defined in Ant's build.xml script:

  • The lib.dir property refers to a directory where Apache Ivy puts all the downloaded dependencies
  • build.dir will contain all the files we produce during the compilation and build
  • src.main.dir and src.test.dir refer to the directories holding the Groovy main and test code respectively
  • classes.main.dir and classes.test.dir hold the compiled classes for the main and test code
  • test.result.dir will contain the JUnit test results
  • lib.path.id is reference to all the libraries imported by Ivy
  • runtime.path.id appends all the compiled classes to lib.path.id
  • test.path.id also appends the compiled test classes to runtime.path.id

Step 2 shows the Ivy dependencies file: there are only 2 dependencies declared for this project: groovy-all library for actually running the Groovy code and junit library for tests. The groovy -all artifact has the transitive property set to false, because the main Groovy library is enough for running this project and we don't need to download other dependencies.

Now let's go into more detail of our Ant script targets. The clean target just deletes the output folder. The prepare target does several things:

  • Downloads all the dependencies specified in the ivy.xml file,
  • Creates all output folders,
  • Holds the definition of the groovyc task that we will use for compiling Groovy code.

As you can notice we specify the location of Groovy sources through the srcdir attribute, and the output folder of compiled classes through desdir attribute. classpathref is pointing to the classpath variable, lib.path.id, which contains the groovy-all library that holds the Groovy compiler.

The groovyc task has a number of options, including the initial and maximum memory size (memoryInitialSize, memoryMaximumSize) and the file encoding (encoding). A more exhaustive explanation of these attributes can be found in the documentation.

Finally, the testing target does not differ from what you would normally do for running Java unit tests. That is possible only because Groovy code is actually compiled into Java byte code.

There's more...

If the same source folder contains both Java and Groovy code, the groovyc Ant task can run in joint compilation mode. In this mode, the Groovy compiler creates Java files out of the Groovy source code and feeds them directly to the Java javac, which proceeds to compile them along with the standard Java files. In order to enable the joint compilation, a javac nested element must be added to the groovyc task:

<target name="compile"
        depends="prepare"
        description="Compile Groovy and Java code">
  <groovyc srcdir="${src.main.dir}"
           destdir="${classes.main.dir}"
           classpathref="lib.path.id">
    <javac source="1.6" target="1.6" debug="on" />
  </groovyc>
</target>

So far, this recipe has demonstrated how to compile Groovy code using Ant. However, the integration between Groovy and Ant can go further than that: Groovy can be used to extend Ant capabilities by calling embedded or external Groovy scripts from an Ant build file.

Ant can call Groovy scripts with the groovy task that is also part of the groovy-all library. To show how this can work, we can extend the prepare target with another taskdef as follows:

<taskdef name="groovy"
         classname="org.codehaus.groovy.ant.Groovy"
         classpathref="lib.path.id" />

The taskdef declaration should be placed between properties and targets definitions.

Once the taskdef is declared, the groovy task can be called from within any target. For example, the following target, info, prints the project name and the list of dependencies using a Groovy embedded script:

<target name="info"
        depends="prepare"
        description="Print project information">
  <groovy>
    println "Name: $project.name"
    println 'Dependencies: '
    project.references."lib.path.id".each { println it.name }
  </groovy>
</target>

The embedded Groovy script has access to the Ant API and script variables from within the script body. The output of running the ant info command will display an output similar to the following:

info:
   [groovy] Name: build-groovy
   [groovy] Dependencies:
   [groovy] groovy-all-2.1.6-javadoc.jar
   [groovy] groovy-all-2.1.6-sources.jar
   [groovy] groovy-all-2.1.6.jar
   [groovy] hamcrest-core-1.1.jar
   [groovy] junit-4.10-javadoc.jar
   [groovy] junit-4.10-sources.jar
   [groovy] junit-4.10.jar

BUILD SUCCESSFUL
Total time: 2 seconds

You can also externalize your scripts and run them by specifying the src attribute of the groovy task:

<groovy src="info.groovy"/>

See also

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset