Skip navigation.

Mixed Groovy Java Compilation With Ant

Update: Mixed compilation is already enabled in the development branch of groovy!

Special note: The method described here works right now (September 2007). But the situtation will be changing soon. Groovy 1.1 should have a compiler that can automatically resolves all problems with mixed compilation. And it may have new ant tasks.

Groovy classes compile to 100% standard .class files that can run on any JVM-- so, that means you can write a mixed Java/Groovy project just by mixing Java and Groovy class right? Yes and no. The answer is yes because all Groovy projects are mixed because they use and actually depend on the standard JDK as well as a few extras (which have been compiled into the standard Groovy jar). You can use any .class files or any pre-compiled jars you want from groovy seamlessly. But-- you might have noticed-- I said ".class files" not classes and "pre-compiled jars" rather than "packages". To paraphrase Bill Clinton it depends on what the definition of "is", is. As in: "is" that class compiled, right now, into a valid .class file? vs. "is" that class a valid class the can be compiled into a .class file. Long story short the necessary Groovy/Java linker or linking stage is not available yet. As of this writing it's development is a focus of the Groovy mailing list-- but it's not here yet. The standard javac can link (and compile) any .java source files together and groovyc can link and compile any .groovy sources together (again with any Java .class files) but there's nothing that can take a of mixed .groovy and .java source files that depend on each other and produce .class files and therefore a complete jar. Right now there are a couple options:

  1. Write a makefile, ant file or script to compile files one by one in the correct order.
  2. Use c-like #IFDEF statements in your source files to indicate what the sources depends on. (Basically same thing as #1).
  3. Compile big modules in two or more stages. The modules can only depend on modules in and earlier (compiled first) stage.

The first option isn't going to be fun and still won't deal with a circular dependancy like: chicken.groovy depends on egg.java which depends on mommychicken.groovy and daddychicken.groovy. The second option, is basically the same as the first-- but with an effort to move some of the information into the sources. What I went with is the third option. I am writing my application's networking and xml parsers in Java and writing other large modules like the GUI and Database access in Groovy. The Groovy modules depend on the Java modules but the Java modules don't depend on the Groovy modules. So, in other words, it's basically a Groovy app with the speed critical parts writing in Java. I have two big codebases, a lower Java one and a higher Groovy one, the higher one depending on the lower one. This is compiled with an ant build file. It compiles all my Java stuff into a jar and then compiles the Groovy stuff using my jar along with all the others. The groovyc ant task made it difficult to do this without bundling the Java classes into a jar.

It's not perfect-- namely it's slow, but hopefully the 'real' solution will come soon enough :)


	<?xml version="1.0" encoding="UTF-8"?><
	<project name="feedback" default="compile" basedir=".">

		<property name="src" location="src"/>
		<property name="bin" location="bin"/>
		<property name="lib" location="lib"/>
		<property name="dist" location="dist"/>

		<property name="jarfile" location="${dist}/project.jar"/>
		<property name="compile.debug" value="true"/>

		<fileset id="lib.jars" dir="${lib}">
			<include name="**/*.jar"/>
		</fileset>

		<path id="lib.path">
			<fileset refid="lib.jars"/>
		</path>

		<path id="groovyclasspath">
			<fileset refid="lib.jars"/>
			<pathelement path="${dist}/javaclasses.jar"/>
		</path>

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

		<target name="clean" description="Remove build and dist directories">
			<delete dir="${bin}"/>
			<delete dir="${dist}"/>
		</target>

		<target name="init">
			<mkdir dir="${bin}"/>
			<mkdir dir="${dist}"/>
			<mkdir dir="${lib}"/>
		</target>

		<target name="compilejava" depends="clean,init">
			<javac 
				srcdir="${src}" 
				destdir="${bin}"
				source="1.4" target="1.4"
				includeAntRuntime="no"
				classpathref="lib.path" 
				debug="${compile.debug}">
			</javac>
			<jar jarfile="${dist}/javaclasses.jar" basedir="${bin}"></jar>
		</target>

		<target name="compilegroovy" depends="clean,init,compilejava">
			<groovyc 
				srcdir="${src}" 
				destdir="${bin}" 
				classpathref="groovyclasspath">
			</groovyc>

			<!-- The groovy jar must be compiled right in -->
			<jar jarfile="${jarfile}" basedir="${bin}" manifest="Manifest">
				<manifest>
					<attribute 
					name="Main-Class" 
					value="net.glenp.feedbackbrowser.main.Main"
					/>
				</manifest>
				<zipgroupfileset refid="lib.jars"/>
			</jar>
		</target>

		<target 
			name="compile" 
			description="Compile code" 
			depends="clean,init,compilejava,compilegroovy">
		</target>

	</project>

There is also a Manifest file that just contains the name of the main class:

Main-Class: net.glenp.feedbackbrowser.main.Main