- Groovy 2 Cookbook
- Andrey Adamovich Luciano Fiandesio
- 1264字
- 2021-07-23 15:57:23
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.
- 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>
- In order to start using Ivy, we need to define a dependency descriptor file,
ivy.xml
, and place it in the same directory asbuild.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>
- Once the dependencies are added, we can define the
clean
andprepare
targets insidebuild.xml
. Replace the<!-- tasks -->
comment in thebuild.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>
- 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>
- 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>
- 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>
- 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: output\main [mkdir] Created dir: output\test [mkdir] Created dir: output\test-results compile: [groovyc] Compiling 3 source files to output\main testCompile: [groovyc] Compiling 1 source file to output\test 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 buildsrc.main.dir
andsrc.test.dir
refer to the directories holding the Groovy main and test code respectivelyclasses.main.dir
andclasses.test.dir
hold the compiled classes for the main and test codetest.result.dir
will contain the JUnit test resultslib.path.id
is reference to all the libraries imported by Ivyruntime.path.id
appends all the compiled classes tolib.path.id
test.path.id
also appends the compiled test classes toruntime.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
- The Groovy Compile Ant task documentation: http://groovy.codehaus.org/The+groovyc+Ant+Task
- The Groovy Ant task documentation: http://groovy.codehaus.org/The+groovy+Ant+Task
- Go Web編程
- 精通Nginx(第2版)
- Apache ZooKeeper Essentials
- Responsive Web Design with HTML5 and CSS3
- C語(yǔ)言程序設(shè)計(jì)教程(第2版)
- HBase從入門(mén)到實(shí)戰(zhàn)
- 數(shù)據(jù)結(jié)構(gòu)(Java語(yǔ)言描述)
- Python自然語(yǔ)言處理(微課版)
- Symfony2 Essentials
- Spring Boot企業(yè)級(jí)項(xiàng)目開(kāi)發(fā)實(shí)戰(zhàn)
- OpenStack Orchestration
- Mastering Web Application Development with AngularJS
- Bootstrap for Rails
- OpenCV Android Programming By Example
- Mastering Python