Wednesday, April 1, 2009

Accessing the Maven runtime classpath in a Groovy script

Lately, I've been involved in converting a good portion of our source tree that's currently built with Ant over to building with Maven. We have a number of Groovy scripts that we use throughout our build, and I've had great success with the GMaven plugin to get things working. I ran into one snag, though, when I was trying to run one of our Groovy scripts that generates Textile wiki markup via a custom Javadoc doclet.

To get the doclet to run, I needed to supply the path/classpath where it could be found, and since the doclet is built during the project's compile phase, I needed access to the Maven runtime classpath to get the documentation built (admittedly, it shouldn't be part of the "project", but if it were, instead, an outside dependency, I'd still need access to the Maven compile classpath, so the problem is pretty much the same). The Maven AntRun plugin provides a nice reference to that classpath via the "maven.runtime.classpath" property that can be accessed in the Maven Ant targets, so I was hoping that GMaven would provide the same sort of thing, especially since they give good access to an AntBuilder automatically.

At this point, though, they don't, and I really didn't want to resort to embedding Ant in our Maven build just to run a Groovy script (for a number of reasons). After a good amount of experimentation and searching, I found this GMaven Jira entry that gives a little bit of guidance for how a GMaven Mojo might build those same kind of classpath references. It didn't really address how to get those classpaths in an executed script, though, but Groovy is Groovy, so I figured it could be done :). Experimenting with what's laid out there and digging into the Ant API docs, here's a what I came up with:

First, of course, you need to get GMaven to run during your build. The GMaven documentation is great for showing how to edit your POM to call a Groovy script during the build. Next, in the Groovy script you want to run, you need to import org.apache.tools.ant.types.Path, and instantiate a new Path object (before you want to reference the path, naturally). When you make a new Path object, you need to give it a reference to the Ant Project to which it belongs, and, as I mentioned, GMaven gives you reference to an AntBuilder (via the "ant" default variable) where you can get that project. The code so far might look like this:

import org.apache.tools.ant.types.Path

Path runtimePath = new Path(ant.antProject)


Once you have that Path object, you need to tell it what its path should look like. Since we're trying to set the runtime classpath Maven builds, we need to find out what JAR files and compiled classes are in Maven's runtime classpath. Fortunately, GMaven also gives us access to a MavenProject object by default, referenced by the default variable "project" in the script. We can build up our path, then, by joining the different pieces of the MavenProject's runtimeClasspathElements and setting that to our Path's path:

runtimePath.path = project.runtimeClasspathElements.join(File.pathSeparator)


Finally, in order to reference that path by name in our script where we need it, we have to add a reference to it in our Ant Project object:

ant.antProject.addReference('runtime.classpath',runtimePath)


The final code for building the runtime classpath looks like this:

import org.apache.tools.ant.types.Path

Path runtimePath = new Path(ant.antProject)
runtimePath.path = project.runtimeClasspathElements.join(File.pathSeparator)
ant.antProject.addReference('runtime.classpath',runtimePath)


Once we have that defined, we can reference it in the script just like we would any other Ant reference. For example, in the project I'm working on, I need to supply a path reference to the Ant Javadoc task. Since I named the path above "runtime.classpath", I set up my Javadoc task call in the Groovy script like this:

ant.javadoc(... docletpathref: 'runtime.classpath' ...) {
... more Groovy code to execute ...
}


While it would definitely be easier if GMaven provided these classpaths out of the box, it's actually not that hard to build them up in the script when you need them. By the way, you can do the same thing with the Maven compile classpath: instead of referencing "project.runtimeClasspathElements" when building the path, you use "project.compileClasspathElements". If you need the plugin classpath, the process is a little more complex, but if you get what's going on above, then you can probably follow the example in the GMaven Jira post to build the plugin classpath.

No comments: